This commit is contained in:
emmymayo
2025-02-05 23:15:46 +01:00
commit 7269c99357
16995 changed files with 3389680 additions and 0 deletions
@@ -0,0 +1,36 @@
<?php
/**
* Module Name: Blaze
* Module Description: Grow your audience by promoting your content across Tumblr and WordPress.com.
* Sort Order: 22
* Recommendation Order: 12
* First Introduced: 12.3
* Requires Connection: Yes
* Auto Activate: Yes
* Module Tags: Traffic, Social
* Additional Search Queries: advertising, ads
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Blaze;
Blaze::init();
/**
* Remove post row Blaze actions in the Jetpack plugin.
* Keep them on for products.
*
* @param bool $are_quick_links_enabled Should Blaze row actions be enabled.
* @param WP_Post $post The current post in the post list table.
*
* @return bool
*/
function jetpack_blaze_post_row_actions_disable( $are_quick_links_enabled, $post ) {
if ( 'product' !== $post->post_type ) {
return false;
}
return $are_quick_links_enabled;
}
add_filter( 'jetpack_blaze_post_row_actions_enable', 'jetpack_blaze_post_row_actions_disable', 10, 2 );
@@ -0,0 +1,46 @@
<?php
/**
* Module Name: Blocks
* Module Description: Add additional blocks to your site and post editors.
* Sort Order: 5
* First Introduced: 13.9-a.8
* Requires Connection: No
* Auto Activate: Yes
* Module Tags: blocks
* Feature: Writing
*
* @package automattic/jetpack
*/
add_action( 'jetpack_activate_module_blocks', 'jetpack_blocks_activate_module' );
/**
* Actions needed upon activating the blocks module.
*
* There is a legacy option to disable Jetpack blocks that we'll delete when this module is activated.
* Via jetpack_get_default_modules filter, we remove blocks from the default if the option is true.
* We'll leave that in place so _until the module is activated_ we will be sure to respect the previous
* setting.
*
* @since 13.9
* @return void
*/
function jetpack_blocks_activate_module() {
delete_option( 'jetpack_blocks_disabled' ); // The function will check and return early if not present.
}
Jetpack_Gutenberg::load_block_editor_extensions();
Jetpack_Gutenberg::load_independent_blocks();
Jetpack_Gutenberg::register_block_metadata_collection();
/**
* We've switched from enqueue_block_editor_assets to enqueue_block_assets in WP-Admin because the assets with the former are loaded on the main site-editor.php.
*
* With the latter, the assets are now loaded in the SE iframe; the implementation is now faster because Gutenberg doesn't need to inject the assets in the iframe on client-side.
*/
if ( is_admin() ) {
add_action( 'enqueue_block_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
} else {
add_action( 'enqueue_block_editor_assets', array( 'Jetpack_Gutenberg', 'enqueue_block_editor_assets' ) );
}
add_filter( 'render_block', array( 'Jetpack_Gutenberg', 'display_deprecated_block_message' ), 10, 2 );
@@ -0,0 +1,20 @@
<?php
/**
* Module Name: Carousel
* Module Description: Display images and galleries in a gorgeous, full-screen browsing experience
* Sort Order: 22
* Recommendation Order: 12
* First Introduced: 1.5
* Requires Connection: No
* Auto Activate: No
* Module Tags: Photos and Videos
* Feature: Appearance
* Additional Search Queries: gallery, carousel, diaporama, slideshow, images, lightbox, exif, metadata, image, creator
*
* @package automattic/jetpack
*/
/**
* Require the jetpack-carousel module code
*/
require __DIR__ . '/carousel/jetpack-carousel.php';
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
# Carousel Swiper.js integration
Currently the Jetpack carousel is using the Swiper.js library to provide the slider functionality. In order to reduce the bundle size of this library a custom build is used.
## How to generate a new custom build
- Check out the https://github.com/Automattic/swiper repo
- Switch to the `jetpack-build` branch and follow the instructions on the readme
## ToDo
- Automate this process somehow
@@ -0,0 +1,409 @@
/* eslint-disable */
/* If a new version is imported from swiper the selectors should all be namespaced with .jp-carousel-overlay
/* to prevent clashes with other plugins that are overrding swiperjs css
*/
/**
* Swiper 6.7.0
* Most modern mobile touch slider and framework with hardware accelerated transitions
* https://swiperjs.com
*
* Copyright 2014-2021 Vladimir Kharlampidi
*
* Released under the MIT License
*
* Released on: June 22, 2021
*/
@font-face {
font-family: 'swiper-icons';
src: url( 'data:application/font-woff;charset=utf-8;base64, d09GRgABAAAAAAZgABAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAGRAAAABoAAAAci6qHkUdERUYAAAWgAAAAIwAAACQAYABXR1BPUwAABhQAAAAuAAAANuAY7+xHU1VCAAAFxAAAAFAAAABm2fPczU9TLzIAAAHcAAAASgAAAGBP9V5RY21hcAAAAkQAAACIAAABYt6F0cBjdnQgAAACzAAAAAQAAAAEABEBRGdhc3AAAAWYAAAACAAAAAj//wADZ2x5ZgAAAywAAADMAAAD2MHtryVoZWFkAAABbAAAADAAAAA2E2+eoWhoZWEAAAGcAAAAHwAAACQC9gDzaG10eAAAAigAAAAZAAAArgJkABFsb2NhAAAC0AAAAFoAAABaFQAUGG1heHAAAAG8AAAAHwAAACAAcABAbmFtZQAAA/gAAAE5AAACXvFdBwlwb3N0AAAFNAAAAGIAAACE5s74hXjaY2BkYGAAYpf5Hu/j+W2+MnAzMYDAzaX6QjD6/4//Bxj5GA8AuRwMYGkAPywL13jaY2BkYGA88P8Agx4j+/8fQDYfA1AEBWgDAIB2BOoAeNpjYGRgYNBh4GdgYgABEMnIABJzYNADCQAACWgAsQB42mNgYfzCOIGBlYGB0YcxjYGBwR1Kf2WQZGhhYGBiYGVmgAFGBiQQkOaawtDAoMBQxXjg/wEGPcYDDA4wNUA2CCgwsAAAO4EL6gAAeNpj2M0gyAACqxgGNWBkZ2D4/wMA+xkDdgAAAHjaY2BgYGaAYBkGRgYQiAHyGMF8FgYHIM3DwMHABGQrMOgyWDLEM1T9/w8UBfEMgLzE////P/5//f/V/xv+r4eaAAeMbAxwIUYmIMHEgKYAYjUcsDAwsLKxc3BycfPw8jEQA/gZBASFhEVExcQlJKWkZWTl5BUUlZRVVNXUNTQZBgMAAMR+E+gAEQFEAAAAKgAqACoANAA+AEgAUgBcAGYAcAB6AIQAjgCYAKIArAC2AMAAygDUAN4A6ADyAPwBBgEQARoBJAEuATgBQgFMAVYBYAFqAXQBfgGIAZIBnAGmAbIBzgHsAAB42u2NMQ6CUAyGW568x9AneYYgm4MJbhKFaExIOAVX8ApewSt4Bic4AfeAid3VOBixDxfPYEza5O+Xfi04YADggiUIULCuEJK8VhO4bSvpdnktHI5QCYtdi2sl8ZnXaHlqUrNKzdKcT8cjlq+rwZSvIVczNiezsfnP/uznmfPFBNODM2K7MTQ45YEAZqGP81AmGGcF3iPqOop0r1SPTaTbVkfUe4HXj97wYE+yNwWYxwWu4v1ugWHgo3S1XdZEVqWM7ET0cfnLGxWfkgR42o2PvWrDMBSFj/IHLaF0zKjRgdiVMwScNRAoWUoH78Y2icB/yIY09An6AH2Bdu/UB+yxopYshQiEvnvu0dURgDt8QeC8PDw7Fpji3fEA4z/PEJ6YOB5hKh4dj3EvXhxPqH/SKUY3rJ7srZ4FZnh1PMAtPhwP6fl2PMJMPDgeQ4rY8YT6Gzao0eAEA409DuggmTnFnOcSCiEiLMgxCiTI6Cq5DZUd3Qmp10vO0LaLTd2cjN4fOumlc7lUYbSQcZFkutRG7g6JKZKy0RmdLY680CDnEJ+UMkpFFe1RN7nxdVpXrC4aTtnaurOnYercZg2YVmLN/d/gczfEimrE/fs/bOuq29Zmn8tloORaXgZgGa78yO9/cnXm2BpaGvq25Dv9S4E9+5SIc9PqupJKhYFSSl47+Qcr1mYNAAAAeNptw0cKwkAAAMDZJA8Q7OUJvkLsPfZ6zFVERPy8qHh2YER+3i/BP83vIBLLySsoKimrqKqpa2hp6+jq6RsYGhmbmJqZSy0sraxtbO3sHRydnEMU4uR6yx7JJXveP7WrDycAAAAAAAH//wACeNpjYGRgYOABYhkgZgJCZgZNBkYGLQZtIJsFLMYAAAw3ALgAeNolizEKgDAQBCchRbC2sFER0YD6qVQiBCv/H9ezGI6Z5XBAw8CBK/m5iQQVauVbXLnOrMZv2oLdKFa8Pjuru2hJzGabmOSLzNMzvutpB3N42mNgZGBg4GKQYzBhYMxJLMlj4GBgAYow/P/PAJJhLM6sSoWKfWCAAwDAjgbRAAB42mNgYGBkAIIbCZo5IPrmUn0hGA0AO8EFTQAA' )
format( 'woff' );
font-weight: 400;
font-style: normal;
}
:root {
--swiper-theme-color: #007aff;
}
.jp-carousel-overlay .swiper-container {
margin-left: auto;
margin-right: auto;
position: relative;
overflow: hidden;
list-style: none;
padding: 0;
/* Fix of Webkit flickering */
z-index: 1;
}
.jp-carousel-overlay .swiper-container-vertical > .swiper-wrapper {
flex-direction: column;
}
.jp-carousel-overlay .swiper-wrapper {
position: relative;
width: 100%;
height: 100%;
z-index: 1;
display: flex;
transition-property: transform;
box-sizing: content-box;
}
.jp-carousel-overlay .swiper-container-android .swiper-slide,
.jp-carousel-overlay .swiper-wrapper {
transform: translate3d( 0px, 0, 0 );
}
.jp-carousel-overlay .swiper-container-multirow > .swiper-wrapper {
flex-wrap: wrap;
}
.jp-carousel-overlay .swiper-container-multirow-column > .swiper-wrapper {
flex-wrap: wrap;
flex-direction: column;
}
.jp-carousel-overlay .swiper-container-free-mode > .swiper-wrapper {
transition-timing-function: ease-out;
margin: 0 auto;
}
.jp-carousel-overlay .swiper-container-pointer-events {
touch-action: pan-y;
}
.jp-carousel-overlay .swiper-container-pointer-events.swiper-container-vertical {
touch-action: pan-x;
}
.jp-carousel-overlay .swiper-slide {
flex-shrink: 0;
width: 100%;
height: 100%;
position: relative;
transition-property: transform;
}
.jp-carousel-overlay .swiper-slide-invisible-blank {
visibility: hidden;
}
/* Auto Height */
.jp-carousel-overlay .swiper-container-autoheight,
.jp-carousel-overlay .swiper-container-autoheight .swiper-slide {
height: auto;
}
.jp-carousel-overlay .swiper-container-autoheight .swiper-wrapper {
align-items: flex-start;
transition-property: transform, height;
}
/* 3D Effects */
.jp-carousel-overlay .swiper-container-3d {
perspective: 1200px;
}
.jp-carousel-overlay .swiper-container-3d .swiper-wrapper,
.jp-carousel-overlay .swiper-container-3d .swiper-slide,
.jp-carousel-overlay .swiper-container-3d .swiper-slide-shadow-left,
.jp-carousel-overlay .swiper-container-3d .swiper-slide-shadow-right,
.jp-carousel-overlay .swiper-container-3d .swiper-slide-shadow-top,
.jp-carousel-overlay .swiper-container-3d .swiper-slide-shadow-bottom,
.jp-carousel-overlay .swiper-container-3d .swiper-cube-shadow {
transform-style: preserve-3d;
}
.jp-carousel-overlay .swiper-container-3d .swiper-slide-shadow-left,
.jp-carousel-overlay .swiper-container-3d .swiper-slide-shadow-right,
.jp-carousel-overlay .swiper-container-3d .swiper-slide-shadow-top,
.jp-carousel-overlay .swiper-container-3d .swiper-slide-shadow-bottom {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
.jp-carousel-overlay .swiper-container-3d .swiper-slide-shadow-left {
background-image: linear-gradient( to left, rgba( 0, 0, 0, 0.5 ), rgba( 0, 0, 0, 0 ) );
}
.jp-carousel-overlay .swiper-container-3d .swiper-slide-shadow-right {
background-image: linear-gradient( to right, rgba( 0, 0, 0, 0.5 ), rgba( 0, 0, 0, 0 ) );
}
.jp-carousel-overlay .swiper-container-3d .swiper-slide-shadow-top {
background-image: linear-gradient( to top, rgba( 0, 0, 0, 0.5 ), rgba( 0, 0, 0, 0 ) );
}
.jp-carousel-overlay .swiper-container-3d .swiper-slide-shadow-bottom {
background-image: linear-gradient( to bottom, rgba( 0, 0, 0, 0.5 ), rgba( 0, 0, 0, 0 ) );
}
/* CSS Mode */
.jp-carousel-overlay .swiper-container-css-mode > .swiper-wrapper {
overflow: auto;
scrollbar-width: none;
/* For Firefox */
-ms-overflow-style: none;
/* For Internet Explorer and Edge */
}
.jp-carousel-overlay .swiper-container-css-mode > .swiper-wrapper::-webkit-scrollbar {
display: none;
}
.jp-carousel-overlay .swiper-container-css-mode > .swiper-wrapper > .swiper-slide {
scroll-snap-align: start start;
}
.jp-carousel-overlay .swiper-container-horizontal.swiper-container-css-mode > .swiper-wrapper {
scroll-snap-type: x mandatory;
}
.jp-carousel-overlay .swiper-container-vertical.swiper-container-css-mode > .swiper-wrapper {
scroll-snap-type: y mandatory;
}
:root {
--swiper-navigation-size: 44px;
/*
--swiper-navigation-color: var(--swiper-theme-color);
*/
}
.jp-carousel-overlay .swiper-button-prev,
.jp-carousel-overlay .swiper-button-next {
position: absolute;
top: 50%;
width: calc( var( --swiper-navigation-size ) / 44 * 27 );
height: var( --swiper-navigation-size );
margin-top: calc( 0px - ( var( --swiper-navigation-size ) / 2 ) );
z-index: 10;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: var( --swiper-navigation-color, var( --swiper-theme-color ) );
}
.jp-carousel-overlay .swiper-button-prev.swiper-button-disabled,
.jp-carousel-overlay .swiper-button-next.swiper-button-disabled {
opacity: 0.35;
cursor: auto;
pointer-events: none;
}
.jp-carousel-overlay .swiper-button-prev:after,
.jp-carousel-overlay .swiper-button-next:after {
font-family: swiper-icons;
font-size: var( --swiper-navigation-size );
text-transform: none !important;
letter-spacing: 0;
text-transform: none;
font-variant: initial;
line-height: 1;
}
.jp-carousel-overlay .swiper-button-prev,
.jp-carousel-overlay .swiper-container-rtl .swiper-button-next {
left: 10px;
right: auto;
}
.jp-carousel-overlay .swiper-button-prev:after,
.jp-carousel-overlay .swiper-container-rtl .swiper-button-next:after {
content: 'prev';
}
.jp-carousel-overlay .swiper-button-next,
.jp-carousel-overlay .swiper-container-rtl .swiper-button-prev {
right: 10px;
left: auto;
}
.jp-carousel-overlay .swiper-button-next:after,
.jp-carousel-overlay .swiper-container-rtl .swiper-button-prev:after {
content: 'next';
}
.jp-carousel-overlay .swiper-button-prev.swiper-button-white,
.jp-carousel-overlay .swiper-button-next.swiper-button-white {
--swiper-navigation-color: #ffffff;
}
.jp-carousel-overlay .swiper-button-prev.swiper-button-black,
.jp-carousel-overlay .swiper-button-next.swiper-button-black {
--swiper-navigation-color: #000000;
}
.jp-carousel-overlay .swiper-button-lock {
display: none;
}
:root {
/*
--swiper-pagination-color: var(--swiper-theme-color);
*/
}
.jp-carousel-overlay .swiper-pagination {
position: absolute;
text-align: center;
transition: 300ms opacity;
transform: translate3d( 0, 0, 0 );
z-index: 10;
}
.jp-carousel-overlay .swiper-pagination.swiper-pagination-hidden {
opacity: 0;
}
/* Common Styles */
.jp-carousel-overlay .swiper-pagination-fraction,
.jp-carousel-overlay .swiper-pagination-custom,
.jp-carousel-overlay .swiper-container-horizontal > .swiper-pagination-bullets {
bottom: 10px;
left: 0;
width: 100%;
}
/* Bullets */
.jp-carousel-overlay .swiper-pagination-bullets-dynamic {
overflow: hidden;
font-size: 0;
}
.jp-carousel-overlay .swiper-pagination-bullets-dynamic .swiper-pagination-bullet {
transform: scale( 0.33 );
position: relative;
}
.jp-carousel-overlay .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active {
transform: scale( 1 );
}
.jp-carousel-overlay .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-main {
transform: scale( 1 );
}
.jp-carousel-overlay .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev {
transform: scale( 0.66 );
}
.jp-carousel-overlay .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev-prev {
transform: scale( 0.33 );
}
.jp-carousel-overlay .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next {
transform: scale( 0.66 );
}
.jp-carousel-overlay .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next-next {
transform: scale( 0.33 );
}
.jp-carousel-overlay .swiper-pagination-bullet {
width: 8px;
height: 8px;
display: inline-block;
border-radius: 50%;
background: #000;
opacity: 0.2;
}
.jp-carousel-overlay button.swiper-pagination-bullet {
border: none;
margin: 0;
padding: 0;
box-shadow: none;
-webkit-appearance: none;
appearance: none;
}
.jp-carousel-overlay .swiper-pagination-clickable .swiper-pagination-bullet {
cursor: pointer;
}
.jp-carousel-overlay .swiper-pagination-bullet-active {
opacity: 1;
background: var( --swiper-pagination-color, var( --swiper-theme-color ) );
}
.jp-carousel-overlay .swiper-container-vertical > .swiper-pagination-bullets {
right: 10px;
top: 50%;
transform: translate3d( 0px, -50%, 0 );
}
.jp-carousel-overlay
.swiper-container-vertical
> .swiper-pagination-bullets
.swiper-pagination-bullet {
margin: 6px 0;
display: block;
}
.jp-carousel-overlay
.swiper-container-vertical
> .swiper-pagination-bullets.swiper-pagination-bullets-dynamic {
top: 50%;
transform: translateY( -50% );
width: 8px;
}
.jp-carousel-overlay
.swiper-container-vertical
> .swiper-pagination-bullets.swiper-pagination-bullets-dynamic
.swiper-pagination-bullet {
display: inline-block;
transition: 200ms transform, 200ms top;
}
.jp-carousel-overlay
.swiper-container-horizontal
> .swiper-pagination-bullets
.swiper-pagination-bullet {
margin: 0 4px;
}
.jp-carousel-overlay
.swiper-container-horizontal
> .swiper-pagination-bullets.swiper-pagination-bullets-dynamic {
left: 50%;
transform: translateX( -50% );
white-space: nowrap;
}
.jp-carousel-overlay
.swiper-container-horizontal
> .swiper-pagination-bullets.swiper-pagination-bullets-dynamic
.swiper-pagination-bullet {
transition: 200ms transform, 200ms left;
}
.jp-carousel-overlay
.swiper-container-horizontal.swiper-container-rtl
> .swiper-pagination-bullets-dynamic
.swiper-pagination-bullet {
transition: 200ms transform, 200ms right;
}
/* Progress */
.jp-carousel-overlay .swiper-pagination-progressbar {
background: rgba( 0, 0, 0, 0.25 );
position: absolute;
}
.jp-carousel-overlay .swiper-pagination-progressbar .swiper-pagination-progressbar-fill {
background: var( --swiper-pagination-color, var( --swiper-theme-color ) );
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
transform: scale( 0 );
transform-origin: left top;
}
.jp-carousel-overlay
.swiper-container-rtl
.swiper-pagination-progressbar
.swiper-pagination-progressbar-fill {
transform-origin: right top;
}
.jp-carousel-overlay .swiper-container-horizontal > .swiper-pagination-progressbar,
.jp-carousel-overlay
.swiper-container-vertical
> .swiper-pagination-progressbar.swiper-pagination-progressbar-opposite {
width: 100%;
height: 4px;
left: 0;
top: 0;
}
.jp-carousel-overlay .swiper-container-vertical > .swiper-pagination-progressbar,
.jp-carousel-overlay
.swiper-container-horizontal
> .swiper-pagination-progressbar.swiper-pagination-progressbar-opposite {
width: 4px;
height: 100%;
left: 0;
top: 0;
}
.jp-carousel-overlay .swiper-pagination-white {
--swiper-pagination-color: #ffffff;
}
.jp-carousel-overlay .swiper-pagination-black {
--swiper-pagination-color: #000000;
}
.jp-carousel-overlay .swiper-pagination-lock {
display: none;
}
.jp-carousel-overlay .swiper-zoom-container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.jp-carousel-overlay .swiper-zoom-container > img,
.jp-carousel-overlay .swiper-zoom-container > svg,
.jp-carousel-overlay .swiper-zoom-container > canvas {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.jp-carousel-overlay .swiper-slide-zoomed {
cursor: move;
}
/* a11y */
.jp-carousel-overlay .swiper-container .swiper-notification {
position: absolute;
left: 0;
top: 0;
pointer-events: none;
opacity: 0;
z-index: -1000;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,26 @@
<?php
/**
* Cloudflare Analytics
* Let WPCOM users automatically insert a Cloudflare analytics JS snippet into their site header.
*
* @deprecated 13.4 Moved to mu-wpcom.
*
* @package automattic/jetpack
*/
namespace Automattic\Jetpack\Cloudflare_Analytics;
_deprecated_file( __FILE__, 'jetpack-13.4' );
/**
* Add Cloudflare Analytics tracking code to the head.
* This is currently only available to Atomic and WordPress.com Simple sites.
*
* @deprecated 13.4 Moved to mu-wpcom.
* @since 9.5.0
*
* @return void
*/
function insert_tracking_id() {
_deprecated_function( __FUNCTION__, 'jetpack-13.4', 'Automattic\\Jetpack\\Jetpack_Mu_Wpcom\\Cloudflare_Analytics\\insert_tracking_id' );
}
@@ -0,0 +1,250 @@
<?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Module Name: Comment Likes
* Module Description: Increase visitor engagement by adding a Like button to comments.
* Sort Order: 39
* Recommendation Order: 17
* First Introduced: 5.1
* Requires Connection: Yes
* Auto Activate: No
* Module Tags: Social
* Additional Search Queries: like widget, like button, like, likes
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Assets;
Assets::add_resource_hint( '//widgets.wp.com', 'dns-prefetch' );
require_once __DIR__ . '/likes/jetpack-likes-master-iframe.php';
require_once __DIR__ . '/likes/jetpack-likes-settings.php';
/**
* Jetpack Comment Like Class
*/
class Jetpack_Comment_Likes {
/**
* Jetpack_Likes_Settings object
*
* @var Jetpack_Likes_Settings
*/
public $settings;
/**
* Blog ID
*
* @var int
*/
public $blog_id;
/**
* Site home URL domain
*
* @var string
*/
public $domain;
/**
* Initialize comment like module
*/
public static function init() {
static $instance = null;
if ( ! $instance ) {
$instance = new Jetpack_Comment_Likes();
}
return $instance;
}
/**
* Construct comment like module.
*/
private function __construct() {
$this->settings = new Jetpack_Likes_Settings();
$this->blog_id = Jetpack_Options::get_option( 'id' );
$url_parts = wp_parse_url( home_url() );
$this->domain = $url_parts['host'];
add_action( 'template_redirect', array( $this, 'frontend_init' ) );
add_action( 'admin_init', array( $this, 'admin_init' ) );
if ( ! Jetpack::is_module_active( 'likes' ) ) {
$active = Jetpack::get_active_modules();
if ( in_array( 'publicize', $active, true ) && ! in_array( 'sharedaddy', $active, true ) ) {
// we have a sharing page but not the global options area.
add_action( 'pre_admin_screen_sharing', array( $this->settings, 'sharing_block' ), 20 );
add_action( 'pre_admin_screen_sharing', array( $this->settings, 'updated_message' ), -10 );
}
if ( ! in_array( 'sharedaddy', $active, true ) ) {
add_action( 'admin_init', array( $this->settings, 'process_update_requests_if_sharedaddy_not_loaded' ) );
add_action( 'sharing_global_options', array( $this->settings, 'admin_settings_showbuttonon_init' ), 19 );
add_action( 'sharing_admin_update', array( $this->settings, 'admin_settings_showbuttonon_callback' ), 19 );
add_action( 'admin_init', array( $this->settings, 'add_meta_box' ) );
} else {
add_filter( 'sharing_meta_box_title', array( $this->settings, 'add_likes_to_sharing_meta_box_title' ) );
add_action( 'start_sharing_meta_box_content', array( $this->settings, 'meta_box_content' ) );
}
add_action( 'save_post', array( $this->settings, 'meta_box_save' ) );
add_action( 'edit_attachment', array( $this->settings, 'meta_box_save' ) );
add_action( 'sharing_global_options', array( $this->settings, 'admin_settings_init' ), 20 );
add_action( 'sharing_admin_update', array( $this->settings, 'admin_settings_callback' ), 20 );
}
}
/**
* Initialize admin section
*/
public function admin_init() {
add_filter( 'manage_edit-comments_columns', array( $this, 'add_like_count_column' ) );
add_action( 'manage_comments_custom_column', array( $this, 'comment_likes_edit_column' ), 10, 2 );
add_action( 'admin_print_styles-edit-comments.php', array( $this, 'enqueue_admin_styles_scripts' ) );
}
/**
* Displays number of comment likes in comment admin page.
*
* @param string $column_name name of the column.
* @param int $comment_id ID of the comment.
*/
public function comment_likes_edit_column( $column_name, $comment_id ) {
if ( 'comment_likes' !== $column_name ) {
return;
}
$permalink = get_permalink( get_the_ID() );
?>
<a
data-comment-id="<?php echo absint( $comment_id ); ?>"
data-blog-id="<?php echo absint( $this->blog_id ); ?>"
class="comment-like-count"
id="comment-like-count-<?php echo absint( $comment_id ); ?>"
href="<?php echo esc_url( $permalink ); ?>#comment-<?php echo absint( $comment_id ); ?>"
>
<span class="like-count">0</span>
</a>
<?php
}
/**
* Enqueue admin style scripts.
*/
public function enqueue_admin_styles_scripts() {
wp_enqueue_style( 'comment-like-count', plugins_url( 'comment-likes/admin-style.css', __FILE__ ), array(), JETPACK__VERSION );
wp_enqueue_script(
'comment-like-count',
Assets::get_file_url_for_environment(
'_inc/build/comment-likes/comment-like-count.min.js',
'modules/comment-likes/comment-like-count.js'
),
array( 'jquery' ),
JETPACK__VERSION,
false
);
}
/**
* Adds like count column to admin page.
*
* @param array $columns column of admin table.
*/
public function add_like_count_column( $columns ) {
$columns['comment_likes'] = '<span class="vers"></span>';
return $columns;
}
/**
* Initialize front end
*/
public function frontend_init() {
if ( class_exists( Jetpack_AMP_Support::class ) && Jetpack_AMP_Support::is_amp_request() ) {
return;
}
add_action( 'wp_enqueue_scripts', array( $this, 'load_styles_register_scripts' ) );
add_filter( 'comment_text', array( $this, 'comment_likes' ), 10, 2 );
}
/**
* Load styling scripts
*/
public function load_styles_register_scripts() {
if ( ! $this->settings->is_likes_visible() ) {
return;
}
if ( ! wp_style_is( 'open-sans', 'registered' ) ) {
wp_register_style( 'open-sans', 'https://fonts.googleapis.com/css?family=Open+Sans', array(), JETPACK__VERSION );
}
wp_enqueue_style( 'jetpack_likes', plugins_url( 'likes/style.css', __FILE__ ), array( 'open-sans' ), JETPACK__VERSION );
wp_enqueue_script( 'jetpack_likes_queuehandler', plugins_url( 'likes/queuehandler.js', __FILE__ ), array(), JETPACK__VERSION, true );
}
/**
* Display like count.
*
* @param string $content text content of the comment itself.
* @param object $comment comment object containing comment data.
*/
public function comment_likes( $content, $comment = null ) {
if ( empty( $comment ) ) {
return $content;
}
if ( ! $this->settings->is_likes_visible() ) {
return $content;
}
$comment_id = get_comment_ID();
if ( empty( $comment_id ) && ! empty( $comment->comment_ID ) ) {
$comment_id = $comment->comment_ID;
}
if ( empty( $content ) || empty( $comment_id ) ) {
return $content;
}
if ( empty( $comment->comment_approved ) ) {
return $content;
}
// In case master iframe hasn't been loaded. This could be the case when Post Likes module is disabled,
// or on pages on which we have comments but post likes are disabled.
if ( false === has_action( 'wp_footer', 'jetpack_likes_master_iframe' ) ) {
add_action( 'wp_footer', 'jetpack_likes_master_iframe', 21 );
}
$uniqid = uniqid();
$src = sprintf( 'https://widgets.wp.com/likes/#blog_id=%1$d&amp;comment_id=%2$d&amp;origin=%3$s&amp;obj_id=%1$d-%2$d-%4$s', $this->blog_id, $comment_id, $this->domain, $uniqid );
$name = sprintf( 'like-comment-frame-%1$d-%2$d-%3$s', $this->blog_id, $comment_id, $uniqid );
$wrapper = sprintf( 'like-comment-wrapper-%1$d-%2$d-%3$s', $this->blog_id, $comment_id, $uniqid );
$html = '';
$html .= "<div class='jetpack-comment-likes-widget-wrapper jetpack-likes-widget-unloaded' id='$wrapper' data-src='$src' data-name='$name'>";
$html .= "<div class='likes-widget-placeholder comment-likes-widget-placeholder comment-likes'><span class='loading'>" . esc_html__( 'Loading...', 'jetpack' ) . '</span></div>';
$html .= "<div class='comment-likes-widget jetpack-likes-widget comment-likes'><span class='comment-like-feedback'></span>";
$html .= "<span class='sd-text-color'></span><a class='sd-link-color'></a>";
$html .= '</div></div>';
/**
* Filters the Comment Likes button content.
*
* @module comment-likes
*
* @since 5.1.0
*
* @param string $html Comment Likes button content.
*/
$like_button = apply_filters( 'comment_like_button', $html );
return $content . $like_button;
}
}
Jetpack_Comment_Likes::init();
@@ -0,0 +1,45 @@
.fixed .column-comment_likes {
width: 5.5em;
padding: 8px 0;
text-align: center;
}
.fixed .column-stats {
width: 5em;
}
.fixed .column-comment_likes .comment-like-count {
box-sizing: border-box;
display: inline-block;
padding: 0 8px;
height: 2em;
margin-top: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
background-color: #787c82;
color: #fff;
font-size: 11px;
line-height: 21px;
}
.fixed .column-comment_likes .comment-like-count:after {
border: none;
}
.fixed .column-comment_likes .comment-like-count:hover {
background-color: #2271b1;
}
.fixed .column-comment_likes .vers:before {
font: normal 20px/1 dashicons;
content: '\f155';
speak: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@media screen and (max-width: 782px) {
.fixed .column-comment_likes {
display: none;
}
}
@@ -0,0 +1,42 @@
jQuery( document ).ready( function ( $ ) {
var jsonAPIbase = 'https://public-api.wordpress.com/rest/v1',
APIqueue = [];
function getCommentLikeCounts() {
$( '.comment-like-count' ).each( function () {
var blogId = $( this ).attr( 'data-blog-id' ),
commentId = $( this ).attr( 'data-comment-id' );
APIqueue.push( '/sites/' + blogId + '/comments/' + commentId + '/likes' );
} );
return $.ajax( {
type: 'GET',
url: jsonAPIbase + '/batch',
dataType: 'jsonp',
data: 'urls[]=' + APIqueue.map( encodeURIComponent ).join( '&urls[]=' ),
success: function ( response ) {
for ( var path in response ) {
if ( ! response[ path ].error_data ) {
var urlPieces = path.split( '/' ),
commentId = urlPieces[ 4 ],
likeCount = response[ path ].found;
if ( likeCount < 1 ) {
return;
}
$( '#comment-like-count-' + commentId )
.find( '.like-count' )
.hide()
.text( likeCount )
.fadeIn();
}
}
},
error: function () {},
} );
}
getCommentLikeCounts();
} );
@@ -0,0 +1,50 @@
<?php
/**
* Module Name: Comments
* Module Description: Let visitors use a WordPress.com or Facebook account to comment
* First Introduced: 1.4
* Sort Order: 20
* Requires Connection: Yes
* Auto Activate: No
* Module Tags: Social
* Feature: Engagement
* Additional Search Queries: comments, comment, facebook, social
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Assets;
Assets::add_resource_hint(
array(
'//jetpack.wordpress.com',
'//s0.wp.com',
'//public-api.wordpress.com',
'//0.gravatar.com',
'//1.gravatar.com',
'//2.gravatar.com',
),
'dns-prefetch'
);
/*
* Add the main commenting system.
*/
require __DIR__ . '/comments/comments.php';
require __DIR__ . '/comments/subscription-modal-on-comment/class-jetpack-subscription-modal-on-comment.php';
if ( is_admin() ) {
/**
* Add the admin functionality.
*/
require __DIR__ . '/comments/admin.php';
}
/**
* Module loader.
*/
function jetpack_comments_load() {
Jetpack::enable_module_configurable( __FILE__ );
}
add_action( 'jetpack_modules_loaded', 'jetpack_comments_load' );
@@ -0,0 +1,233 @@
<?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Jetpack comments admin menu file.
*
* @package automattic/jetpack
*/
/**
* Class Jetpack_Comments_Settings
* This class represents the comments settings functionality.
*/
class Jetpack_Comments_Settings {
/** Variables *************************************************************/
/**
* The Jetpack Comments singleton
*
* @var Highlander_Comments_Base
*/
public $jetpack_comments;
/**
* The default comment form greeting - blank to start with
*
* @var string
*/
public $default_greeting = ''; // Set in constructor.
/**
* The default comment form color scheme - an empty array to start with
*
* @var array
*/
public $color_schemes = array();
/**
* Initialize class
*/
public static function init() {
static $instance = false;
if ( ! $instance ) {
$instance = new Jetpack_Comments_Settings( Jetpack_Comments::init() );
}
return $instance;
}
/**
* Constructor
*
* @param Highlander_Comments_Base $jetpack_comments The Jetpack Comments singleton.
*/
public function __construct( Highlander_Comments_Base $jetpack_comments ) {
$this->jetpack_comments = $jetpack_comments;
// Setup settings.
add_action( 'admin_init', array( $this, 'add_settings' ) );
$this->setup_globals();
}
/** Private Methods ****************************************************** */
/**
* Set any global variables or class variables
*
* @since 1.4
*/
protected function setup_globals() {
// Default option values.
$this->default_greeting = __( 'Leave a Reply', 'jetpack' );
// Possible color schemes.
$this->color_schemes = array(
'light' => __( 'Light', 'jetpack' ),
'dark' => __( 'Dark', 'jetpack' ),
'transparent' => __( 'Transparent', 'jetpack' ),
);
}
/** Settings ************************************************************* */
/**
* Add the Jetpack settings to WordPress's discussions page
*
* @since 1.4
*/
public function add_settings() {
// Create the section.
add_settings_section(
'jetpack_comment_form',
__( 'Comments', 'jetpack' ),
array( $this, 'comment_form_settings_section' ),
'discussion'
);
/**
* Clever Greeting
*/
add_settings_field(
'highlander_comment_form_prompt',
__( 'Greeting Text', 'jetpack' ),
array( $this, 'comment_form_greeting_setting' ),
'discussion',
'jetpack_comment_form'
);
register_setting(
'discussion',
'highlander_comment_form_prompt',
array( $this, 'comment_form_greeting_sanitize' )
);
/**
* Color Scheme
*/
add_settings_field(
'jetpack_comment_form_color_scheme',
__( 'Color Scheme', 'jetpack' ),
array( $this, 'comment_form_color_scheme_setting' ),
'discussion',
'jetpack_comment_form'
);
register_setting(
'discussion',
'jetpack_comment_form_color_scheme',
array( $this, 'comment_form_color_scheme_sanitize' )
);
}
/**
* Discussions setting section blurb
*
* @since 1.4
*/
public function comment_form_settings_section() {
?>
<p id="jetpack-comments-settings"><?php esc_html_e( 'Adjust your Comments form with a clever greeting and color-scheme.', 'jetpack' ); ?></p>
<?php
}
/**
* Custom Comment Greeting Text
*
* @since 1.4
*/
public function comment_form_greeting_setting() {
// The greeting.
$greeting = get_option( 'highlander_comment_form_prompt', $this->default_greeting );
?>
<input type="text" name="highlander_comment_form_prompt" id="jetpack-comment-form-greeting" value="<?php echo esc_attr( $greeting ); ?>" class="regular-text">
<p class="description"><?php esc_html_e( 'A few catchy words to motivate your readers to comment', 'jetpack' ); ?></p>
<?php
}
/**
* Sanitize the clever comment greeting
*
* @since 1.4
* @param string $val The contact form greeting string.
* @return string
*/
public function comment_form_greeting_sanitize( $val ) {
// Delete if empty or the default.
if ( empty( $val ) || ( $this->default_greeting === $val ) ) {
delete_option( 'highlander_comment_form_prompt' );
return false;
}
return wp_kses( $val, array() );
}
/**
* Comment Form Color Scheme Setting
*
* @since 1.4
*/
public function comment_form_color_scheme_setting() {
// The color scheme.
$scheme = get_option( 'jetpack_comment_form_color_scheme', $this->jetpack_comments->default_color_scheme );
?>
<fieldset>
<legend class="screen-reader-text"><?php esc_html_e( 'Color Scheme', 'jetpack' ); ?></legend>
<?php foreach ( $this->color_schemes as $key => $label ) : ?>
<label>
<input type="radio" name="jetpack_comment_form_color_scheme" id="jetpack-comment-form-color-scheme" value="<?php echo esc_attr( $key ); ?>" <?php checked( $scheme, $key ); ?>>
<?php echo esc_attr( $label ); ?>
</label>
<br />
<?php endforeach; ?>
</fieldset>
<?php
}
/**
* Sanitize the color scheme
*
* @since 1.4
* @param string $val The color scheme string.
* @return string
*/
public function comment_form_color_scheme_sanitize( $val ) {
// Delete the option if it's unknown, or the default.
if (
empty( $val ) || ! array_key_exists( $val, $this->color_schemes )
||
$val === $this->jetpack_comments->default_color_scheme
) {
delete_option( 'jetpack_comment_form_color_scheme' );
return false;
}
return $val;
}
}
Jetpack_Comments_Settings::init();
@@ -0,0 +1,350 @@
<?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Jetpack comments base file - where the code shared between WP.com Highlander and Jetpack Highlander is defined
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Image_CDN\Image_CDN_Core;
/**
* All the code shared between WP.com Highlander and Jetpack Highlander
*/
class Highlander_Comments_Base {
/**
* Constructor
*/
public function __construct() {
$this->setup_globals();
$this->setup_actions();
$this->setup_filters();
}
/**
* Set any global variables or class variables
*
* @since 1.4
*/
protected function setup_globals() {}
/**
* Setup actions for methods in this class
*
* @since 1.4
*/
protected function setup_actions() {
// Before a comment is posted.
add_action( 'pre_comment_on_post', array( $this, 'allow_logged_out_user_to_comment_as_external' ) );
// After a comment is posted.
add_action( 'comment_post', array( $this, 'set_comment_cookies' ) );
}
/**
* Setup filters for methods in this class
*
* @since 1.4
*/
protected function setup_filters() {
add_filter( 'comments_array', array( $this, 'comments_array' ) );
add_filter( 'preprocess_comment', array( $this, 'allow_logged_in_user_to_comment_as_guest' ), 0 );
}
/**
* Is this a Highlander POST request?
* Optionally restrict to one or more credentials slug (facebook, ...)
*
* @param mixed ...$args Comments credentials slugs.
* @return false|string false if it's not a Highlander POST request. The matching credentials slug if it is.
*/
public function is_highlander_comment_post( ...$args ) {
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verification should happen in Jetpack_Comments::pre_comment_on_post(). Internal ref for details: p1645643468937519/1645189749.180299-slack-C02HQGKMFJ8
if ( empty( $_POST['hc_post_as'] ) ) {
return false;
}
$hc_post_as = wp_unslash( $_POST['hc_post_as'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized here by comparing against known values.
// phpcs:enable WordPress.Security.NonceVerification.Missing
if ( $args ) {
foreach ( $args as $id_source ) {
if ( $id_source === $hc_post_as ) {
return $id_source;
}
}
return false;
}
return is_string( $hc_post_as ) && in_array( $hc_post_as, $this->id_sources, true ) ? $hc_post_as : false;
}
/**
* Signs an array of scalars with the self-hosted blog's Jetpack Token
*
* If parameter values are not scalars a WP_Error is returned, otherwise a keyed hash value is returned using the HMAC method.
*
* @param array $parameters Comment parameters.
* @param string $key Key used for generating the HMAC variant of the message digest.
* @return string HMAC
*/
public static function sign_remote_comment_parameters( $parameters, $key ) {
unset(
$parameters['sig'], // Don't sign the signature.
$parameters['replytocom'] // This parameter is unsigned - it changes dynamically as the comment form moves from parent comment to parent comment.
);
ksort( $parameters );
$signing = array();
foreach ( $parameters as $k => $v ) {
if ( ! is_scalar( $v ) ) {
return new WP_Error( 'invalid_input', __( 'Invalid request', 'jetpack' ), array( 'status' => 400 ) );
}
$signing[] = "{$k}={$v}";
}
return hash_hmac( 'sha1', implode( ':', $signing ), $key );
}
/**
* Adds comment author email and whether the comment is approved to the comments array
*
* After commenting as a guest while logged in, the user needs to see both:
* ( user_id = blah AND comment_approved = 0 )
* and ( comment_author_email = blah AND comment_approved = 0 )
* Core only does the first since the user is logged in, so this adds the second to the comments array.
*
* @param array $comments All comment data.
* @return array A modified array of comment data.
*/
public function comments_array( $comments ) {
global $wpdb, $post;
$commenter = $this->get_current_commenter();
if ( ! $commenter['user_id'] ) {
return $comments;
}
if ( ! $commenter['comment_author'] ) {
return $comments;
}
$in_moderation_comments = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM `$wpdb->comments` WHERE `comment_post_ID` = %d AND `user_id` = 0 AND `comment_author` = %s AND `comment_author_email` = %s AND `comment_approved` = '0' ORDER BY `comment_date_gmt` /* Highlander_Comments_Base::comments_array() */",
$post->ID,
wp_specialchars_decode( $commenter['comment_author'], ENT_QUOTES ),
$commenter['comment_author_email']
)
);
if ( ! $in_moderation_comments ) {
return $comments;
}
// @todo ZOMG this is a bad idea
$comments = array_merge( $comments, $in_moderation_comments );
usort( $comments, array( $this, 'sort_comments_by_comment_date_gmt' ) );
return $comments;
}
/**
* Comment sort comparator: comment_date_gmt
*
* @since 1.4
* @param object $a The first comment to compare dates with.
* @param object $b The second comment to compare dates with.
* @return int
*/
public function sort_comments_by_comment_date_gmt( $a, $b ) {
return $a->comment_date_gmt <=> $b->comment_date_gmt;
}
/**
* Get the current commenter's information from their cookie
*
* @since 1.4
* @return array Commenters information from cookie
*/
protected function get_current_commenter() {
// Defaults.
$user_id = 0;
$comment_author = '';
$comment_author_email = '';
$comment_author_url = '';
if ( isset( $_COOKIE[ 'comment_author_' . COOKIEHASH ] ) ) {
$comment_author = sanitize_text_field( wp_unslash( $_COOKIE[ 'comment_author_' . COOKIEHASH ] ) );
}
if ( isset( $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] ) ) {
$comment_author_email = sanitize_email( wp_unslash( $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] ) );
}
if ( isset( $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] ) ) {
$comment_author_url = esc_url_raw( wp_unslash( $_COOKIE[ 'comment_author_url_' . COOKIEHASH ] ) );
}
if ( is_user_logged_in() ) {
$user = wp_get_current_user();
$user_id = $user->ID;
}
return compact( 'comment_author', 'comment_author_email', 'comment_author_url', 'user_id' );
}
/**
* Allows a logged out user to leave a comment as a facebook/wp.com credentialed user.
* Overrides WordPress' core comment_registration option to treat these commenters as "registered" (verified) users.
*
* @since 1.4
*/
public function allow_logged_out_user_to_comment_as_external() {
// phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText
if ( ! $this->is_highlander_comment_post( 'facebook', 'wordpress' ) ) {
return;
}
add_filter( 'pre_option_comment_registration', '__return_zero' );
add_filter( 'pre_option_require_name_email', '__return_zero' );
}
/**
* Allow a logged in user to post as a guest, or FB credentialed request.
* Bypasses WordPress' core overrides that force a logged in user to comment as that user.
* Respects comment_registration option.
*
* @since 1.4
* @param array $comment_data All data for a specific comment.
* @return array Modified comment data, or an error if the required fields or a valid email address are not entered.
*/
public function allow_logged_in_user_to_comment_as_guest( $comment_data ) {
// Bail if user registration is allowed.
if ( get_option( 'comment_registration' ) ) {
return $comment_data;
}
// Bail if user is not logged in or not a post request.
if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) || ! is_user_logged_in() ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- simple comparison
return $comment_data;
}
// Bail if this is not a guest or external service credentialed request.
if ( ! $this->is_highlander_comment_post( 'guest', 'facebook' ) ) {
return $comment_data;
}
$user = wp_get_current_user();
foreach ( array(
'comment_author' => 'display_name',
'comment_author_email' => 'user_email',
'comment_author_url' => 'user_url',
) as $comment_field => $user_field ) {
if ( addslashes( $user->$user_field ) !== $comment_data[ $comment_field ] ) {
return $comment_data; // some other plugin already did something funky.
}
}
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verification should happen in Jetpack_Comments::pre_comment_on_post()
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitization too
if ( get_option( 'require_name_email' ) ) {
if ( isset( $_POST['email'] ) && 6 > strlen( wp_unslash( $_POST['email'] ) ) || empty( $_POST['author'] ) ) {
wp_die( esc_html__( 'Error: please fill the required fields (name, email).', 'jetpack' ), 400 );
} elseif ( ! isset( $_POST['email'] ) || ! is_email( wp_unslash( $_POST['email'] ) ) ) {
wp_die( esc_html__( 'Error: please enter a valid email address.', 'jetpack' ), 400 );
}
}
$author_change = false;
foreach ( array(
'comment_author' => 'author',
'comment_author_email' => 'email',
'comment_author_url' => 'url',
) as $comment_field => $post_field ) {
if ( ( ! isset( $_POST[ $post_field ] ) || $comment_data[ $comment_field ] !== $_POST[ $post_field ] ) && 'url' !== $post_field ) {
$author_change = true;
}
$comment_data[ $comment_field ] = isset( $_POST[ $post_field ] ) ? wp_unslash( $_POST[ $post_field ] ) : null;
}
// phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
// Mark as guest comment if name or email were changed.
if ( $author_change ) {
$comment_data['user_ID'] = 0;
$comment_data['user_id'] = $comment_data['user_ID'];
}
return $comment_data;
}
/**
* Set the comment cookies or bail if comment is invalid
*
* @since 1.4
* @param int $comment_id The comment ID.
*/
public function set_comment_cookies( $comment_id ) {
// Get comment and bail if it's invalid somehow.
$comment = get_comment( $comment_id );
if ( empty( $comment ) || is_wp_error( $comment ) ) {
return;
}
$id_source = $this->is_highlander_comment_post();
if ( empty( $id_source ) ) {
return;
}
// Set comment author cookies.
// We don't set the cookies if they are logged in with WordPress.com because they already have a cookie set.
// phpcs:ignore WordPress.WP.CapitalPDangit
if ( 'wordpress' !== $id_source ) {
// phpcs:disable WordPress.Security.NonceVerification -- Nonce verification should happen in Jetpack_Comments::pre_comment_on_post().
$is_consenting_to_cookies = ( isset( $_POST['wp-comment-cookies-consent'] ) );
$cookie_options = array(
'expires' => time() + apply_filters( 'comment_cookie_lifetime', YEAR_IN_SECONDS ),
'path' => COOKIEPATH,
'domain' => COOKIE_DOMAIN,
'secure' => is_ssl(),
'httponly' => true,
);
// If there is no consent, remove any cookies that may have been set.
if ( ( 'guest' === $id_source ) && ! $is_consenting_to_cookies ) {
$cookie_options['expires'] = time() - YEAR_IN_SECONDS;
}
// Set samesite to None if the request is from Jetpack iframe.
// This is needed because it is considered third party.
if ( isset( $_REQUEST['for'] ) && 'jetpack' === $_REQUEST['for'] ) {
$cookie_options['samesite'] = 'None';
}
// phpcs:enable WordPress.Security.NonceVerification
// phpcs:disable Jetpack.Functions.SetCookie.MissingTrueHTTPOnly
isset( $comment->comment_author ) ? setcookie( 'comment_author_' . COOKIEHASH, $comment->comment_author, $cookie_options ) : null;
isset( $comment->comment_author_email ) ? setcookie( 'comment_author_email_' . COOKIEHASH, $comment->comment_author_email, $cookie_options ) : null;
isset( $comment->comment_author_url ) ? setcookie( 'comment_author_url_' . COOKIEHASH, esc_url( $comment->comment_author_url ), $cookie_options ) : null;
// phpcs:enable Jetpack.Functions.SetCookie.MissingTrueHTTPOnly
}
}
/**
* Get an avatar from Photon
*
* @since 1.4
* @param string $url The avatar URL.
* @param int $size The avatar size.
* @return string
*/
protected function photon_avatar( $url, $size ) {
$size = (int) $size;
return Image_CDN_Core::cdn_url( $url, array( 'resize' => "$size,$size" ) );
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,209 @@
<?php
/**
* Adds support for Jetpack Subscription Modal On Comment feature
* Limited to Atomic sites.
*
* @package automattic/jetpack-mu-wpcom
* @since 12.4
*/
use Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service\Abstract_Token_Subscription_Service;
use Automattic\Jetpack\Status\Host;
use const Automattic\Jetpack\Extensions\Subscriptions\META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS;
/**
* Jetpack_Subscription_Modal_On_Comment class.
*/
class Jetpack_Subscription_Modal_On_Comment {
/**
* Jetpack_Subscription_Modal_On_Comment singleton instance.
*
* @var Jetpack_Subscription_Modal_On_Comment|null
*/
private static $instance;
/**
* Jetpack_Subscription_Modal_On_Comment instance init.
*/
public static function init() {
if ( self::$instance === null ) {
self::$instance = new Jetpack_Subscription_Modal_On_Comment();
}
return self::$instance;
}
const BLOCK_TEMPLATE_PART_SLUG = 'jetpack-subscription-modal';
/**
* Returns the block template part ID.
*
* @return string
*/
public static function get_block_template_part_id() {
return get_stylesheet() . '//' . self::BLOCK_TEMPLATE_PART_SLUG;
}
/**
* Jetpack_Subscription_Modal_On_Comment class constructor.
* Limited to Atomic sites.
*/
public function __construct() {
if ( ( new Host() )->is_woa_site() && get_option( 'jetpack_verbum_subscription_modal', true ) ) {
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_action( 'wp_footer', array( $this, 'add_subscription_modal_to_frontend' ) );
add_filter( 'get_block_template', array( $this, 'get_block_template_filter' ), 10, 3 );
}
}
/**
* Enqueues JS to load modal.
*
* @return void
*/
public function enqueue_assets() {
if ( ! $this->should_user_see_modal() ) {
return;
}
wp_enqueue_style( 'subscription-modal-css', plugins_url( 'subscription-modal.css', __FILE__ ), array(), JETPACK__VERSION );
wp_enqueue_script( 'subscription-modal-js', plugins_url( 'subscription-modal.js', __FILE__ ), array( 'wp-dom-ready' ), JETPACK__VERSION, true );
wp_localize_script(
'subscription-modal-js',
'subscriptionData',
array(
'homeUrl' => wp_parse_url( home_url(), PHP_URL_HOST ),
)
);
}
/**
* Adds modal with Subscribe Modal content.
*
* @return void
*/
public function add_subscription_modal_to_frontend() {
if ( $this->should_user_see_modal() ) { ?>
<div class="jetpack-subscription-modal">
<div class="jetpack-subscription-modal__modal-content">
<?php block_template_part( self::BLOCK_TEMPLATE_PART_SLUG ); ?>
</div>
</div>
<?php
}
}
/**
* Makes get_block_template return the WP_Block_Template for the Subscribe Modal.
*
* @param WP_Block_Template $block_template The block template to be returned.
* @param string $id Template unique identifier (example: theme_slug//template_slug).
* @param string $template_type Template type: `'wp_template'` or '`wp_template_part'`.
*
* @return WP_Block_Template
*/
public function get_block_template_filter( $block_template, $id, $template_type ) {
if ( empty( $block_template ) && $template_type === 'wp_template_part' ) {
if ( $id === self::get_block_template_part_id() ) {
return $this->get_template();
}
}
return $block_template;
}
/**
* Returns a custom template for the Subscribe Modal.
*
* @return WP_Block_Template
*/
public function get_template() {
$template = new WP_Block_Template();
$template->theme = get_stylesheet();
$template->slug = self::BLOCK_TEMPLATE_PART_SLUG;
$template->id = self::get_block_template_part_id();
$template->area = 'uncategorized';
$template->content = $this->get_subscribe_template_content();
$template->source = 'plugin';
$template->type = 'wp_template_part';
$template->title = __( 'Jetpack Subscription modal', 'jetpack' );
$template->status = 'publish';
$template->has_theme_file = false;
$template->is_custom = true;
$template->description = __( 'A subscribe form that submit a comment.', 'jetpack' );
return $template;
}
/**
* Returns the initial content of the Subscribe Modal template.
* This can then be edited by the user.
*
* @return string
*/
public function get_subscribe_template_content() {
// translators: %s is the name of the site.
$discover_more_from = sprintf( __( 'Discover more from %s', 'jetpack' ), get_bloginfo( 'name' ) );
$subscribe_text = __( 'Subscribe now to keep reading and get access to the full archive.', 'jetpack' );
$continue_reading = __( 'Continue reading', 'jetpack' );
return <<<HTML
<!-- wp:group {"style":{"spacing":{"top":"32px","bottom":"32px","left":"32px","right":"32px"},"margin":{"top":"0","bottom":"0"}},"border":{"color":"#dddddd","width":"1px"}},"layout":{"type":"constrained","contentSize":"450px"}} -->
<div class="wp-block-group has-border-color jetpack-subscription-modal__modal-content-form" style="border-color:#dddddd;border-width:1px;margin-top:0;margin-bottom:0;padding:32px">
<!-- wp:heading {"textAlign":"center","style":{"typography":{"fontStyle":"normal","fontWeight":"600","fontSize":"26px"},"layout":{"selfStretch":"fit","flexSize":null},"spacing":{"margin":{"top":"4px","bottom":"10px"}}}} -->
<h2 class="wp-block-heading has-text-align-center" style="margin-top:4px;margin-bottom:10px;font-size:26px;font-style:normal;font-weight:600">$discover_more_from</h2>
<!-- /wp:heading -->
<!-- wp:paragraph {"align":"center","style":{"typography":{"fontSize":"15px"},"spacing":{"margin":{"top":"4px","bottom":"0px"}}}} -->
<p class='has-text-align-center' style='margin-top:4px;margin-bottom:0px;font-size:15px'>$subscribe_text</p>
<!-- /wp:paragraph -->
<!-- wp:jetpack/subscriptions {"borderRadius":50,"className":"is-style-compact","appSource":"atomic-subscription-modal-lo"} /-->
<!-- wp:paragraph {"align":"center","style":{"spacing":{"margin":{"top":"20px"}},"typography":{"fontSize":"14px"}},"className":"jetpack-subscription-modal__close"} -->
<p class="has-text-align-center jetpack-subscription-modal__close" style="margin-top:20px;font-size:14px"><a href="#">$continue_reading</a></p>
<!-- /wp:paragraph -->
</div>
<!-- /wp:group -->
HTML;
}
/**
* Returns true if a site visitor should see
* the Subscribe Modal.
*
* @return bool
*/
public function should_user_see_modal() {
// Only show when viewing frontend single post.
if ( is_admin() || ! is_singular( 'post' ) ) {
return false;
}
// Don't show if post is for subscribers only or has paywall block
global $post;
if ( defined( 'Automattic\\Jetpack\\Extensions\\Subscriptions\\META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS' ) ) {
$access_level = get_post_meta( $post->ID, META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS, true );
} else {
$access_level = get_post_meta( $post->ID, '_jetpack_newsletter_access', true );
}
require_once JETPACK__PLUGIN_DIR . 'extensions/blocks/premium-content/_inc/subscription-service/include.php';
$is_accessible_by_everyone = Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_EVERYBODY === $access_level || empty( $access_level );
if ( ! $is_accessible_by_everyone ) {
return false;
}
return true;
}
}
Jetpack_Subscription_Modal_On_Comment::init();
add_action(
'rest_api_switched_to_blog',
function () {
Jetpack_Subscription_Modal_On_Comment::init();
}
);
@@ -0,0 +1,68 @@
.jetpack-subscription-modal {
visibility: hidden;
position: fixed;
z-index: 50000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
opacity: 0;
transition: visibility 0s, opacity 0.5s ease;
display: flex;
justify-content: space-evenly;
align-content: center;
flex-wrap: wrap;
}
.jetpack-subscription-modal.open {
opacity: 1;
background-color: rgba(0,0,0,0.3);
visibility: visible;
}
.jetpack-subscription-modal__modal-content {
text-align: center;
background-color: #fefefe;
width: 100%;
max-width: 650px;
box-sizing: border-box;
transition: visibility 0s, opacity 0.3s linear;
opacity: 0;
display: flex;
flex-direction: column;
gap: 5px;
justify-content: center;
}
.jetpack-subscription-modal.open .jetpack-subscription-modal__modal-content {
opacity: 1;
top: 0;
visibility: visible;
}
/* Hide the modal content when iframe is present */
.jetpack-subscription-modal.has-iframe .jetpack-subscription-modal__modal-content {
background: transparent;
}
.jetpack-subscription-modal.has-iframe .jetpack-subscription-modal__modal-content-form {
visibility: hidden;
opacity: 0;
}
/*
* These text-wrap properties still have limited browser
* support, but based on feedback still adding them for when
* they are supported.
*/
.jetpack-subscription-modal__modal-content p {
text-wrap: pretty;
text-wrap: pretty;
}
@media screen and (max-width: 640px) {
.jetpack-subscription-modal__modal-content {
width: 94%;
}
}
@@ -0,0 +1,146 @@
/* global subscriptionData */
document.addEventListener( 'DOMContentLoaded', function () {
const modal = document.getElementsByClassName( 'jetpack-subscription-modal' )[ 0 ];
if ( ! modal ) {
return;
}
const close = document.getElementsByClassName( 'jetpack-subscription-modal__close' )[ 0 ];
let redirectUrl = '';
let hasLoaded = false;
function reloadOnCloseSubscriptionModal( customUrl ) {
const destinationUrl = customUrl ? new URL( customUrl ) : new URL( redirectUrl );
// Prevent redirect to external sites.
if ( destinationUrl.hostname !== window.location.hostname ) {
return;
}
try {
localStorage.setItem(
'jetpack-subscription-modal-on-comment-scroll-to',
destinationUrl.hash
);
} catch {
// Ok if we can't set it.
}
// Add cache-busting parameter
destinationUrl.searchParams.set( '_ctn', Date.now() );
window.location.href = destinationUrl.toString();
}
function JetpackSubscriptionModalOnCommentMessageListener( event ) {
let message = event && event.data;
if ( typeof message === 'string' ) {
try {
message = JSON.parse( message );
} catch {
return;
}
}
const type = message && message.type;
const data = message && message.data;
if ( type !== 'subscriptionModalShow' || typeof data.url === 'undefined' ) {
return;
}
if ( subscriptionData.homeUrl !== event.origin ) {
return;
}
if ( data.email ) {
const emailInput = document.querySelector(
'.jetpack-subscription-modal__modal-content input[type=email]'
);
if ( ! emailInput ) {
reloadOnCloseSubscriptionModal( data.url );
return;
}
const appSource = document.querySelector(
'.jetpack-subscription-modal__modal-content input[name=app_source]'
);
if ( ! appSource ) {
reloadOnCloseSubscriptionModal( data.url );
return;
}
emailInput.value = data.email;
if ( data.is_logged_in ) {
emailInput.setAttribute( 'readonly', 'readonly' );
appSource.value = 'atomic-subscription-modal-li';
}
}
if ( ! hasLoaded ) {
try {
const storedCount = parseInt(
sessionStorage.getItem( 'jetpack-subscription-modal-shown-count' )
);
const showCount = ( isNaN( storedCount ) ? 0 : storedCount ) + 1;
sessionStorage.setItem( 'jetpack-subscription-modal-shown-count', showCount );
if ( showCount > 5 ) {
new Image().src =
document.location.protocol +
'//pixel.wp.com/g.gif?v=wpcom-no-pv&x_jetpack-subscribe-modal-comm=hidden_views_limit&r=' +
Math.random();
reloadOnCloseSubscriptionModal( data.url );
return;
}
} catch {
// Ignore any errors.
}
new Image().src =
document.location.protocol +
'//pixel.wp.com/g.gif?v=wpcom-no-pv&x_jetpack-subscribe-modal-comm=showed&r=' +
Math.random();
modal.classList.toggle( 'open' );
hasLoaded = true;
redirectUrl = data.url;
}
}
window.addEventListener( 'message', JetpackSubscriptionModalOnCommentMessageListener );
if ( close ) {
close.onclick = function ( event ) {
event.preventDefault();
modal.classList.toggle( 'open' );
reloadOnCloseSubscriptionModal();
};
}
window.onclick = function ( event ) {
if ( event.target === modal ) {
modal.style.display = 'none';
reloadOnCloseSubscriptionModal();
}
};
window.addEventListener( 'load', () => {
// Scroll to the last comment.
const subscriptionScroll = localStorage.getItem(
'jetpack-subscription-modal-on-comment-scroll-to'
);
if ( subscriptionScroll ) {
window.location.hash = subscriptionScroll;
localStorage.removeItem( 'jetpack-subscription-modal-on-comment-scroll-to' );
const comment = document.querySelector( subscriptionScroll );
if ( comment ) {
comment.scrollIntoView( { block: 'center', behavior: 'smooth' } );
}
}
} );
} );
@@ -0,0 +1,30 @@
<?php
/**
* Contact form module.
*
* @package automattic/jetpack
*/
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
use Automattic\Jetpack\Forms\Jetpack_Forms;
/**
* Module Name: Forms
* Module Description: Add a customizable form to any post or page using the Jetpack Form block.
* Sort Order: 15
* Recommendation Order: 14
* First Introduced: 1.3
* Requires Connection: No
* Auto Activate: Yes
* Module Tags: Other
* Feature: Writing
* Additional Search Queries: contact, form, grunion, feedback, submission, contact form, email, feedback, contact form plugin, custom form, custom form plugin, form builder, forms, form maker, survey, contact by jetpack, contact us, forms free, creator
*/
/**
* Load the newer Jetpack Forms package.
*/
Jetpack_Forms::load_contact_form();
@@ -0,0 +1,383 @@
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Module Name: Copy Post
* Module Description: Enable the option to copy entire posts and pages, including tags and settings
* Sort Order: 15
* First Introduced: 7.0
* Requires Connection: No
* Auto Activate: No
* Module Tags: Writing
* Feature: Writing
* Additional Search Queries: copy, duplicate
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Assets\Logo;
// phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files.
/**
* Copy Post class.
*/
class Jetpack_Copy_Post {
/**
* Jetpack_Copy_Post_By_Param constructor.
* Add row actions to post/page/CPT listing screens.
* Process any `?copy` param if on a create new post/page/CPT screen.
*
* @return void
*/
public function __construct() {
if ( 'edit.php' === $GLOBALS['pagenow'] || ( 'admin-ajax.php' === $GLOBALS['pagenow'] && ! empty( $_POST['post_view'] ) && 'list' === $_POST['post_view'] && ! empty( $_POST['action'] ) && 'inline-save' === $_POST['action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- update_post_data() handles access check.
add_action( 'admin_head', array( $this, 'print_inline_styles' ) );
add_filter( 'post_row_actions', array( $this, 'add_row_action' ), 10, 2 );
add_filter( 'page_row_actions', array( $this, 'add_row_action' ), 10, 2 );
return;
}
if ( ! empty( $_GET['jetpack-copy'] ) && 'post-new.php' === $GLOBALS['pagenow'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- update_post_data() handles access check.
add_action( 'wp_insert_post', array( $this, 'update_post_data' ), 10, 3 );
add_filter( 'pre_option_default_post_format', '__return_empty_string' );
}
}
/**
* Echos inline styles for the Jetpack logo.
*
* @return void
*/
public function print_inline_styles() {
echo '
<style id="jetpack-copy-post-styles">
#jetpack-logo__icon {
height: 14px;
width: 14px;
vertical-align: text-bottom;
}
#jetpack-logo__icon path {
fill: inherit;
}
</style>
';
}
/**
* Update the new (target) post data with the source post data.
*
* @param int $target_post_id Target post ID.
* @param WP_Post $post Target post object (not used).
* @param bool $update Whether this is an existing post being updated or not.
* @return void
*/
public function update_post_data( $target_post_id, $post, $update ) {
// This `$update` check avoids infinite loops of trying to update our updated post.
if ( $update ) {
return;
}
// Shouldn't happen, since this filter is only added when the value isn't empty, but check anyway.
if ( empty( $_GET['jetpack-copy'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return;
}
$source_post = get_post( intval( $_GET['jetpack-copy'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! $source_post instanceof WP_Post ||
! $this->user_can_access_post( $source_post->ID ) ||
! $this->validate_post_type( $source_post ) ) {
return;
}
$update_results = array(
'update_content' => $this->update_content( $source_post, $target_post_id ),
'update_featured_image' => $this->update_featured_image( $source_post, $target_post_id ),
'update_post_format' => $this->update_post_format( $source_post, $target_post_id ),
'update_likes_sharing' => $this->update_likes_sharing( $source_post, $target_post_id ),
'update_post_type_terms' => $this->update_post_type_terms( $source_post, $target_post_id ),
);
// Required to satisfy get_default_post_to_edit(), which has these filters after post creation.
add_filter( 'default_title', array( $this, 'filter_title' ), 10, 2 );
add_filter( 'default_content', array( $this, 'filter_content' ), 10, 2 );
add_filter( 'default_excerpt', array( $this, 'filter_excerpt' ), 10, 2 );
// Required to avoid the block editor from adding default blocks according to post format.
add_filter( 'block_editor_settings_all', array( $this, 'remove_post_format_template' ) );
/**
* Fires after all updates have been performed, and default content filters have been added.
* Allows for any cleanup or post operations, and default content filters can be removed or modified.
*
* @module copy-post
*
* @since 7.0.0
*
* @param WP_Post $source_post Post object that was copied.
* @param int $target_post_id Target post ID.
* @param array $update_results Results of all update operations, allowing action to be taken.
*/
do_action( 'jetpack_copy_post', $source_post, $target_post_id, $update_results );
}
/**
* Determine if the current user has edit access to the source post.
*
* @param int $post_id Source post ID (the post being copied).
* @return bool True if user has the meta cap of `edit_post` for the given post ID, false otherwise.
*/
protected function user_can_access_post( $post_id ) {
return current_user_can( 'edit_post', $post_id );
}
/**
* Update the target post's title, content, excerpt, categories, and tags.
*
* @param WP_Post $source_post Post object to be copied.
* @param int $target_post_id Target post ID.
* @return int 0 on failure, or the updated post ID on success.
*/
protected function update_content( $source_post, $target_post_id ) {
$data = array(
'ID' => $target_post_id,
'post_title' => $source_post->post_title,
'post_content' => $source_post->post_content,
'post_excerpt' => $source_post->post_excerpt,
'comment_status' => $source_post->comment_status,
'ping_status' => $source_post->ping_status,
'post_category' => wp_get_post_categories( $source_post->ID ),
'post_password' => $source_post->post_password,
'tags_input' => $source_post->tags_input,
);
/**
* Fires just before the target post is updated with its new data.
* Allows for final data adjustments before updating the target post.
*
* @module copy-post
*
* @since 7.0.0
*
* @param array $data Post data with which to update the target (new) post.
* @param WP_Post $source_post Post object being copied.
* @param int $target_post_id Target post ID.
*/
$data = apply_filters( 'jetpack_copy_post_data', $data, $source_post, $target_post_id );
return wp_update_post( $data );
}
/**
* Update terms for post types.
*
* @param WP_Post $source_post Post object to be copied.
* @param int $target_post_id Target post ID.
* @return array Results of attempts to set each term to the target (new) post.
*/
protected function update_post_type_terms( $source_post, $target_post_id ) {
$results = array();
$bypassed_post_types = apply_filters( 'jetpack_copy_post_bypassed_post_types', array( 'post', 'page' ), $source_post, $target_post_id );
if ( in_array( $source_post->post_type, $bypassed_post_types, true ) ) {
return $results;
}
$taxonomies = get_object_taxonomies( $source_post, 'objects' );
foreach ( $taxonomies as $taxonomy ) {
$terms = wp_get_post_terms( $source_post->ID, $taxonomy->name, array( 'fields' => 'ids' ) );
$results[] = wp_set_post_terms( $target_post_id, $terms, $taxonomy->name );
}
return $results;
}
/**
* Update the target post's featured image.
*
* @param WP_Post $source_post Post object to be copied.
* @param int $target_post_id Target post ID.
* @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
*/
protected function update_featured_image( $source_post, $target_post_id ) {
$featured_image_id = get_post_thumbnail_id( $source_post );
return update_post_meta( $target_post_id, '_thumbnail_id', $featured_image_id );
}
/**
* Update the target post's post format.
*
* @param WP_Post $source_post Post object to be copied.
* @param int $target_post_id Target post ID.
* @return array|WP_Error|false WP_Error on error, array of affected term IDs on success.
*/
protected function update_post_format( $source_post, $target_post_id ) {
$post_format = get_post_format( $source_post );
return set_post_format( $target_post_id, $post_format );
}
/**
* Ensure the block editor doesn't modify the source post content for non-standard post formats.
*
* @param array $settings Settings to be passed into the block editor.
* @return array Settings with any `template` key removed.
*/
public function remove_post_format_template( $settings ) {
unset( $settings['template'] );
return $settings;
}
/**
* Update the target post's Likes and Sharing statuses.
*
* @param WP_Post $source_post Post object to be copied.
* @param int $target_post_id Target post ID.
* @return array Array with the results of each update action.
*/
protected function update_likes_sharing( $source_post, $target_post_id ) {
$likes = get_post_meta( $source_post->ID, 'switch_like_status', true );
$sharing = get_post_meta( $source_post->ID, 'sharing_disabled', true );
if ( '' !== $likes ) {
$likes_result = update_post_meta( $target_post_id, 'switch_like_status', $likes );
} else {
$likes_result = null;
}
if ( '' !== $sharing ) {
$sharing_result = update_post_meta( $target_post_id, 'sharing_disabled', $sharing );
} else {
$sharing_result = null;
}
return array(
'likes' => $likes_result,
'sharing' => $sharing_result,
);
}
/**
* Update the target post's title.
*
* @param string $post_title Post title determined by `get_default_post_to_edit()`.
* @param WP_Post $post Post object of newly-inserted post.
* @return string Updated post title from source post.
*/
public function filter_title( $post_title, $post ) {
return $post->post_title;
}
/**
* Update the target post's content (`post_content`).
*
* @param string $post_content Post content determined by `get_default_post_to_edit()`.
* @param WP_Post $post Post object of newly-inserted post.
* @return string Updated post content from source post.
*/
public function filter_content( $post_content, $post ) {
return $post->post_content;
}
/**
* Update the target post's excerpt.
*
* @param string $post_excerpt Post excerpt determined by `get_default_post_to_edit()`.
* @param WP_Post $post Post object of newly-inserted post.
* @return string Updated post excerpt from source post.
*/
public function filter_excerpt( $post_excerpt, $post ) {
return $post->post_excerpt;
}
/**
* Validate the post type to be used for the target post.
*
* @param WP_Post $post Post object of current post in listing.
* @return bool True if the post type is in a list of supported psot types; false otherwise.
*/
protected function validate_post_type( $post ) {
/**
* Fires when determining if the "Copy" row action should be made available.
* Allows overriding supported post types.
*
* @module copy-post
*
* @since 7.0.0
*
* @param array Post types supported by default.
* @param WP_Post $post Post object of current post in listing.
*/
$valid_post_types = apply_filters(
'jetpack_copy_post_post_types',
array(
'post',
'page',
'jetpack-testimonial',
'jetpack-portfolio',
),
$post
);
return in_array( $post->post_type, $valid_post_types, true );
}
/**
* Add a "Copy" row action to supported posts/pages/CPTs on list views.
*
* @param array $actions Existing actions.
* @param WP_Post $post Post object of current post in list.
* @return array Array of updated row actions.
*/
public function add_row_action( $actions, $post ) {
if ( ! $this->user_can_access_post( $post->ID ) ||
! $post instanceof WP_Post ||
! $this->validate_post_type( $post ) ) {
return $actions;
}
$edit_url = add_query_arg(
array(
'post_type' => $post->post_type,
'jetpack-copy' => $post->ID,
),
admin_url( 'post-new.php' )
);
$jetpack_logo = new Logo();
$edit_action = array(
'jetpack-copy' => sprintf(
'<a href="%1$s" aria-label="%2$s">%3$s %4$s</a>',
esc_url( $edit_url ),
esc_attr__( 'Copy this post with Jetpack', 'jetpack' ),
esc_html__( 'Copy', 'jetpack' ),
$jetpack_logo->get_jp_emblem()
),
);
// Insert the Copy action before the Trash action.
$edit_offset = array_search( 'trash', array_keys( $actions ), true );
$updated_actions = array_merge(
array_slice( $actions, 0, $edit_offset ),
$edit_action,
array_slice( $actions, $edit_offset )
);
/**
* Fires after the new Copy action has been added to the row actions.
* Allows changes to the action presentation, or other final checks.
*
* @module copy-post
*
* @since 7.0.0
*
* @param array $updated_actions Updated row actions with the Copy Post action.
* @param array $actions Original row actions passed to this filter.
* @param WP_Post $post Post object of current post in listing.
*/
return apply_filters( 'jetpack_copy_post_row_actions', $updated_actions, $actions, $post );
}
}
/**
* Instantiate an instance of Jetpack_Copy_Post on the `admin_init` hook.
*/
function jetpack_copy_post_init() {
new Jetpack_Copy_Post();
}
add_action( 'admin_init', 'jetpack_copy_post_init' );
@@ -0,0 +1,122 @@
<?php
/**
* Module Name: Custom content types
* Module Description: Display different types of content on your site with custom content types.
* First Introduced: 3.1
* Requires Connection: No
* Auto Activate: No
* Module Tags: Writing
* Sort Order: 34
* Feature: Writing
* Additional Search Queries: cpt, custom post types, portfolio, portfolios, testimonial, testimonials
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Redirect;
if ( ! function_exists( 'jetpack_load_custom_post_types' ) ) {
if ( class_exists( 'Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Portfolio' ) ) {
// Temporarily require the file here to prevent possible deprecation notices for a short period of time.
// @todo: Can remove this if check after the subsequent release.
include __DIR__ . '/custom-post-types/portfolios.php';
} else {
/**
* Load Portfolio CPT.
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*/
function jetpack_load_custom_post_types() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
include __DIR__ . '/custom-post-types/portfolios.php';
}
}
}
if ( ! function_exists( 'jetpack_custom_post_types_loaded' ) ) {
if ( class_exists( 'Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Portfolio' ) ) {
// Temporarily require the file here to prevent possible deprecation notices for a short period of time.
// @todo: Can remove this if check after the subsequent release.
add_action(
'jetpack_modules_loaded',
function () {
Jetpack::enable_module_configurable( __FILE__ );
}
);
} else {
/**
* Make module configurable.
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*/
function jetpack_custom_post_types_loaded() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
Jetpack::enable_module_configurable( __FILE__ );
}
add_action( 'jetpack_modules_loaded', 'jetpack_custom_post_types_loaded' );
}
}
if ( ! function_exists( 'jetpack_cpt_settings_api_init' ) ) {
if ( class_exists( 'Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Portfolio' ) ) {
// Temporarily require the file here to prevent possible deprecation notices for a short period of time.
// @todo: Can remove this if check after the subsequent release.
add_action(
'admin_init',
function () {
add_settings_section(
'jetpack_cpt_section',
'<span id="cpt-options">' . __( 'Your Custom Content Types', 'jetpack' ) . '</span>',
function () {
?>
<p>
<?php esc_html_e( 'Use these settings to display different types of content on your site.', 'jetpack' ); ?>
<a target="_blank" rel="noopener noreferrer" href="<?php echo esc_url( Redirect::get_url( 'jetpack-support-custom-content-types' ) ); ?>"><?php esc_html_e( 'Learn More', 'jetpack' ); ?></a>
</p>
<?php
},
'writing'
);
}
);
} else {
/**
* Add Settings Section for CPT
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*/
function jetpack_cpt_settings_api_init() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
add_settings_section(
'jetpack_cpt_section',
'<span id="cpt-options">' . __( 'Your Custom Content Types', 'jetpack' ) . '</span>',
'jetpack_cpt_section_callback',
'writing'
);
}
add_action( 'admin_init', 'jetpack_cpt_settings_api_init' );
}
}
if ( ! function_exists( 'jetpack_cpt_section_callback' ) ) {
/**
* Settings Description
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*/
function jetpack_cpt_section_callback() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
?>
<p>
<?php esc_html_e( 'Use these settings to display different types of content on your site.', 'jetpack' ); ?>
<a target="_blank" rel="noopener noreferrer" href="<?php echo esc_url( Redirect::get_url( 'jetpack-support-custom-content-types' ) ); ?>"><?php esc_html_e( 'Learn More', 'jetpack' ); ?></a>
</p>
<?php
}
}
if ( function_exists( 'jetpack_load_custom_post_types' ) ) {
jetpack_load_custom_post_types();
}
@@ -0,0 +1,24 @@
.widefat .menu-label-row td {
border-bottom-width: 1px;
}
.widefat .menu-label-row td h3 {
padding-left: 30px;
}
.widefat .menu-order-value {
width: 2.5em;
text-align: center;
}
.widefat .menu-label-row, .widefat .menu-label-row td {
background-color: #d6d6d6;
color: #111;
border: 0 none;
}
.ui-sortable .type-nova_menu_item {
cursor: move;
}
.tablenav .button-reorder {
margin-top: 4px;
}
.tablenav .view-switch a, .tablenav div.tablenav-pages {
display: none;
}
@@ -0,0 +1,14 @@
.many-items-table th, .many-items-table td {
width: 25%;
}
.many-items-table input, .many-items-table textarea {
width: 100%;
}
.many-items-table input[type=file] {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
@@ -0,0 +1,30 @@
@font-face {
font-family: 'nova-font';
src: url('../fonts/nova.eot');
}
@font-face {
font-family: 'nova-font';
src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg5lAuAAAAC8AAAAYGNtYXDL9xqaAAABHAAAADxnYXNwAAAAEAAAAVgAAAAIZ2x5Zrlfj0YAAAFgAAABrGhlYWQAW+atAAADDAAAADZoaGVhB2ED4AAAA0QAAAAkaG10eAXcAGQAAANoAAAADGxvY2EACgDWAAADdAAAAAhtYXhwAAgAkQAAA3wAAAAgbmFtZXvEneAAAAOcAAABHnBvc3QAAwAAAAAEvAAAACAAAwPoAZAABQAAAooCvAAAAIwCigK8AAAB4AAxAQIAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAACDmAwOp/8L/wgOpAD4AAAAAAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEACgAAAAGAAQAAQACACDmA///AAAAIOYD////4Rn/AAEAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAQAZAAyA7YDhAAoAEUAfQCOAAABMhY6ATMyPgI/ASMHJz8BDwEnNycHDgMHFgYWBhcHMgYWIjMXNwUHDgMHHgMXHgMzMj4CPwEnKgMjFyc3Ni4CJy4DIyIOAgcOAxceAxceAz8BAR4DMzI+Ajc+Ayc2LgIvAQEmPgI3PgEeARceAxcnApQCBAQEAg4XFxQKtzGXH2oaM3UhlwHUCg0KBAEBAQIBAhQBAQEBAXIN/t3oCwwLAwEBAwsMCwgVFhkMDhgXEwuxcgICAwEC94wFBQMUIRkRLS0xFQsVFxULCQ8GAgQDDxMaDxUuNDIaDgEwBhAQFQkMExIQCAcMBgUBAQUGDAfm/i8CAwIHAhApLy0VCRMMCwLrAfUBBAoNCtWWIHYyGWsglDS5CRQWGQ0CBAQEAw8BAWkOKsIJFBYZDQ0YFxQJCg0KBAQKDQrWngp+DxczNTUXEx4VDAMHDAoKGh4iExMnJSMPFB4SBQQD/l8HCgYEBAcLCAcQEhQKCxMSEAjPAV8BBgcHBA4KCRgTCxgWFQlaAAAAAAEAAAABAAAD2anvXw889QALA+gAAAAAzsPRIgAAAADOw9EiAAAAAAO2A4QAAAAIAAIAAAAAAAAAAQAAA6n/wgAAA+gAAAAyA7YAAQAAAAAAAAAAAAAAAAAAAAMAAAAAAfQAAAPoAGQAAAAAAAoA1gABAAAAAwCPAAQAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEACAAAAAEAAAAAAAIADgAyAAEAAAAAAAMACAAeAAEAAAAAAAQACABAAAEAAAAAAAUAFgAIAAEAAAAAAAYABAAmAAEAAAAAAAoAKABIAAMAAQQJAAEACAAAAAMAAQQJAAIADgAyAAMAAQQJAAMACAAeAAMAAQQJAAQACABAAAMAAQQJAAUAFgAIAAMAAQQJAAYACAAqAAMAAQQJAAoAKABIAG4AbwB2AGEAVgBlAHIAcwBpAG8AbgAgADAALgAwAG4AbwB2AGFub3ZhAG4AbwB2AGEAUgBlAGcAdQBsAGEAcgBuAG8AdgBhAEcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=) format('truetype'),
url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAUoAAsAAAAABNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgDmUC4GNtYXAAAAFoAAAAPAAAADzL9xqaZ2FzcAAAAaQAAAAIAAAACAAAABBnbHlmAAABrAAAAawAAAGsuV+PRmhlYWQAAANYAAAANgAAADYAW+ataGhlYQAAA5AAAAAkAAAAJAdhA+BobXR4AAADtAAAAAwAAAAMBdwAZGxvY2EAAAPAAAAACAAAAAgACgDWbWF4cAAAA8gAAAAgAAAAIAAIAJFuYW1lAAAD6AAAAR4AAAEee8Sd4HBvc3QAAAUIAAAAIAAAACAAAwAAAAMD6AGQAAUAAAKKArwAAACMAooCvAAAAeAAMQECAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg5gMDqf/C/8IDqQA+AAAAAAAAAAAAAAAAAAAAIAAAAAAAAgAAAAMAAAAUAAMAAQAAABQABAAoAAAABgAEAAEAAgAg5gP//wAAACDmA////+EZ/wABAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAAEAGQAMgO2A4QAKABFAH0AjgAAATIWOgEzMj4CPwEjByc/AQ8BJzcnBw4DBxYGFgYXBzIGFiIzFzcFBw4DBx4DFx4DMzI+Aj8BJyoDIxcnNzYuAicuAyMiDgIHDgMXHgMXHgM/AQEeAzMyPgI3PgMnNi4CLwEBJj4CNz4BHgEXHgMXJwKUAgQEBAIOFxcUCrcxlx9qGjN1IZcB1AoNCgQBAQECAQIUAQEBAQFyDf7d6AsMCwMBAQMLDAsIFRYZDA4YFxMLsXICAgMBAveMBQUDFCEZES0tMRULFRcVCwkPBgIEAw8TGg8VLjQyGg4BMAYQEBUJDBMSEAgHDAYFAQEFBgwH5v4vAgMCBwIQKS8tFQkTDAsC6wH1AQQKDQrVliB2MhlrIJQ0uQkUFhkNAgQEBAMPAQFpDirCCRQWGQ0NGBcUCQoNCgQECg0K1p4Kfg8XMzU1FxMeFQwDBwwKChoeIhMTJyUjDxQeEgUEA/5fBwoGBAQHCwgHEBIUCgsTEhAIzwFfAQYHBwQOCgkYEwsYFhUJWgAAAAABAAAAAQAAA9mp718PPPUACwPoAAAAAM7D0SIAAAAAzsPRIgAAAAADtgOEAAAACAACAAAAAAAAAAEAAAOp/8IAAAPoAAAAMgO2AAEAAAAAAAAAAAAAAAAAAAADAAAAAAH0AAAD6ABkAAAAAAAKANYAAQAAAAMAjwAEAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAAgAAAABAAAAAAACAA4AMgABAAAAAAADAAgAHgABAAAAAAAEAAgAQAABAAAAAAAFABYACAABAAAAAAAGAAQAJgABAAAAAAAKACgASAADAAEECQABAAgAAAADAAEECQACAA4AMgADAAEECQADAAgAHgADAAEECQAEAAgAQAADAAEECQAFABYACAADAAEECQAGAAgAKgADAAEECQAKACgASABuAG8AdgBhAFYAZQByAHMAaQBvAG4AIAAwAC4AMABuAG8AdgBhbm92YQBuAG8AdgBhAFIAZQBnAHUAbABhAHIAbgBvAHYAYQBHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('woff');
font-weight: normal;
font-style: normal;
}
#menu-posts-nova_menu_item:before,
#dashboard_right_now .nova-menu-count a:before,
#dashboard_right_now .nova-menu-count span:before {
font-family: 'nova-font';
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#dashboard_right_now .nova-menu-count a:before, #dashboard_right_now .nova-menu-count span:before {
content: '\e603';
}
@@ -0,0 +1,110 @@
/* edit-items.css
-------------------------------------------------------------- */
.widefat .menu-label-row td {
border-bottom-width: 1px;
}
.widefat .menu-label-row td h3 {
padding-left: 30px;
}
.widefat .menu-label-row td h3 .edit-nova-section {
font-size: .8em;
font-weight: normal;
margin-left: 5px;
}
.widefat .menu-order-value {
width: 2.5em;
text-align: center;
}
.widefat .menu-label-row, .widefat .menu-label-row td {
background-color: #f0f0f1;
color: #111;
border: 0 none;
-webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.05);
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.05);
}
.ui-sortable .type-nova_menu_item {
cursor: move;
background-color: #fff;
}
.ui-sortable .type-nova_menu_item:nth-child(even) {
background-color: #f6f7f7;
}
.ui-sortable .type-nova_menu_item.ui-sortable-helper {
-webkit-box-shadow: 0 0 1px rgba(0, 0, 0, 0.1);
box-shadow: 0 0 1px rgba(0, 0, 0, 0.1);
}
.tablenav .button-reorder {
margin-top: 4px;
}
.tablenav .view-switch a, .tablenav div.tablenav-pages {
display: none;
}
/* many-items.css
-------------------------------------------------------------- */
.many-items-table th, .many-items-table td {
width: 30%;
}
.many-items-table th.nova-price, .many-items-table td.nova-price {
width: 10%;
}
.many-items-table input, .many-items-table textarea {
width: 100%;
}
.many-items-table input[type=file] {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
/* new
-------------------------------------------------------------- */
#the-list tr td:nth-of-type(2) {
padding-top: 15px;
}
.nova-move-menu-up:before,
.nova-move-menu-down:before {
margin-right: 5px;
font: normal 10px/1 'dashicons' !important;
speak: none;
}
.nova-move-menu-up:before {
content: "\f342";
}
.nova-move-menu-down:before {
content: "\f346";
}
.dashicon:before {
font: normal 20px/1 'dashicons';
speak: none;
top: 5px;
display: inline-block;
position: relative;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-decoration: none !important;
vertical-align: top;
}
.dashicon-plus:before {
content: "\f132";
}
.dashicon-edit:before {
margin: 2px 5px 0 10px;
content: "\f327";
font-size: 10px;
}
@@ -0,0 +1,131 @@
.jetpack-portfolio-shortcode {
clear: both;
margin: 0;
overflow: hidden;
padding: 0;
}
.portfolio-entry {
float: left;
margin: 0 0 3em;
padding: 0;
width: 100%;
}
/* Column setting */
.portfolio-entry-column-1 {
width: 100%;
}
.portfolio-entry-column-2 {
margin-right: 4%;
width: 48%;
}
.portfolio-entry-column-3 {
margin-right: 3.5%;
width: 31%;
}
.portfolio-entry-column-4 {
margin-right: 3%;
width: 22%;
}
.portfolio-entry-column-5 {
margin-right: 2.5%;
width: 18%;
}
.portfolio-entry-column-6 {
margin-right: 2%;
width: 15%;
}
.portfolio-entry-first-item-row {
clear: both;
}
.portfolio-entry-last-item-row {
margin-right: 0;
}
@media screen and (max-width:768px) {
.portfolio-entry-mobile-first-item-row{
margin-right: 4%;
width: 48%;
clear:both;
}
.portfolio-entry-first-item-row {
clear:none;
}
.portfolio-entry-mobile-last-item-row{
width: 48%;
margin-right: 0;
}
}
/* Entry Header */
.portfolio-entry-header {
border: 0;
margin: 0;
padding: 0;
}
.portfolio-featured-image {
margin: 0;
padding: 0;
}
.portfolio-featured-image img {
border: 0;
height: auto;
max-width: 100%;
vertical-align: middle;
}
.portfolio-entry-title {
font-weight: 700;
margin: 0;
padding: 0;
}
.portfolio-featured-image + .portfolio-entry-title {
margin-top: 1.0em;
}
.portfolio-entry-title a {
border: 0;
text-decoration: none;
}
/* Entry Meta */
.portfolio-entry-meta {
margin: 0;
padding: 0;
}
.portfolio-entry-title + .portfolio-entry-meta {
margin-top: 0.75em;
}
.portfolio-entry-title + .portfolio-entry-meta:empty {
margin: 0;
}
.portfolio-entry-meta span,
.portfolio-entry-meta a {
font-size: 0.9em;
padding: 0;
}
.portfolio-entry-meta a {
border: 0;
text-decoration: none;
}
/* Entry Content */
.portfolio-entry-content {
margin: 0.75em 0 0;
padding: 0;
}
.portfolio-entry-content > :last-child {
margin: 0;
}
@@ -0,0 +1,102 @@
.jetpack-testimonial-shortcode {
clear: both;
margin: 0;
overflow: hidden;
padding: 0;
}
.testimonial-entry {
float: left;
margin: 0 0 3em;
padding: 0;
width: 100%;
}
/* Column setting */
.testimonial-entry-column-1 {
width: 100%;
}
.testimonial-entry-column-2 {
margin-right: 4%;
width: 48%;
}
.testimonial-entry-column-3 {
margin-right: 3.5%;
width: 31%;
}
.testimonial-entry-column-4 {
margin-right: 3%;
width: 22%;
}
.testimonial-entry-column-5 {
margin-right: 2.5%;
width: 18%;
}
.testimonial-entry-column-6 {
margin-right: 2%;
width: 15%;
}
.testimonial-entry-first-item-row {
clear: both;
}
.testimonial-entry-last-item-row {
margin-right: 0;
}
@media screen and (max-width:768px) {
.testimonial-entry-mobile-first-item-row{
margin-right: 4%;
width: 48%;
clear:both;
}
.testimonial-entry-first-item-row {
clear:none;
}
.testimonial-entry-mobile-last-item-row{
width: 48%;
margin-right: 0;
}
}
.testimonial-featured-image {
padding: 0;
margin: 0;
}
.testimonial-featured-image img {
border: 0;
height: auto;
max-width: 100%;
vertical-align: middle;
}
.testimonial-entry-title {
font-weight: 700;
margin: 0;
padding: 0;
display: block;
}
.testimonial-featured-image + .testimonial-entry-title {
margin-top: 1.0em;
}
.testimonial-entry-title a {
border: 0;
text-decoration: none;
}
/* Entry Content */
.testimonial-entry-content {
margin: 0.75em 0;
padding: 0;
}
.testimonial-entry-content > :last-child {
margin: 0;
}
@@ -0,0 +1,144 @@
( function () {
let menuSelector, nonceInput;
const initializedTables = new Set();
const methods = {
init: function ( table ) {
let tbody = table.lastElementChild;
while ( tbody && tbody.tagName !== 'TBODY' ) {
tbody = tbody.previousElementSibling;
}
const row = tbody.querySelector( 'tr:first-child' ).cloneNode( true );
table.dataset.form = table.closest( 'form' );
table.dataset.tbody = tbody;
table.dataset.row = row;
table.dataset.currentRow = row;
menuSelector = document.getElementById( 'nova-menu-tax' );
nonceInput = document.getElementById( '_wpnonce' );
table.addEventListener( 'keypress', function ( event ) {
if ( event.which !== 13 ) return;
event.preventDefault();
if ( typeof FormData === 'function' ) {
methods.submitRow.call( table );
}
methods.addRow.call( table );
} );
table.addEventListener( 'focusin', function ( event ) {
if ( event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA' ) {
table.dataset.currentRow = event.target.closest( 'tr' );
}
} );
initializedTables.add( table );
return table;
},
destroy: function ( table ) {
if ( this.observer ) {
this.observer.disconnect();
}
table.removeEventListener( 'keypress', methods.keypressHandler );
table.removeEventListener( 'focusin', methods.focusinHandler );
initializedTables.delete( table );
return table;
},
submitRow: function ( table ) {
const submittedRow = table.dataset.currentRow;
const currentInputs = submittedRow.querySelectorAll( 'input, textarea, select' );
const form = document.querySelector( table.dataset.form );
const allInputs = Array.from( form.querySelectorAll( 'input, textarea, select' ) );
currentInputs.forEach( input => ( input.disabled = true ) );
allInputs
.filter( input => ! currentInputs.includes( input ) )
.forEach( input => ( input.disabled = true ) );
const partialFormData = new FormData( form );
partialFormData.append( 'ajax', '1' );
partialFormData.append( 'nova_menu_tax', menuSelector.value );
partialFormData.append( '_wpnonce', nonceInput.value );
fetch( '', {
method: 'POST',
body: partialFormData,
} )
.then( response => response.text() )
.then( responseText => {
submittedRow.innerHTML = responseText;
} );
allInputs.forEach( input => ( input.disabled = false ) );
return table;
},
addRow: function ( table ) {
const row = table.dataset.row.cloneNode( true );
const tbody = table.dataset.tbody;
tbody.appendChild( row );
const firstInput = row.querySelector( 'input, textarea, select' );
if ( firstInput ) firstInput.focus();
return table;
},
clickAddRow: function ( table ) {
let tbody = table.lastElementChild;
while ( tbody && tbody.tagName !== 'TBODY' ) {
tbody = tbody.previousElementSibling;
}
const row = tbody.querySelector( 'tr:first-child' ).cloneNode( true );
row.querySelectorAll( 'input, textarea' ).forEach( input => {
input.value = '';
} );
tbody.appendChild( row );
},
};
const observeTableRemoval = function ( list ) {
const observer = new MutationObserver( mutations => {
mutations.forEach( mutation => {
mutation.removedNodes.forEach( node => {
if ( node.matches && node.matches( '.many-items-table' ) ) {
methods.destroy( node );
}
} );
} );
} );
observer.observe( list, { childList: true, subtree: true } );
};
// Initialization for many-items-table
document.addEventListener( 'focusin', event => {
const table = event.target.closest( '.many-items-table' );
if ( table && ! initializedTables.has( table ) ) {
methods.init( table );
}
} );
document.addEventListener( 'click', event => {
if ( event.target.matches( 'a.nova-new-row' ) ) {
const table = event.target.closest( '.many-items-table' );
if ( table ) {
event.preventDefault();
methods.clickAddRow( table );
}
}
} );
const list = document.querySelector( '#the-list' ); // Scope to the specific table
if ( list ) {
observeTableRemoval( list );
}
} )();
@@ -0,0 +1,75 @@
( function () {
const NovaCheckBoxes = {
inputs: null,
popInputs: null,
initialize: function () {
// Get all checkboxes in the "nova_menuchecklist-pop"
NovaCheckBoxes.popInputs = document.querySelectorAll(
'#nova_menuchecklist-pop input[type="checkbox"]'
);
// Get all checkboxes in the "nova_menuchecklist" and add event listeners
NovaCheckBoxes.inputs = document.querySelectorAll(
'#nova_menuchecklist input[type="checkbox"]'
);
NovaCheckBoxes.inputs.forEach( input => {
input.addEventListener( 'change', NovaCheckBoxes.checkOne );
input.addEventListener( 'change', NovaCheckBoxes.syncPop );
} );
// If no checkboxes are checked, check the first one
if ( ! NovaCheckBoxes.isChecked() ) {
NovaCheckBoxes.checkFirst();
}
// Sync the state of the "pop" inputs
NovaCheckBoxes.syncPop();
},
syncPop: function () {
NovaCheckBoxes.popInputs.forEach( popInput => {
const linkedInput = document.querySelector( `#in-nova_menu-${ popInput.value }` );
popInput.checked = linkedInput ? linkedInput.checked : false;
} );
},
isChecked: function () {
return Array.from( NovaCheckBoxes.inputs ).some( input => input.checked );
},
checkFirst: function () {
const firstInput = NovaCheckBoxes.inputs[ 0 ];
if ( firstInput ) {
firstInput.checked = true;
}
},
checkOne: function () {
const currentInput = this;
// If the current checkbox is checked, uncheck all other checkboxes
if ( currentInput.checked ) {
NovaCheckBoxes.inputs.forEach( input => {
if ( input !== currentInput ) {
input.checked = false;
}
} );
return;
}
const checklist = document.querySelector( '#nova_menuchecklist' );
// If at least one checkbox is still checked, uncheck the current one
if ( checklist.querySelectorAll( 'input[type="checkbox"]:checked' ).length > 0 ) {
currentInput.checked = false;
return;
}
// Otherwise, check the first checkbox
NovaCheckBoxes.checkFirst();
},
};
// Initialize when the DOM is fully loaded
document.addEventListener( 'DOMContentLoaded', NovaCheckBoxes.initialize );
} )();
@@ -0,0 +1,66 @@
/* global _novaDragDrop */
( function ( $ ) {
var list;
function init() {
list = $( '#the-list' );
dragMenus();
addNonce();
addSubmitButton();
changeToPost();
}
function dragMenus() {
list.sortable( {
cancel: '.no-items, .inline-edit-row',
stop: function ( event, ui ) {
if ( ui.item.is( ':first-child' ) ) {
return list.sortable( 'cancel' );
}
//
reOrder();
},
} );
}
function reOrder() {
list.find( '.menu-label-row' ).each( function () {
var term_id = $( this ).data( 'term_id' );
$( this )
.nextUntil( '.menu-label-row' )
.each( function ( i ) {
var row = $( this );
row.find( '.menu-order-value' ).val( i );
row.find( '.nova-menu-term' ).val( term_id );
} );
} );
}
function addSubmitButton() {
$( '.tablenav' ).prepend(
'<input type="submit" class="button-primary button-reorder alignright" value="' +
_novaDragDrop.reorder +
'" name="' +
_novaDragDrop.reorderName +
'" />'
);
}
function addNonce() {
$( '#posts-filter' ).append(
'<input type="hidden" name="' +
_novaDragDrop.nonceName +
'" value="' +
_novaDragDrop.nonce +
'" />'
);
}
function changeToPost() {
$( '#posts-filter' ).attr( 'method', 'post' );
}
// do it
$( document ).ready( init );
} )( jQuery );
@@ -0,0 +1,779 @@
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Manage restaurant menus from your WordPress site,
* via a new "nova" CPT.
*
* Put the following code in your theme's Food Menu Page Template to customize the markup of the menu.
*
* if ( class_exists( 'Nova_Restaurant' ) ) {
* Nova_Restaurant::init( array(
* 'menu_tag' => 'section',
* 'menu_class' => 'menu-items',
* 'menu_header_tag' => 'header',
* 'menu_header_class' => 'menu-group-header',
* 'menu_title_tag' => 'h1',
* 'menu_title_class' => 'menu-group-title',
* 'menu_description_tag' => 'div',
* 'menu_description_class' => 'menu-group-description',
* ) );
* }
*
* @todo
* - Bulk/Quick edit response of Menu Item rows is broken.
* - Drag and Drop reordering.
*
* @package automattic/jetpack
*/
if ( ! class_exists( '\Nova_Restaurant' ) ) {
/**
* Create the new Nova CPT.
*/
class Nova_Restaurant {
const MENU_ITEM_POST_TYPE = 'nova_menu_item';
const MENU_ITEM_LABEL_TAX = 'nova_menu_item_label';
const MENU_TAX = 'nova_menu';
/**
* Store an instance of the new class
*
* @var Automattic\Jetpack\Classic_Theme_Helper\Nova_Restaurant
*/
protected $new_instance;
/**
* Version number used when enqueuing all resources (css and js).
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @var string
*/
public $version = '20210303';
/**
* Default markup for the menu items.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @var array
*/
protected $default_menu_item_loop_markup = array(
'menu_tag' => 'section',
'menu_class' => 'menu-items',
'menu_header_tag' => 'header',
'menu_header_class' => 'menu-group-header',
'menu_title_tag' => 'h1',
'menu_title_class' => 'menu-group-title',
'menu_description_tag' => 'div',
'menu_description_class' => 'menu-group-description',
);
/**
* Array of markup for the menu items.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @var array
*/
protected $menu_item_loop_markup = array();
/**
* Last term ID of a loop of menu items.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @var bool|int
*/
protected $menu_item_loop_last_term_id = false;
/**
* Current term ID of a loop of menu items.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @var bool|int
*/
protected $menu_item_loop_current_term = false;
/**
* Initialize class.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param array $menu_item_loop_markup Array of markup for the menu items.
*
* @return Automattic\Jetpack\Classic_Theme_Helper\Nova_Restaurant
*/
public static function init( $menu_item_loop_markup = array() ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return Automattic\Jetpack\Classic_Theme_Helper\Nova_Restaurant::init( $menu_item_loop_markup );
}
/**
* Constructor.
* Hook into WordPress to create CPT and utilities if needed.
*/
public function __construct() {
$this->new_instance = new Automattic\Jetpack\Classic_Theme_Helper\Nova_Restaurant();
}
/**
* Forward all method calls to the Nova_Restaurant class.
*
* @param string $name The name of the method.
* @param array $arguments The arguments to pass to the method.
*
* @throws Exception If the method is not found.
*/
public function __call( $name, $arguments ) {
if ( method_exists( $this->new_instance, $name ) ) {
return call_user_func_array( array( $this->new_instance, $name ), $arguments );
} else {
// Handle cases where the method is not found
throw new Exception( sprintf( 'Undefined method: %s', esc_html( $name ) ) );
}
}
/**
* Forward all static method calls to the Nova_Restaurant class.
*
* @param string $name The name of the method.
* @param array $arguments The arguments to pass to the method.
*
* @throws Exception If the method is not found.
*/
public static function __callStatic( $name, $arguments ) {
if ( method_exists( Automattic\Jetpack\Classic_Theme_Helper\Nova_Restaurant::class, $name ) ) {
return call_user_func_array( array( Automattic\Jetpack\Classic_Theme_Helper\Nova_Restaurant::class, $name ), $arguments );
} else {
// Handle cases where the method is not found
throw new Exception( sprintf( 'Undefined static method: %s', esc_html( $name ) ) );
}
}
/**
* Should this Custom Post Type be made available?
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @return bool
*/
public function site_supports_nova() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return $this->new_instance->site_supports_nova();
}
/* Setup */
/**
* Register Taxonomies and Post Type
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*/
public function register_taxonomies() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->register_taxonomies();
}
/**
* Register our Post Type.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*/
public function register_post_types() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->register_post_types();
}
/**
* Update messages for the Menu Item admin.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param array $messages Existing post update messages.
*
* @return array $messages Updated post update messages.
*/
public function updated_messages( $messages ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return $this->new_instance->updated_messages( $messages );
}
/**
* Nova styles and scripts.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param string $hook Page hook.
*
* @return void
*/
public function enqueue_nova_styles( $hook ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->enqueue_nova_styles( $hook );
}
/**
* Change Enter Title Here text for the Menu Item.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param string $title Default title placeholder text.
*
* @return string
*/
public function change_default_title( $title ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return $this->new_instance->change_default_title( $title );
}
/**
* Add to Dashboard At A Glance
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @return void
*/
public function add_to_dashboard() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->add_to_dashboard();
}
/**
* If the WP query for our menu items.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param WP_Query $query WP Query.
*
* @return bool
*/
public function is_menu_item_query( $query ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return $this->new_instance->is_menu_item_query( $query );
}
/**
* Custom sort the menu item queries by menu order.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param WP_Query $query WP Query.
*
* @return void
*/
public function sort_menu_item_queries_by_menu_order( $query ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->sort_menu_item_queries_by_menu_order( $query );
}
/**
* Custom sort the menu item queries by menu taxonomies.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param WP_Post[] $posts Array of post objects.
* @param WP_Query $query The WP_Query instance.
*
* @return WP_Post[]
*/
public function sort_menu_item_queries_by_menu_taxonomy( $posts, $query ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return $this->new_instance->sort_menu_item_queries_by_menu_taxonomy( $posts, $query );
}
/**
* Add new "Add many items" submenu, custom colunmns, and custom bulk actions.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @return void
*/
public function add_admin_menus() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->add_admin_menus();
}
/**
* Custom Nova Icon CSS
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @return void
*/
public function set_custom_font_icon() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->set_custom_font_icon();
}
/**
* Load Nova menu management tools on the CPT admin screen.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @return void
*/
public function current_screen_load() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->current_screen_load();
}
/* Edit Items List */
/**
* Display a notice in wp-admin after items have been changed.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @return void
*/
public function admin_notices() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->admin_notices();
}
/**
* Do not allow sorting by title.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param array $columns An array of sortable columns.
*
* @return array $columns.
*/
public function no_title_sorting( $columns ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return $this->new_instance->no_title_sorting( $columns );
}
/**
* Set up custom columns for our Nova menu.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @return void
*/
public function setup_menu_item_columns() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->setup_menu_item_columns();
}
/**
* Add custom columns to the Nova menu item list.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param array $columns An array of columns.
*
* @return array $columns.
*/
public function menu_item_columns( $columns ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return $this->new_instance->menu_item_columns( $columns );
}
/**
* Display custom data in each new custom column we created.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param string $column The name of the column to display.
* @param int $post_id The current post ID.
*
* @return void
*/
public function menu_item_column_callback( $column, $post_id ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->menu_item_column_callback( $column, $post_id );
}
/**
* Get menu item by post ID.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param int $post_id Post ID.
*
* @return bool|WP_Term
*/
public function get_menu_by_post_id( $post_id = null ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return $this->new_instance->get_menu_by_post_id( $post_id );
}
/**
* Fires on a menu edit page. We might have drag-n-drop reordered
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*/
public function maybe_reorder_menu_items() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->maybe_reorder_menu_items();
}
/**
* Handle changes to menu items.
* (process actions, update data, enqueue necessary scripts).
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @return void
*/
public function edit_menu_items_page_load() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->edit_menu_items_page_load();
}
/**
* Process actions to move menu items around.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @return void
*/
public function handle_menu_item_actions() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->handle_menu_item_actions();
}
/**
* Add menu title rows to the list table
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param WP_Post $post The Post object.
*
* @return void
*/
public function show_menu_titles_in_menu_item_list( $post ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->show_menu_titles_in_menu_item_list( $post );
}
/* Edit Many Items */
/**
* Handle form submissions that aim to add many menu items at once.
* (process posted data and enqueue necessary script).
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @return void
*/
public function add_many_new_items_page_load() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->add_many_new_items_page_load();
}
/**
* Enqueue script to create many items at once.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @return void
*/
public function enqueue_many_items_scripts() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->enqueue_many_items_scripts();
}
/**
* Process form request to create many items at once.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @return void
*/
public function process_form_request() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->process_form_request();
}
/**
* Admin page contents for adding many menu items at once.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @return void
*/
public function add_many_new_items_page() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->add_many_new_items_page();
}
/* Edit One Item */
/**
* Create admin meta box to save price for a menu item,
* and add script to add extra checkboxes to the UI.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @return void
*/
public function register_menu_item_meta_boxes() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->register_menu_item_meta_boxes();
}
/**
* Meta box to edit the price of a menu item.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param WP_Post $post The post object.
*
* @return void
*/
public function menu_item_price_meta_box( $post ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->menu_item_price_meta_box( $post );
}
/**
* Save the price of a menu item.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param int $post_id Post ID.
*/
public function add_post_meta( $post_id ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->add_post_meta( $post_id );
}
/* Data */
/**
* Get ordered array of menu items.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param array $args Optional argumments.
*
* @return array
*/
public function get_menus( $args = array() ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return $this->new_instance->get_menus( $args );
}
/**
* Get first menu taxonomy "leaf".
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param int $post_id Post ID.
*
* @return bool|WP_Term|WP_Error|null
*/
public function get_menu_item_menu_leaf( $post_id ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return $this->new_instance->get_menu_item_menu_leaf( $post_id );
}
/**
* Get a list of the labels linked to a menu item.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param int $post_id Post ID.
*
* @return void
*/
public function list_labels( $post_id = 0 ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->list_labels( $post_id );
}
/**
* Get a list of the labels linked to a menu item, with links to manage them.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param int $post_id Post ID.
*
* @return void
*/
public function list_admin_labels( $post_id = 0 ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->list_admin_labels( $post_id );
}
/**
* Update post meta with the price defined in meta box.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param int $post_id Post ID.
* @param string $price Price.
*
* @return int|bool
*/
public function set_price( $post_id = 0, $price = '' ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return $this->new_instance->set_price( $post_id, $price );
}
/**
* Get the price of a menu item.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param int $post_id Post ID.
*
* @return bool|string
*/
public function get_price( $post_id = 0 ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return $this->new_instance->get_price( $post_id );
}
/**
* Echo the price of a menu item.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param int $post_id Post ID.
*
* @return void
*/
public function display_price( $post_id = 0 ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->display_price( $post_id );
}
/* Menu Item Loop Markup */
/**
* Get markup for a menu item.
* Note: Does not support nested loops.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param null|string $field The field to get the value for.
*
* @return array
*/
public function get_menu_item_loop_markup( $field = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return $this->new_instance->get_menu_item_loop_markup( $field );
}
/**
* Sets up the loop markup.
* Attached to the 'template_include' *filter*,
* which fires only during a real blog view (not in admin, feeds, etc.)
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param string $template Template File.
*
* @return string Template File. VERY Important.
*/
public function setup_menu_item_loop_markup__in_filter( $template ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return $this->new_instance->setup_menu_item_loop_markup__in_filter( $template );
}
/**
* If the Query is a Menu Item Query, start outputing the Menu Item Loop Marku
* Attached to the 'loop_start' action.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param WP_Query $query Post query.
*
* @return void
*/
public function start_menu_item_loop( $query ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->start_menu_item_loop( $query );
}
/**
* Outputs the Menu Item Loop Marku
* Attached to the 'the_post' action.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param WP_Post $post Post object.
*
* @return void
*/
public function menu_item_loop_each_post( $post ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->menu_item_loop_each_post( $post );
}
/**
* If the Query is a Menu Item Query, stop outputing the Menu Item Loop Marku
* Attached to the 'loop_end' action.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param WP_Query $query Post query.
*
* @return void
*/
public function stop_menu_item_loop( $query ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->stop_menu_item_loop( $query );
}
/**
* Outputs the Menu Group Header
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @return void
*/
public function menu_item_loop_header() {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->menu_item_loop_header();
}
/**
* Outputs a Menu Item Markup element opening tag
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param string $field - Menu Item Markup settings field.
*
* @return void
*/
public function menu_item_loop_open_element( $field ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->menu_item_loop_open_element( $field );
}
/**
* Outputs a Menu Item Markup element closing tag
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param string $field - Menu Item Markup settings field.
*
* @return void
*/
public function menu_item_loop_close_element( $field ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
$this->new_instance->menu_item_loop_close_element( $field );
}
/**
* Returns a Menu Item Markup element's class attribute.
*
* @deprecated 14.3 Moved to Classic Theme Helper package.
*
* @param string $class Class name.
*
* @return string HTML class attribute with leading whitespace.
*/
public function menu_item_loop_class( $class ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.3' );
return $this->new_instance->menu_item_loop_class( $class );
}
}
}
@@ -0,0 +1,336 @@
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Register a portfolio post type and handle displaying it anywhere on the site.
*
* @package automattic/jetpack
*/
if ( ! class_exists( 'Jetpack_Portfolio' ) ) {
/**
* Jetpack Portfolio.
*/
class Jetpack_Portfolio {
/**
* Store an instance of the new class
*
* @var Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Portfolio
*/
protected $new_instance;
/**
* Initialize class.
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*/
public static function init() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
return Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Portfolio::init();
}
/**
* Conditionally hook into WordPress.
*
* Setup user option for enabling CPT
* If user has CPT enabled, show in admin
*/
public function __construct() {
$this->new_instance = new Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Portfolio();
}
/**
* Forward all method calls to the Jetpack_Portfolio class.
*
* @param string $name The name of the method.
* @param array $arguments The arguments to pass to the method.
*
* @throws Exception If the method is not found.
*/
public function __call( $name, $arguments ) {
if ( method_exists( $this->new_instance, $name ) ) {
return call_user_func_array( array( $this->new_instance, $name ), $arguments );
} else {
// Handle cases where the method is not found
throw new Exception( sprintf( 'Undefined method: %s', esc_html( $name ) ) );
}
}
/**
* Forward all static method calls to the Jetpack_Portfolio class.
*
* @param string $name The name of the method.
* @param array $arguments The arguments to pass to the method.
*
* @throws Exception If the method is not found.
*/
public static function __callStatic( $name, $arguments ) {
if ( method_exists( Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Portfolio::class, $name ) ) {
return call_user_func_array( array( Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Portfolio::class, $name ), $arguments );
} else {
// Handle cases where the method is not found
throw new Exception( sprintf( 'Undefined static method: %s', esc_html( $name ) ) );
}
}
/**
* Registers the custom post types and adds action/filter handlers, but
* only if the site supports it
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*/
public function maybe_register_cpt() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->maybe_register_cpt();
}
/**
* Add a checkbox field in 'Settings' > 'Writing'
* for enabling CPT functionality.
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*
* @return void
*/
public function settings_api_init() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->settings_api_init();
}
/**
* HTML code to display a checkbox true/false option
* for the Portfolio CPT setting.
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*
* @return void
*/
public function setting_html() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->setting_html();
}
/**
* Bump Portfolio > New Activation stat.
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*/
public function new_activation_stat_bump() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->new_activation_stat_bump();
}
/**
* Bump Portfolio > Option On/Off stats to get total active.
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
* @param mixed $old The old option value.
* @param mixed $new The new option value.
*/
public function update_option_stat_bump( $old, $new ) {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->update_option_stat_bump( $old, $new );
}
/**
* Bump Portfolio > Published Projects stat when projects are published.
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*/
public function new_project_stat_bump() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->new_project_stat_bump();
}
/**
* Flush permalinks when CPT option is turned on/off
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*/
public function flush_rules_on_enable() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->flush_rules_on_enable();
}
/**
* Count published projects and flush permalinks when first projects is published
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*/
public function flush_rules_on_first_project() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->flush_rules_on_first_project();
}
/**
* Flush permalinks when CPT supported theme is activated
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*/
public function flush_rules_on_switch() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->flush_rules_on_switch();
}
/**
* On plugin/theme activation, check if current theme supports CPT
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*/
public static function activation_post_type_support() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Portfolio::activation_post_type_support();
}
/**
* On theme switch, check if CPT item exists and disable if not
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*/
public function deactivation_post_type_support() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->deactivation_post_type_support();
}
/**
* Register Post Type
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*/
public function register_post_types() {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->register_post_types();
}
/**
* Update messages for the Portfolio admin.
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
* @param array $messages Existing post update messages.
*/
public function updated_messages( $messages ) {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->updated_messages( $messages );
}
/**
* Change Title column label
* Add Featured Image column
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*
* @param array $columns An array of column names.
*/
public function edit_admin_columns( $columns ) {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->edit_admin_columns( $columns );
}
/**
* Add featured image to column
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*
* @param string $column The name of the column to display.
* @param int $post_id The current post ID.
*/
public function image_column( $column, $post_id ) {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->image_column( $column, $post_id );
}
/**
* Adjust image column width
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*
* @param string $hook Page hook.
*/
public function enqueue_admin_styles( $hook ) {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->enqueue_admin_styles( $hook );
}
/**
* Adds portfolio section to the Customizer.
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
*
* @param WP_Customize_Manager $wp_customize Customizer instance.
*/
public function customize_register( $wp_customize ) {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->customize_register( $wp_customize );
}
/**
* Follow CPT reading setting on CPT archive and taxonomy pages
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
* @param WP_Query $query A WP_Query instance.
*/
public function query_reading_setting( $query ) {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->query_reading_setting( $query );
}
/**
* If Infinite Scroll is set to 'click', use our custom reading setting instead of core's `posts_per_page`.
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
* @param array $settings Array of Infinite Scroll settings.
*/
public function infinite_scroll_click_posts_per_page( $settings ) {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->infinite_scroll_click_posts_per_page( $settings );
}
/**
* Filter the results of infinite scroll to make sure we get `lastbatch` right.
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
* @param array $results Array of Infinite Scroll results.
* @param array $query_args Array of main query arguments.
* @param WP_Query $query WP Query.
*/
public function infinite_scroll_results( $results, $query_args, $query ) {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->infinite_scroll_results( $results, $query_args, $query );
}
/**
* Add CPT to Dotcom sitemap
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
* @param array $post_types Array of post types included in sitemap.
*/
public function add_to_sitemap( $post_types ) {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->add_to_sitemap( $post_types );
}
/**
* Add to REST API post type allowed list.
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
* @param array $post_types Array of post types to add to the allowed list. Default to `array( 'post', 'page', 'revision' )`.
*/
public function allow_portfolio_rest_api_type( $post_types ) {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
$this->new_instance->allow_portfolio_rest_api_type( $post_types );
}
/**
* Our [portfolio] shortcode.
* Prints Portfolio data styled to look good on *any* theme.
*
* @deprecated 13.9 Moved to Classic Theme Helper package.
* @param array $atts Shortcode attributes.
*
* @return string html
*/
public static function portfolio_shortcode( $atts ) {
_deprecated_function( __FUNCTION__, 'jetpack-13.9' );
return Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Portfolio::portfolio_shortcode( $atts );
}
}
}
@@ -0,0 +1,410 @@
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Register a Testimonial post type and handle displaying it anywhere on the site.
*
* @package automattic/jetpack
*
* @phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
* @phpcs:disable MediaWiki.Usage.NestedFunctions.NestedFunction
*/
// phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files.
if ( ! class_exists( 'Jetpack_Testimonial' ) ) {
/**
* Add a Testimonial CPT, and display it with a shortcode
*/
class Jetpack_Testimonial {
/**
* Store an instance of the new class
*
* @var Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Testimonial
*/
protected $new_instance;
/**
* Initialize class.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*/
public static function init() {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
return Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Testimonial::init();
}
/**
* Conditionally hook into WordPress.
*
* Setup user option for enabling CPT.
* If user has CPT enabled, show in admin.
*/
public function __construct() {
$this->new_instance = new Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Testimonial();
}
/**
* Forward all method calls to the Jetpack_Testimonial class.
*
* @param string $name The name of the method.
* @param array $arguments The arguments to pass to the method.
*
* @throws Exception If the method is not found.
*/
public function __call( $name, $arguments ) {
if ( method_exists( $this->new_instance, $name ) ) {
return call_user_func_array( array( $this->new_instance, $name ), $arguments );
} else {
// Handle cases where the method is not found
throw new Exception( sprintf( 'Undefined method: %s', esc_html( $name ) ) );
}
}
/**
* Forward all static method calls to the Jetpack_Testimonial class.
*
* @param string $name The name of the method.
* @param array $arguments The arguments to pass to the method.
*
* @throws Exception If the method is not found.
*/
public static function __callStatic( $name, $arguments ) {
if ( method_exists( Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Testimonial::class, $name ) ) {
return call_user_func_array( array( Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Testimonial::class, $name ), $arguments );
} else {
// Handle cases where the method is not found
throw new Exception( sprintf( 'Undefined static method: %s', esc_html( $name ) ) );
}
}
/**
* Registers the custom post types and adds action/filter handlers, but
* only if the site supports it.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*/
public function maybe_register_cpt() {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
$this->new_instance->maybe_register_cpt();
}
/**
* Add a checkbox field in 'Settings' > 'Writing'
* for enabling CPT functionality.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*
* @return void
*/
public function settings_api_init() {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
$this->new_instance->settings_api_init();
}
/**
* HTML code to display a checkbox true/false option
* for the CPT setting.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*
* @return void
*/
public function setting_html() {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
$this->new_instance->setting_html();
}
/**
* Add to REST API post type allowed list.
*
* @param array $post_types Array of allowed post types.
* @return array `$post_types` with our type added.
*/
public function allow_cpt_rest_api_type( $post_types ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
return $this->new_instance->allow_cpt_rest_api_type( $post_types );
}
/**
* Bump Testimonial > New Activation stat
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*/
public function new_activation_stat_bump() {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
$this->new_instance->new_activation_stat_bump();
}
/**
* Bump Testimonial > Option On/Off stats to get total active
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*
* @param mixed $old The old option value.
* @param mixed $new The new option value.
*/
public function update_option_stat_bump( $old, $new ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
$this->new_instance->update_option_stat_bump( $old, $new );
}
/**
* Bump Testimonial > Published Testimonials stat when testimonials are published
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*/
public function new_testimonial_stat_bump() {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
$this->new_instance->new_testimonial_stat_bump();
}
/**
* Flush permalinks when CPT option is turned on/off
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*/
public function flush_rules_on_enable() {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
$this->new_instance->flush_rules_on_enable();
}
/**
* Count published testimonials and flush permalinks when first testimonial is published
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*/
public function flush_rules_on_first_testimonial() {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
$this->new_instance->flush_rules_on_first_testimonial();
}
/**
* Flush permalinks when CPT supported theme is activated
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*/
public function flush_rules_on_switch() {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
$this->new_instance->flush_rules_on_switch();
}
/**
* On plugin/theme activation, check if current theme supports CPT
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*/
public static function activation_post_type_support() {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Testimonial::activation_post_type_support();
}
/**
* On theme switch, check if CPT item exists and disable if not
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*/
public function deactivation_post_type_support() {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
$this->new_instance->deactivation_post_type_support();
}
/**
* Register Post Type
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*/
public function register_post_types() {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
$this->new_instance->register_post_types();
}
/**
* Update messages for the Testimonial admin.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*
* @param array $messages Existing post update messages.
* @return array Updated `$messages`.
*/
public function updated_messages( $messages ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
return $this->new_instance->updated_messages( $messages );
}
/**
* Change Enter Title Here text for the Testimonial.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*
* @param string $title Placeholder text. Default 'Add title'.
* @return string Replacement title.
*/
public function change_default_title( $title ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
return $this->new_instance->change_default_title( $title );
}
/**
* Change Title column label on all Testimonials page.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*
* @param array $columns An array of column names.
* @return array Updated array.
*/
public function edit_title_column_label( $columns ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
return $this->new_instance->edit_title_column_label( $columns );
}
/**
* Follow CPT reading setting on CPT archive page
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*
* @param WP_Query $query A WP_Query instance.
*/
public function query_reading_setting( $query ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
$this->new_instance->query_reading_setting( $query );
}
/**
* If Infinite Scroll is set to 'click', use our custom reading setting instead of core's `posts_per_page`.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*
* @param array $settings Array of Infinite Scroll settings.
* @return array Updated `$settings`.
*/
public function infinite_scroll_click_posts_per_page( $settings ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
return $this->new_instance->infinite_scroll_click_posts_per_page( $settings );
}
/**
* Add CPT to Dotcom sitemap
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*
* @param array $post_types Array of post types included in sitemap.
* @return array Updated `$post_types`.
*/
public function add_to_sitemap( $post_types ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
return $this->new_instance->add_to_sitemap( $post_types );
}
/**
* Adds a submenu link to the Customizer.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*/
public function add_customize_page() {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
$this->new_instance->add_customize_page();
}
/**
* Adds testimonial section to the Customizer.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*
* @param WP_Customize_Manager $wp_customize Customizer instance.
*/
public function customize_register( $wp_customize ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
$this->new_instance->customize_register( $wp_customize );
}
/**
* Add Featured image to theme mod if necessary.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*
* @param array $opt The value of the current theme modification.
* @return array Updated `$opt`.
*/
public function coerce_testimonial_image_to_url( $opt ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
return $this->new_instance->coerce_testimonial_image_to_url( $opt );
}
/**
* Our [testimonial] shortcode.
* Prints Testimonial data styled to look good on *any* theme.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*
* @param array $atts Shortcode attributes.
*
* @return string HTML from `self::jetpack_testimonial_shortcode_html()`.
*/
public static function jetpack_testimonial_shortcode( $atts ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
return Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Testimonial::jetpack_testimonial_shortcode( $atts );
}
}
/**
* Additional Testimonial customizer options.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*/
function jetpack_testimonial_custom_control_classes() {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
/**
* Clean the title parameter.
*/
class Jetpack_Testimonial_Title_Control extends WP_Customize_Control {
/**
* Sanitize content passed to control.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*
* @param string $value Control value.
* @return string Sanitized value.
*/
public static function sanitize_content( $value ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
return Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Testimonial_Title_Control::sanitize_content( $value );
}
}
/**
* Clean textarea content.
*/
class Jetpack_Testimonial_Textarea_Control extends WP_Customize_Control {
/**
* Control type.
*
* @var string
*/
public $type = 'textarea';
/**
* Render the control's content.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*/
public function render_content() {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
$testimonial_textarea_control = new Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Testimonial_Textarea_Control( $this->manager, $this->id, $this->args );
$testimonial_textarea_control->render_content();
}
/**
* Sanitize content passed to control.
*
* @deprecated 14.2 Moved to Classic Theme Helper package.
*
* @param string $value Control value.
* @return string Sanitized value.
*/
public static function sanitize_content( $value ) {
_deprecated_function( __FUNCTION__, 'jetpack-14.2' );
return Automattic\Jetpack\Classic_Theme_Helper\Jetpack_Testimonial_Textarea_Control::sanitize_content( $value );
}
}
}
}
@@ -0,0 +1,9 @@
<?php // phpcs:ignore Squiz.Commenting.FileComment.Missing
/**
* Module Name: Geo Location
* Module Description: Add location data to your posts.
*
* @package automattic/jetpack
*/
require_once __DIR__ . '/geo-location/class.jetpack-geo-location.php';
@@ -0,0 +1,71 @@
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Adds support for geo-location features.
*
* All Jetpack sites can support geo-location features. Users can tag posts with geo-location data
* using the UI provided by Calypso. That information will be included in RSS feeds, meta tags during
* wp_head, and in the Geo microformat following post content.
*
* If your theme declares support for "geo-location", you'll also get a small icon and location label
* visible to users at the bottom of single posts and pages.
*
* To declare support in your theme, call `add_theme_support( 'jetpack-geo-location' )`.
*
* Once you've added theme support, you can rely on the standard HTML output generated in the
* the_content_location_display() method of this class. Or, you can use the "geo_location_display"
* filter to generate custom HTML for your particular theme. Your filter function will receive an
* the default HTML as its first argument and an array containing the geo-location information as
* its second argument in the following format:
*
* array(
* 'is_public' => boolean,
* 'latitude' => float,
* 'longitude' => float,
* 'label' => string,
* 'is_populated' => boolean
* )
*
* Add your filter with:
*
* add_filter( 'jetpack_geo_location_display', 'your_filter_function_name', 10, 2);
*/
class Jetpack_Geo_Location {
/**
* Jetpack_Geo_Location singleton instance.
*
* @var Jetpack_Geo_Location|null
*/
private static $instance;
/**
* Jetpack_Geo_Location instance init.
*/
public static function init() {
if ( self::$instance === null ) {
self::$instance = new Jetpack_Geo_Location();
}
return self::$instance;
}
/**
* Jetpack_Geo_Location class constructor.
*/
public function __construct() {
add_action( 'init', array( $this, 'wordpress_init' ) );
}
/**
* Register support for the geo-location feature on pages and posts. Register the meta
* fields managed by this plugin so that they are properly sanitized during save.
*/
public function wordpress_init() {
// Only render location label after post content, if the theme claims to support "geo-location".
if ( current_theme_supports( 'jetpack-geo-location' ) ) {
_deprecated_class( 'Jetpack_Geo_Location', '14.3', '' );
}
}
}
Jetpack_Geo_Location::init();
@@ -0,0 +1,20 @@
<?php
/**
* Module Name: Google Fonts (Beta)
* Module Description: A selection of Google fonts for block enabled themes. This feature is still being developed.
* Sort Order: 1
* Recommendation Order: 2
* First Introduced: 10.8.0
* Requires Connection: No
* Auto Activate: No
* Module Tags: Fonts, Recommended
* Feature: Writing
* Additional Search Queries: fonts, webfonts, typography, creator
*
* @package automattic/jetpack
*/
/**
* Load the Google Fonts module.
*/
require_once __DIR__ . '/google-fonts/load.php';
@@ -0,0 +1,252 @@
<?php
/**
* Jetpack_Google_Font_Face class
*
* @package automattic/jetpack
*/
/**
* Jetpack Google Font Face disables Font Face hooks in Core that prints **ALL** font faces.
* Instead, it collects fonts that are used in global styles or block-level settings and
* print those fonts in use.
*/
class Jetpack_Google_Font_Face {
/**
* The fonts that are used in global styles or block-level settings.
*
* @var array
*/
private $fonts_in_use = array();
/**
* The constructor.
*/
public function __construct() {
// Turns off hooks to print fonts
add_action( 'wp_loaded', array( $this, 'wp_loaded' ) );
add_action( 'current_screen', array( $this, 'current_screen' ), 10 );
// Collect and print fonts in use
if ( wp_is_block_theme() ) {
add_action( 'wp_head', array( $this, 'print_font_faces' ), 50 );
} else {
// In classic themes wp_head runs before the blocks are processed to collect the block fonts.
add_action( 'wp_footer', array( $this, 'print_font_faces' ), 50 );
}
add_filter( 'pre_render_block', array( $this, 'collect_block_fonts' ), 10, 2 );
}
/**
* Turn off hooks to print fonts on frontend
*/
public function wp_loaded() {
remove_action( 'wp_head', 'wp_print_fonts', 50 );
remove_action( 'wp_head', 'wp_print_font_faces', 50 );
}
/**
* Turn off hooks to print fonts on wp-admin, except for GB editor pages.
*/
public function current_screen() {
remove_action( 'admin_print_styles', 'wp_print_fonts', 50 );
if ( ! $this->is_block_editor() ) {
remove_action( 'admin_print_styles', 'wp_print_font_faces', 50 );
}
}
/**
* Print fonts that are used in global styles or block-level settings.
*/
public function print_font_faces() {
$fonts = WP_Font_Face_Resolver::get_fonts_from_theme_json();
$font_slug_aliases = $this->get_font_slug_aliases();
$fonts_to_print = array();
$this->collect_global_styles_fonts();
$fonts_in_use = array_values( array_unique( $this->fonts_in_use, SORT_STRING ) );
$fonts_in_use = array_map(
function ( $font_slug ) use ( $font_slug_aliases ) {
return $font_slug_aliases[ $font_slug ] ?? $font_slug;
},
$this->fonts_in_use
);
foreach ( $fonts as $font_faces ) {
$font_family = $font_faces[0]['font-family'] ?? '';
if ( in_array( $this->format_font( $font_family ), $fonts_in_use, true ) ) {
$fonts_to_print[] = $font_faces;
}
}
if ( ! empty( $fonts_to_print ) ) {
wp_print_font_faces( $fonts_to_print );
}
}
/**
* Collect fonts used for global styles settings.
*/
public function collect_global_styles_fonts() {
$global_styles = wp_get_global_styles();
$global_styles_font_slug = $this->get_font_slug_from_setting( $global_styles );
if ( $global_styles_font_slug ) {
$this->add_font( $global_styles_font_slug );
}
if ( isset( $global_styles['blocks'] ) ) {
foreach ( $global_styles['blocks'] as $setting ) {
$font_slug = $this->get_font_slug_from_setting( $setting );
if ( $font_slug ) {
$this->add_font( $font_slug );
}
}
}
if ( isset( $global_styles['elements'] ) ) {
foreach ( $global_styles['elements'] as $setting ) {
$font_slug = $this->get_font_slug_from_setting( $setting );
if ( $font_slug ) {
$this->add_font( $font_slug );
}
}
}
}
/**
* Collect fonts used for block-level settings.
*
* @filter pre_render_block
*
* @param string|null $content The pre-rendered content. Default null.
* @param array $parsed_block The block being rendered.
*/
public function collect_block_fonts( $content, $parsed_block ) {
if ( ! is_admin() && isset( $parsed_block['attrs']['fontFamily'] ) ) {
$block_font_family = $parsed_block['attrs']['fontFamily'];
$this->add_font( $block_font_family );
}
return $content;
}
/**
* Add the specify font to the fonts_in_use list.
*
* @param string $font_slug The font slug.
*/
public function add_font( $font_slug ) {
if ( is_string( $font_slug ) ) {
$this->fonts_in_use[] = $this->format_font( $font_slug );
}
}
/**
* Format the given font slug.
*
* @example "ABeeZee" formats to "abeezee"
* @example "ADLaM Display" formats to "adlam-display"
* @param string $font_slug The font slug.
* @return string The formatted font slug.
*/
public function format_font( $font_slug ) {
return _wp_to_kebab_case( strtolower( $font_slug ) );
}
/**
* Get the font slug aliases that maps the font slug to the font family if they are different.
*
* The font definition may define an alias slug name, so we have to add the map from the slug name to the font family.
* See https://github.com/WordPress/twentytwentyfour/blob/df92472089ede6fae5924c124a93c843b84e8cbd/theme.json#L215.
*/
public function get_font_slug_aliases() {
$font_slug_aliases = array();
$theme_json = WP_Theme_JSON_Resolver::get_theme_data();
$raw_data = $theme_json->get_data();
if ( ! empty( $raw_data['settings']['typography']['fontFamilies'] ) ) {
foreach ( $raw_data['settings']['typography']['fontFamilies'] as $font ) {
$font_family_name = $this->format_font( $this->get_font_family_name( $font ) );
$font_slug = $font['slug'] ?? '';
if ( $font_slug && $font_slug !== $font_family_name && ! array_key_exists( $font_slug, $font_slug_aliases ) ) {
$font_slug_aliases[ $font_slug ] = $font_family_name;
}
}
}
return $font_slug_aliases;
}
/**
* Get the font family name from a font.
*
* @param array $font The font definition object.
*/
public static function get_font_family_name( $font ) {
$font_family = $font['fontFamily'];
if ( str_contains( $font_family, ',' ) ) {
$font_family = explode( ',', $font_family )[0];
}
return trim( $font_family, "\"'" );
}
/**
* Get the font family slug from a settings array.
*
* @param array $setting The settings object.
*
* @return string|null
*/
public function get_font_slug_from_setting( $setting ) {
if ( ! isset( $setting['typography']['fontFamily'] ) ) {
return null;
}
$font_family = $setting['typography']['fontFamily'];
// The font family may be a reference to a path to the value stored at that location,
// e.g.: { "ref": "styles.elements.heading.typography.fontFamily" }.
// Ignore it as we also get the value stored at that location from the setting.
if ( ! is_string( $font_family ) ) {
return null;
}
// Full string: var(--wp--preset--font-family--slug).
// We do not care about the origin of the font, only its slug.
preg_match( '/font-family--(?P<slug>.+)\)$/', $font_family, $matches );
if ( isset( $matches['slug'] ) ) {
return $matches['slug'];
}
// Full string: var:preset|font-family|slug
// We do not care about the origin of the font, only its slug.
preg_match( '/font-family\|(?P<slug>.+)$/', $font_family, $matches );
if ( isset( $matches['slug'] ) ) {
return $matches['slug'];
}
return $font_family;
}
/**
* Check if the current screen is the block editor.
*
* @return bool
*/
public function is_block_editor() {
if ( function_exists( 'get_current_screen' ) ) {
$current_screen = get_current_screen();
if ( ! empty( $current_screen ) && method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,248 @@
<?php
/**
* Load the google fonts by the new Font Library. See pNEWy-hhx-p2.
*
* @package automattic/jetpack
*/
if ( ! class_exists( 'Jetpack_Google_Font_Face' ) ) {
/**
* Load Jetpack Google Font Face
*/
require_once __DIR__ . '/class-jetpack-google-font-face.php';
}
/**
* Gets the Google Fonts data
*
* @return object[] The collection data of the Google Fonts.
*/
function jetpack_get_google_fonts_data() {
/**
* Filters the Google Fonts data before the default retrieval process.
*
* This filter allows short-circuiting the default Google Fonts data retrieval process.
* Returning a non-null value from this filter will bypass the default retrieval
* and return the filtered value instead.
*
* @module google-fonts
*
* @since 13.7
*
* @param null|array $pre The pre-filtered Google Fonts data, default null.
*/
$pre = apply_filters( 'pre_jetpack_get_google_fonts_data', null );
if ( null !== $pre ) {
return $pre;
}
$default_google_fonts_api_url = 'https://fonts.gstatic.com';
$jetpack_google_fonts_collection_url = 'https://s0.wp.com/i/font-collections/jetpack-google-fonts.json';
$cache_key = 'jetpack_google_fonts_' . md5( $jetpack_google_fonts_collection_url );
$data = get_transient( $cache_key );
if ( $data === false ) {
$response = wp_remote_get( $jetpack_google_fonts_collection_url );
if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
return null;
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( $data === null ) {
return null;
}
set_transient( $cache_key, $data, DAY_IN_SECONDS );
}
// Replace the google fonts api url if the custom one is provided.
$custom_google_fonts_api_url = \esc_url(
/**
* Filters the Google Fonts API URL.
*
* @module google-fonts
*
* @since 12.8
*
* @param string $url The Google Fonts API URL.
*/
apply_filters( 'jetpack_google_fonts_api_url', $default_google_fonts_api_url )
);
if ( $custom_google_fonts_api_url !== $default_google_fonts_api_url ) {
foreach ( $data['fontFamilies'] as &$font_family ) {
foreach ( $font_family['fontFace'] as &$font_face ) {
$font_face['src'] = str_replace(
$default_google_fonts_api_url,
$custom_google_fonts_api_url,
$font_face['src']
);
}
}
}
if ( is_array( $data ) && is_array( $data['fontFamilies'] ) ) {
return $data;
}
}
/**
* Gets the map of the available Google Fonts
*
* @param object[] $google_fonts_data The collection data of the Google Fonts.
* @return object[] The map of the the available Google Fonts.
*/
function jetpack_get_available_google_fonts_map( $google_fonts_data ) {
$jetpack_google_fonts_list = array_map(
function ( $font_family ) {
return $font_family['name'];
},
$google_fonts_data['fontFamilies']
);
/**
* Curated list of Google Fonts.
*
* @module google-fonts
*
* @since 10.8
*
* @param array $fonts_to_register Array of Google Font names to register.
*/
$google_font_list = apply_filters( 'jetpack_google_fonts_list', $jetpack_google_fonts_list );
$available_google_fonts_map = array();
foreach ( $google_font_list as $google_font ) {
$available_google_fonts_map[ $google_font ] = true;
}
return $available_google_fonts_map;
}
/**
* Register google fonts to the theme json data
*
* @param WP_Theme_JSON_Data $theme_json The theme json data of core.
* @return WP_Theme_JSON_Data The theme json data with registered google fonts.
*/
function jetpack_register_google_fonts_to_theme_json( $theme_json ) {
$google_fonts_data = jetpack_get_google_fonts_data();
if ( ! $google_fonts_data ) {
return $theme_json;
}
$available_google_fonts_map = jetpack_get_available_google_fonts_map( $google_fonts_data );
$google_fonts_families = array_values(
array_filter(
$google_fonts_data['fontFamilies'],
function ( $google_fonts_family ) use ( $available_google_fonts_map ) {
$name = $google_fonts_family['name'];
return $available_google_fonts_map[ $name ] ?? false;
}
)
);
$raw_data = $theme_json->get_data();
$origin = 'default';
if ( empty( $raw_data['settings']['typography']['fontFamilies'][ $origin ] ) ) {
$raw_data['settings']['typography']['fontFamilies'][ $origin ] = array();
}
foreach ( $google_fonts_families as $font_family ) {
$raw_data['settings']['typography']['fontFamilies'][ $origin ][] = $font_family;
}
$theme_json_class = get_class( $theme_json );
return new $theme_json_class( $raw_data, $origin );
}
add_filter( 'wp_theme_json_data_default', 'jetpack_register_google_fonts_to_theme_json' );
/**
* Filter out the deprecated font families that are from the jetpack-google-fonts provider.
*
* @param object[] $font_families The font families.
* @return object[] The filtered font families.
*/
function jetpack_google_fonts_filter_out_deprecated_font_data( $font_families ) {
return array_values(
array_filter(
$font_families,
function ( $font_family ) {
$has_deprecated_google_fonts_data = false;
if ( isset( $font_family['fontFace'] ) ) {
foreach ( $font_family['fontFace'] as $font_face ) {
$provider = $font_face['provider'] ?? '';
if ( $provider === 'jetpack-google-fonts' ) {
$has_deprecated_google_fonts_data = true;
break;
}
}
}
return ! $has_deprecated_google_fonts_data;
}
)
);
}
/**
* Unregister the deprecated jetpack-google-fonts provider from theme json data that were stored
* before we moved to the Font Library.
*
* @param WP_Theme_JSON_Data $theme_json The theme json data.
* @return WP_Theme_JSON_Data The filtered theme json data.
*/
function jetpack_unregister_deprecated_google_fonts_from_theme_json_data( $theme_json ) {
$raw_data = $theme_json->get_data();
$origin = 'theme';
if ( empty( $raw_data['settings']['typography']['fontFamilies'][ $origin ] ) ) {
return $theme_json;
}
// Filter out the font definitions that are from the jetpack-google-fonts provider.
$raw_data['settings']['typography']['fontFamilies'][ $origin ] = jetpack_google_fonts_filter_out_deprecated_font_data(
$raw_data['settings']['typography']['fontFamilies'][ $origin ]
);
$theme_json_class = get_class( $theme_json );
return new $theme_json_class( $raw_data, 'custom' );
}
add_filter( 'wp_theme_json_data_theme', 'jetpack_unregister_deprecated_google_fonts_from_theme_json_data' );
add_filter( 'wp_theme_json_data_user', 'jetpack_unregister_deprecated_google_fonts_from_theme_json_data' );
/**
* Clean up the Google Fonts data if either google fonts module is disabled or Jetpack is disabled.
*/
function jetpack_unregister_google_fonts() {
$post_id = WP_Theme_JSON_Resolver::get_user_global_styles_post_id();
// Get user config
$user_config = WP_Theme_JSON_Resolver::get_user_data();
$user_config_raw_data = $user_config->get_raw_data();
$user_config_raw_data['isGlobalStylesUserThemeJSON'] = true;
// Prepare data for saving
if ( ! empty( $user_config_raw_data['settings']['typography']['fontFamilies']['default'] ) ) {
$user_config_raw_data['settings']['typography']['fontFamilies']['default'] = array();
}
if ( ! empty( $user_config_raw_data['settings']['typography']['fontFamilies']['theme'] ) ) {
$user_config_raw_data['settings']['typography']['fontFamilies']['theme'] = jetpack_google_fonts_filter_out_deprecated_font_data(
$user_config_raw_data['settings']['typography']['fontFamilies']['theme'] // @phan-suppress-current-line PhanTypeInvalidDimOffset, PhanTypeMismatchArgument
);
}
// Prepare changes
$changes = new stdClass();
$changes->ID = $post_id;
$changes->post_content = wp_json_encode( $user_config_raw_data );
// Update user config
wp_update_post( wp_slash( (array) $changes ), true );
}
add_action( 'jetpack_deactivate_module_google-fonts', 'jetpack_unregister_google_fonts' );
// Initialize Jetpack Google Font Face to avoid printing **ALL** google fonts provided by this module.
// See p1700040028362329-slack-C4GAQ900P and p7DVsv-jib-p2
new Jetpack_Google_Font_Face();
@@ -0,0 +1,36 @@
<?php
/**
* Load the google fonts based on the current WordPress version.
*
* @package automattic/jetpack
*/
/**
* The modules is loaded during the late_initialization action and it also hooks to the `after_setup_theme`.
* See projects/plugins/jetpack/class.jetpack.php.
*/
add_action(
'after_setup_theme',
function () {
if (
/**
* Filters whether to skip loading the Jetpack Google Fonts module.
*
* This filter allows skipping the loading of the Jetpack Google Fonts module
* based on specific conditions or requirements. By default, the module will
* load normally. If the filter returns true, the module will be skipped.
*
* @module google-fonts
*
* @since 13.4
*
* @param bool $skip Whether to skip loading the Jetpack Google Fonts module. Default false.
*/
apply_filters( 'jetpack_google_fonts_skip_load', false )
) {
return;
}
require_once __DIR__ . '/current/load-google-fonts.php';
}
);
@@ -0,0 +1,401 @@
<?php
/**
* Module Name: Gravatar Hovercards
* Module Description: Enable pop-up business cards over commenters Gravatars.
* Sort Order: 11
* Recommendation Order: 13
* First Introduced: 1.1
* Requires Connection: No
* Auto Activate: No
* Module Tags: Social, Appearance
* Feature: Appearance
* Additional Search Queries: gravatar, hovercards
*
* @package automattic/jetpack
*/
define( 'GROFILES__CACHE_BUSTER', gmdate( 'YW' ) );
/**
* Actions that are run on init.
*/
function grofiles_hovercards_init() {
add_filter( 'get_avatar', 'grofiles_get_avatar', 10, 2 );
add_action( 'wp_enqueue_scripts', 'grofiles_attach_cards' );
add_action( 'wp_footer', 'grofiles_extra_data' );
add_action( 'admin_init', 'grofiles_add_settings' );
add_action( 'load-index.php', 'grofiles_admin_cards' );
add_action( 'load-users.php', 'grofiles_admin_cards' );
add_action( 'load-edit-comments.php', 'grofiles_admin_cards' );
add_action( 'load-options-discussion.php', 'grofiles_admin_cards_forced' );
add_filter( 'jetpack_module_configuration_url_gravatar-hovercards', 'gravatar_hovercards_configuration_url' );
add_filter( 'get_comment_author_url', 'grofiles_amp_comment_author_url', 10, 2 );
}
/**
* Set configuration page URL.
*/
function gravatar_hovercards_configuration_url() {
return admin_url( 'options-discussion.php#show_avatars' );
}
add_action( 'jetpack_modules_loaded', 'grofiles_hovercards_init' );
/* Hovercard Settings */
/**
* Adds Gravatar Hovercard setting
*
* @todo - always print HTML, hide via CSS/JS if !show_avatars
*/
function grofiles_add_settings() {
if ( ! get_option( 'show_avatars' ) ) {
return;
}
add_settings_field( 'gravatar_disable_hovercards', __( 'Gravatar Hovercards', 'jetpack' ), 'grofiles_setting_callback', 'discussion', 'avatars' );
register_setting( 'discussion', 'gravatar_disable_hovercards', 'grofiles_hovercard_option_sanitize' );
}
/**
* HTML for Gravatar Hovercard setting
*/
function grofiles_setting_callback() {
global $current_user;
$option = get_option( 'gravatar_disable_hovercards' );
printf(
"<label id='gravatar-hovercard-options'><input %s name='gravatar_disable_hovercards' id='gravatar_disable_hovercards' type='checkbox' value='enabled' class='code'/>%s</label>",
checked( $option, 'enabled', false ),
esc_html__( 'View people\'s profiles when you mouse over their Gravatars', 'jetpack' )
);
?>
<style type="text/css">
#grav-profile-example img {
float: left;
}
#grav-profile-example span {
padding: 0 1em;
}
</style>
<script type="text/javascript">
// <![CDATA[
jQuery( function($) {
var tr = $( '#gravatar_disable_hovercards' ).change( function() {
if ( $( this ).is( ':checked' ) ) {
$( '#grav-profile-example' ).slideDown( 'fast' );
} else {
$( '#grav-profile-example' ).slideUp( 'fast' );
}
} ).parents( 'tr' );
var ftr = tr.parents( 'table' ).find( 'tr:first' );
if ( ftr.length && !ftr.find( '#gravatar_disable_hovercards' ).length ) {
ftr.after( tr );
}
} );
// ]]>
</script>
<p id="grav-profile-example" class="hide-if-no-js"
<?php
if ( 'disabled' === $option ) {
echo ' style="display:none"';}
?>
>
<?php echo get_avatar( $current_user->ID, 64 ); ?> <span><?php esc_html_e( 'Put your mouse over your Gravatar to check out your profile.', 'jetpack' ); ?> <br class="clear" /></span></p>
<?php
}
/**
* Sanitation filter for Gravatar Hovercard setting
*
* @param string $val Disabled or enabled.
*/
function grofiles_hovercard_option_sanitize( $val ) {
if ( 'disabled' === $val ) {
return $val;
}
return $val ? 'enabled' : 'disabled';
}
/* Hovercard Display */
/**
* Stores the gravatars' users that need extra profile data attached.
*
* Getter/Setter
*
* @param int|string|null $author Setter: User ID or email address. Getter: null.
*
* @return mixed Setter: void. Getter: array of user IDs and email addresses.
*/
function grofiles_gravatars_to_append( $author = null ) {
static $authors = array();
// Get.
if ( $author === null ) {
return array_keys( $authors );
}
// Set.
if ( is_numeric( $author ) ) {
$author = (int) $author;
}
$authors[ $author ] = true;
}
/**
* In AMP, override the comment URL to allow for interactivity without
* navigating to a new page
*
* @param string $url The comment author's URL.
* @param int $id The comment ID.
*
* @return string The adjusted URL
*/
function grofiles_amp_comment_author_url( $url, $id ) {
if ( 'comment' === get_comment_type( $id ) && class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
// @todo Disabling the comment author link in this way is not ideal since clicking the link does not cause the lightbox to open in the same way as clicking the gravatar. Likely get_comment_author_url_link should be used instead so that the href attribute can be replaced with an `on` attribute that activates the gallery.
return '#!';
}
return $url;
}
/**
* Stores the user ID or email address for each gravatar generated.
*
* Attached to the 'get_avatar' filter.
*
* @param string $avatar The <img/> element of the avatar.
* @param mixed $author User ID, email address, user login, comment object, user object, post object.
*
* @return string The <img/> element of the avatar.
*/
function grofiles_get_avatar( $avatar, $author ) {
$is_amp = class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request();
if ( is_numeric( $author ) ) {
grofiles_gravatars_to_append( $author );
} elseif ( is_string( $author ) ) {
if ( str_contains( $author, '@' ) ) {
grofiles_gravatars_to_append( $author );
} else {
$user = get_user_by( 'slug', $author );
if ( $user ) {
grofiles_gravatars_to_append( $user->ID );
}
}
} elseif ( isset( $author->comment_type ) ) {
if ( $is_amp ) {
if ( 1 === preg_match( '/avatar\/([a-zA-Z0-9]+)\?/', $avatar, $email_hash ) ) {
$email_hash = $email_hash[1];
$cache_group = 'gravatar_profiles_';
$cache_key = 'gravatar_profile_' . $email_hash;
$response_body = wp_cache_get( $cache_key, $cache_group );
if ( false === $response_body ) {
$response = wp_remote_get( esc_url_raw( 'https://gravatar.com/' . $email_hash . '.json' ) );
if ( is_array( $response ) && ! is_wp_error( $response ) ) {
$response_body = json_decode( $response['body'] );
wp_cache_set( $cache_key, $response_body, $cache_group, 60 * MINUTE_IN_SECONDS );
}
}
$profile = isset( $response_body->entry[0] ) ? $response_body->entry[0] : null;
$display_name = isset( $profile->displayName ) ? $profile->displayName : ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$location = isset( $profile->currentLocation ) ? $profile->currentLocation : ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$description = isset( $profile->aboutMe ) ? $profile->aboutMe : ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$avatar = '
<figure data-amp-lightbox="true">
' . $avatar . '
<figcaption>
' . esc_html( $display_name ) . ( ! empty( $location ) ? ' ' . esc_html( $location ) : '' ) . ( ! empty( $description ) ? ' ' . esc_html( $description ) : '' ) . '
</figcaption>
</figure>
';
}
return $avatar;
}
if ( '' !== $author->comment_type && 'comment' !== $author->comment_type ) {
return $avatar;
}
if ( $author->user_id ) {
grofiles_gravatars_to_append( $author->user_id );
} else {
grofiles_gravatars_to_append( $author->comment_author_email );
}
} elseif ( isset( $author->user_login ) ) {
grofiles_gravatars_to_append( $author->ID );
} elseif ( isset( $author->post_author ) ) {
grofiles_gravatars_to_append( $author->post_author );
}
return $avatar;
}
/**
* Loads Gravatar Hovercard script.
*
* @todo is_singular() only?
*/
function grofiles_attach_cards() {
// Is the display of Avatars disabled?
if ( ! get_option( 'show_avatars' ) ) {
return;
}
// Is the display of Gravatar Hovercards disabled?
if ( 'disabled' === Jetpack_Options::get_option_and_ensure_autoload( 'gravatar_disable_hovercards', '0' ) ) {
return;
}
if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
wp_enqueue_style( 'gravatar-hovercard-style', plugins_url( '/gravatar/gravatar-hovercards-amp.css', __FILE__ ), array(), JETPACK__VERSION );
} else {
wp_enqueue_script( 'grofiles-cards', 'https://secure.gravatar.com/js/gprofiles.js', array(), GROFILES__CACHE_BUSTER, true );
wp_enqueue_script( 'wpgroho', plugins_url( 'wpgroho.js', __FILE__ ), array( 'grofiles-cards' ), JETPACK__VERSION, true );
if ( is_user_logged_in() ) {
$cu = wp_get_current_user();
$my_hash = md5( $cu->user_email );
} elseif ( ! empty( $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] ) ) {
$my_hash = md5( filter_var( wp_unslash( $_COOKIE[ 'comment_author_email_' . COOKIEHASH ] ) ) );
} else {
$my_hash = '';
}
wp_localize_script( 'wpgroho', 'WPGroHo', compact( 'my_hash' ) );
}
}
/**
* Add hovercards on Discussion settings panel.
*/
function grofiles_attach_cards_forced() {
add_filter( 'pre_option_gravatar_disable_hovercards', 'grofiles_force_gravatar_enable_hovercards' );
grofiles_attach_cards();
}
/**
* Set hovercards as enabled on Discussion settings panel.
*/
function grofiles_force_gravatar_enable_hovercards() {
return 'enabled';
}
/**
* Add script to admin footer on Discussion settings panel.
*/
function grofiles_admin_cards_forced() {
add_action( 'admin_footer', 'grofiles_attach_cards_forced' );
}
/**
* Add script to admin footer.
*/
function grofiles_admin_cards() {
add_action( 'admin_footer', 'grofiles_attach_cards' );
}
/**
* Dequeue the FE assets when there are no gravatars on the page to be displayed.
*/
function grofiles_extra_data() {
$authors = grofiles_gravatars_to_append();
if ( ! $authors ) {
wp_dequeue_script( 'grofiles-cards' );
wp_dequeue_script( 'wpgroho' );
} else {
?>
<div style="display:none">
<?php
foreach ( $authors as $author ) {
grofiles_hovercards_data_html( $author );
}
?>
</div>
<?php
}
}
/**
* Echoes the data from grofiles_hovercards_data() as HTML elements.
*
* @since 5.5.0 Add support for a passed WP_User object
*
* @param int|string|WP_User $author User ID, email address, or a WP_User object.
*/
function grofiles_hovercards_data_html( $author ) {
$data = grofiles_hovercards_data( $author );
$hash = '';
if ( is_numeric( $author ) ) {
$user = get_userdata( $author );
if ( $user ) {
$hash = md5( $user->user_email );
}
} elseif ( is_email( $author ) ) {
$hash = md5( $author );
} elseif ( is_a( $author, 'WP_User' ) ) {
$hash = md5( $author->user_email );
}
if ( ! $hash ) {
return;
}
?>
<div class="grofile-hash-map-<?php echo esc_attr( $hash ); ?>">
<?php foreach ( $data as $key => $value ) : ?>
<span class="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $value ); ?></span>
<?php endforeach; ?>
</div>
<?php
}
/* API */
/**
* Returns the PHP callbacks for data sources.
*
* 'grofiles_hovercards_data_callbacks' filter
*
* @return array<string,callable> ( data_key => data_callback, ... )
*/
function grofiles_hovercards_data_callbacks() {
/**
* Filter the Gravatar Hovercard PHP callbacks.
*
* @module gravatar-hovercards
*
* @since 1.1.0
*
* @param array $args Array of data callbacks.
*/
return apply_filters( 'grofiles_hovercards_data_callbacks', array() );
}
/**
* Keyed JSON object containing all profile data provided by registered callbacks
*
* @param int|string $author User ID or email address.
*
* @return array<string,mixed> ( data_key => data, ... )
*/
function grofiles_hovercards_data( $author ) {
$r = array();
foreach ( grofiles_hovercards_data_callbacks() as $key => $callback ) {
if ( ! is_callable( $callback ) ) {
continue;
}
$data = call_user_func( $callback, $author, $key );
if ( $data !== null ) {
$r[ $key ] = $data;
}
}
return $r;
}
@@ -0,0 +1,3 @@
.comment-author figcaption {
display: none;
}
@@ -0,0 +1,244 @@
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Module Name: Infinite Scroll
* Module Description: Automatically load new content when a visitor scrolls
* Sort Order: 26
* First Introduced: 2.0
* Requires Connection: No
* Auto Activate: No
* Module Tags: Appearance
* Feature: Appearance
* Additional Search Queries: scroll, infinite, infinite scroll
*/
use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
use Automattic\Jetpack\Stats\Options as Stats_Options;
/**
* Jetpack-specific elements of Infinite Scroll
*/
class Jetpack_Infinite_Scroll_Extras {
/**
* Class variable singleton.
*
* @var Jetpack_Infinite_Scroll_Extras
*/
private static $instance = null;
/**
* Option names.
*
* @var string
*/
private $option_name_google_analytics = 'infinite_scroll_google_analytics';
/**
* Singleton implementation
*
* @return object
*/
public static function instance() {
if ( ! self::$instance instanceof Jetpack_Infinite_Scroll_Extras ) {
self::$instance = new Jetpack_Infinite_Scroll_Extras();
}
return self::$instance;
}
/**
* Register actions and filters
*
* @uses add_action, add_filter
*/
private function __construct() {
add_action( 'jetpack_modules_loaded', array( $this, 'action_jetpack_modules_loaded' ) );
add_action( 'admin_init', array( $this, 'action_admin_init' ), 11 );
add_action( 'after_setup_theme', array( $this, 'action_after_setup_theme' ), 5 );
add_filter( 'infinite_scroll_js_settings', array( $this, 'filter_infinite_scroll_js_settings' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'action_wp_enqueue_scripts' ) );
}
/**
* Enable "Configure" button on module card
*
* @uses Jetpack::enable_module_configurable
* @action jetpack_modules_loaded
*/
public function action_jetpack_modules_loaded() {
Jetpack::enable_module_configurable( __FILE__ );
}
/**
* Register Google Analytics setting
*
* @uses add_settings_field, __, register_setting
* @action admin_init
*/
public function action_admin_init() {
if ( ! Jetpack_Plan::supports( 'google-analytics' ) ) {
return;
}
add_settings_field( $this->option_name_google_analytics, '<span id="infinite-scroll-google-analytics">' . __( 'Use Google Analytics with Infinite Scroll', 'jetpack' ) . '</span>', array( $this, 'setting_google_analytics' ), 'reading' );
register_setting( 'reading', $this->option_name_google_analytics, array( $this, 'sanitize_boolean_value' ) );
}
/**
* Render Google Analytics option
*
* @uses checked, get_option, __
*/
public function setting_google_analytics() {
echo '<label><input name="infinite_scroll_google_analytics" type="checkbox" value="1" ' . checked( true, (bool) get_option( $this->option_name_google_analytics, false ), false ) . ' /> ' . esc_html__( 'Track each scroll load (7 posts by default) as a page view in Google Analytics', 'jetpack' ) . '</label>';
echo '<p class="description">' . esc_html__( 'Check the box above to record each new set of posts loaded via Infinite Scroll as a page view in Google Analytics.', 'jetpack' ) . '</p>';
}
/**
* Sanitize value as a boolean
*
* @param mixed $value - the value we're sanitizing.
* @return bool
*/
public function sanitize_boolean_value( $value ) {
return (bool) $value;
}
/**
* Load theme's infinite scroll annotation file, if present in the IS plugin.
* The `setup_theme` action is used because the annotation files should be using `after_setup_theme` to register support for IS.
*
* As released in Jetpack 2.0, a child theme's parent wasn't checked for in the plugin's bundled support, hence the convoluted way the parent is checked for now.
*
* @uses is_admin, wp_get_theme, apply_filters
* @action setup_theme
* @return null
*/
public function action_after_setup_theme() {
$theme = wp_get_theme();
if ( ! $theme instanceof WP_Theme && ! is_array( $theme ) ) {
return;
}
/** This filter is already documented in modules/infinite-scroll/infinity.php */
$customization_file = apply_filters( 'infinite_scroll_customization_file', __DIR__ . "/infinite-scroll/themes/{$theme['Stylesheet']}.php", $theme['Stylesheet'] );
if ( is_readable( $customization_file ) ) {
require_once $customization_file;
} elseif ( ! empty( $theme['Template'] ) ) {
$customization_file = __DIR__ . "/infinite-scroll/themes/{$theme['Template']}.php";
if ( is_readable( $customization_file ) ) {
require_once $customization_file;
}
}
}
/**
* Modify Infinite Scroll configuration information
*
* @uses Jetpack::get_active_modules, is_user_logged_in, stats_get_options, Jetpack_Options::get_option, get_option, JETPACK__API_VERSION, JETPACK__VERSION
* @filter infinite_scroll_js_settings
*
* @param array $settings - the settings.
* @return array
*/
public function filter_infinite_scroll_js_settings( $settings ) {
// Provide WP Stats info for tracking Infinite Scroll loads
// Abort if Stats module isn't active
if ( in_array( 'stats', Jetpack::get_active_modules(), true ) ) {
// Abort if user is logged in but logged-in users shouldn't be tracked.
if ( is_user_logged_in() ) {
$stats_options = Stats_Options::get_options();
$track_loggedin_users = isset( $stats_options['count_roles'] ) ? (bool) $stats_options['count_roles'] : false;
if ( ! $track_loggedin_users ) {
return $settings;
}
}
// We made it this far, so gather the data needed to track IS views
$settings['stats'] = 'blog=' . Jetpack_Options::get_option( 'id' ) . '&host=' . wp_parse_url( get_option( 'home' ), PHP_URL_HOST ) . '&v=ext&j=' . JETPACK__API_VERSION . ':' . JETPACK__VERSION;
// Pagetype parameter
$settings['stats'] .= '&x_pagetype=infinite';
if ( 'click' === $settings['type'] ) {
$settings['stats'] .= '-click';
}
$settings['stats'] .= '-jetpack';
}
// Check if Google Analytics tracking is requested.
$settings['google_analytics'] = Jetpack_Plan::supports( 'google-analytics' ) && Jetpack_Options::get_option_and_ensure_autoload( $this->option_name_google_analytics, 0 );
return $settings;
}
/**
* Always load certain scripts when IS is enabled, as they can't be loaded after `document.ready` fires, meaning they can't leverage IS's script loader.
*
* @global $videopress
* @uses do_action()
* @uses apply_filters()
* @uses wp_enqueue_style()
* @uses wp_enqueue_script()
* @action wp_enqueue_scripts
* @return null
*/
public function action_wp_enqueue_scripts() {
// Do not load scripts and styles on singular pages and static pages
$load_scripts_and_styles = ! ( is_singular() || is_page() );
if (
/**
* Allow plugins to enqueue all Infinite Scroll scripts and styles on singular pages as well.
*
* @module infinite-scroll
*
* @since 3.1.0
*
* @param bool $load_scripts_and_styles Should scripts and styles be loaded on singular pahes and static pages. Default to false.
*/
! apply_filters( 'jetpack_infinite_scroll_load_scripts_and_styles', $load_scripts_and_styles )
) {
return;
}
// VideoPress stand-alone plugin
global $videopress;
if ( ! empty( $videopress ) && The_Neverending_Home_Page::archive_supports_infinity() && is_a( $videopress, 'VideoPress' ) && method_exists( $videopress, 'enqueue_scripts' ) ) {
$videopress->enqueue_scripts();
}
// VideoPress Jetpack module
if ( Jetpack::is_module_active( 'videopress' ) ) {
wp_enqueue_script( 'videopress' );
}
// Fire the post_gallery action early so Carousel scripts are present.
if ( Jetpack::is_module_active( 'carousel' ) ) {
/** This filter is already documented in core/wp-includes/media.php */
do_action( 'post_gallery', '', '', 0 );
}
// Always enqueue Tiled Gallery scripts when both IS and Tiled Galleries are enabled
if ( Jetpack::is_module_active( 'tiled-gallery' ) ) {
Jetpack_Tiled_Gallery::default_scripts_and_styles();
}
}
}
Jetpack_Infinite_Scroll_Extras::instance();
/**
* Load main IS file
*/
require_once __DIR__ . '/infinite-scroll/infinity.php';
/**
* Remove the IS annotation loading function bundled with the IS plugin in favor of the Jetpack-specific version in Jetpack_Infinite_Scroll_Extras::action_after_setup_theme();
*/
remove_action( 'after_setup_theme', 'the_neverending_home_page_theme_support', 5 );
@@ -0,0 +1,53 @@
( function ( $ ) {
/**
* Ready, set, go!
*/
$( document ).ready( function () {
// Integrate with Selective Refresh in the Customizer.
if ( 'undefined' !== typeof wp && wp.customize && wp.customize.selectiveRefresh ) {
/**
* Handle rendering of selective refresh partials.
*
* Make sure that when a partial is rendered, the Jetpack post-load event
* will be triggered so that any dynamic elements will be re-constructed,
* such as ME.js elements, Photon replacements, social sharing, and more.
* Note that this is applying here not strictly to posts being loaded.
* If a widget contains a ME.js element and it is previewed via selective
* refresh, the post-load would get triggered allowing any dynamic elements
* therein to also be re-constructed.
*
* @param {wp.customize.selectiveRefresh.Placement} placement
*/
wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function ( placement ) {
var content;
if ( 'string' === typeof placement.addedContent ) {
content = placement.addedContent;
} else if ( placement.container ) {
content = $( placement.container ).html();
}
if ( content ) {
$( document.body ).trigger( 'post-load', { html: content } );
}
} );
/*
* Add partials for posts added via infinite scroll.
*
* This is unnecessary when MutationObserver is supported by the browser
* since then this will be handled by Selective Refresh in core.
*/
if ( 'undefined' === typeof MutationObserver ) {
$( document.body ).on( 'post-load', function ( e, response ) {
var rootElement = null;
if ( response.html && -1 !== response.html.indexOf( 'data-customize-partial' ) ) {
if ( window.infiniteScroll.settings.id ) {
rootElement = $( '#' + window.infiniteScroll.settings.id );
}
wp.customize.selectiveRefresh.addPartials( rootElement );
}
} );
}
}
} );
} )( jQuery ); // Close closure
@@ -0,0 +1,278 @@
/* =Infinity Styles
-------------------------------------------------------------- */
.infinite-loader {
color: #000;
display: block;
height: 28px;
text-align: center;
}
#infinite-handle span {
background: #333;
border-radius: 1px;
color: #f0f0f1;
cursor: pointer;
font-size: 13px;
padding: 6px 16px;
}
/**
* CSS Spinner Styles
*/
@keyframes spinner-inner {
0% { opacity: 1 }
100% { opacity: 0 }
}
.infinite-loader .spinner-inner div {
left: 47px;
top: 24px;
position: absolute;
animation: spinner-inner linear 1s infinite;
background: #000000;
outline: 1px solid white;
width: 6px;
height: 12px;
border-radius: 3px / 6px;
transform-origin: 3px 26px;
}
.infinite-loader .spinner-inner div:nth-child(1) {
transform: rotate(0deg);
animation-delay: -0.9166666666666666s;
background: #000000;
}
.infinite-loader .spinner-inner div:nth-child(2) {
transform: rotate(30deg);
animation-delay: -0.8333333333333334s;
background: #000000;
}
.infinite-loader .spinner-inner div:nth-child(3) {
transform: rotate(60deg);
animation-delay: -0.75s;
background: #000000;
}
.infinite-loader .spinner-inner div:nth-child(4) {
transform: rotate(90deg);
animation-delay: -0.6666666666666666s;
background: #000000;
}
.infinite-loader .spinner-inner div:nth-child(5) {
transform: rotate(120deg);
animation-delay: -0.5833333333333334s;
background: #000000;
}
.infinite-loader .spinner-inner div:nth-child(6) {
transform: rotate(150deg);
animation-delay: -0.5s;
background: #000000;
}
.infinite-loader .spinner-inner div:nth-child(7) {
transform: rotate(180deg);
animation-delay: -0.4166666666666667s;
background: #000000;
}
.infinite-loader .spinner-inner div:nth-child(8) {
transform: rotate(210deg);
animation-delay: -0.3333333333333333s;
background: #000000;
}
.infinite-loader .spinner-inner div:nth-child(9) {
transform: rotate(240deg);
animation-delay: -0.25s;
background: #000000;
}
.infinite-loader .spinner-inner div:nth-child(10) {
transform: rotate(270deg);
animation-delay: -0.16666666666666666s;
background: #000000;
}
.infinite-loader .spinner-inner div:nth-child(11) {
transform: rotate(300deg);
animation-delay: -0.08333333333333333s;
background: #000000;
}
.infinite-loader .spinner-inner div:nth-child(12) {
transform: rotate(330deg);
animation-delay: 0s;
background: #000000;
}
.infinite-loader .spinner {
width: 28px;
height: 28px;
display: inline-block;
overflow: hidden;
background: none;
}
.infinite-loader .spinner-inner {
width: 100%;
height: 100%;
position: relative;
transform: translateZ(0) scale(0.28);
backface-visibility: hidden;
transform-origin: 0 0; /* see note above */
}
.infinite-loader .spinner-inner div {
box-sizing: content-box;
}
/**
* Using a highly-specific rule to make sure that all button styles
* will be reset
*/
#infinite-handle span button,
#infinite-handle span button:hover,
#infinite-handle span button:focus {
display: inline;
position: static;
padding: 0;
margin: 0;
border: none;
line-height: inherit;
background: transparent;
color: inherit;
cursor: inherit;
font-size: inherit;
font-weight: inherit;
font-family: inherit;
}
/**
* This is used to avoid unnecessary inner button spacing in Firefox
*/
#infinite-handle span button::-moz-focus-inner {
margin: 0;
padding: 0;
border: none;
}
/**
* For smaller viewports, remove the down-arrow icon and turn
* the button into a block element, spanning the content's full width.
*/
@media (max-width: 800px) {
#infinite-handle span:before {
display: none;
}
#infinite-handle span {
display: block;
}
}
/**
* Footer
*/
#infinite-footer {
position: fixed;
bottom: -50px;
left: 0;
width: 100%;
}
#infinite-footer a {
text-decoration: none;
}
#infinite-footer .blog-info a:hover,
#infinite-footer .blog-credits a:hover {
color: #444;
text-decoration: underline;
}
#infinite-footer .container {
background: rgba( 255, 255, 255, 0.8 );
border-color: #ccc;
border-color: rgba( 0, 0, 0, 0.1 );
border-style: solid;
border-width: 1px 0 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
margin: 0 auto;
overflow: hidden;
padding: 1px 20px;
width: 780px;
}
#infinite-footer .blog-info,
#infinite-footer .blog-credits {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
line-height: 25px;
}
#infinite-footer .blog-info {
float: left;
overflow: hidden;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
width: 40%;
}
#infinite-footer .blog-credits {
font-weight: normal;
float: right;
width: 60%;
}
#infinite-footer .blog-info a {
color: #111;
font-size: 14px;
font-weight: bold;
}
#infinite-footer .blog-credits {
color: #888;
font-size: 12px;
text-align: right;
}
#infinite-footer .blog-credits a {
color: #646970;
}
/**
* Hooks to infinity-end body class to restore footer
*/
.infinity-end.neverending #infinite-footer {
display: none;
}
/**
* Responsive structure for the footer
*/
@media (max-width: 640px) {
#infinite-footer .container {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
}
#infinite-footer .blog-info {
width: 30%;
}
#infinite-footer .blog-credits {
width: 70%;
}
#infinite-footer .blog-info a,
#infinite-footer .blog-credits {
font-size: 10px;
}
}
/**
* No fixed footer on small viewports
*/
@media ( max-width: 640px ) {
#infinite-footer {
position: static;
}
}
/**
* Hide infinite aria feedback visually
*/
#infinite-aria {
position: absolute;
overflow: hidden;
clip: rect(0 0 0 0);
height: 1px; width: 1px;
margin: -1px; padding: 0; border: 0;
}
/**
* Hide focus on infinite wrappers
*/
.infinite-wrap:focus {
outline: 0 !important;
}
@@ -0,0 +1,916 @@
/* globals infiniteScroll, _wpmejsSettings, ga, _gaq, WPCOM_sharing_counts, MediaElementPlayer */
( function () {
// Open closure.
// Local vars.
var Scroller, stats, type, text, totop, loading_text;
// IE requires special handling
var isIE = -1 !== navigator.userAgent.search( 'MSIE' );
if ( isIE ) {
var IEVersion = navigator.userAgent.match( /MSIE\s?(\d+)\.?\d*;/ );
IEVersion = parseInt( IEVersion[ 1 ] );
}
// HTTP ajaxurl when site is HTTPS causes Access-Control-Allow-Origin failure in Desktop and iOS Safari
if ( 'https:' === document.location.protocol ) {
infiniteScroll.settings.ajaxurl = infiniteScroll.settings.ajaxurl.replace(
'http://',
'https://'
);
}
/**
* Loads new posts when users scroll near the bottom of the page.
*/
Scroller = function ( settings ) {
var self = this;
// Initialize our variables
this.id = settings.id;
this.body = document.body;
this.window = window;
this.element = document.getElementById( settings.id );
this.wrapperClass = settings.wrapper_class;
this.ready = true;
this.disabled = false;
this.page = 1;
this.offset = settings.offset;
this.currentday = settings.currentday;
this.order = settings.order;
this.throttle = false;
this.click_handle = settings.click_handle;
this.google_analytics = settings.google_analytics;
this.history = settings.history;
this.origURL = window.location.href;
// Handle element
this.handle = document.createElement( 'div' );
this.handle.setAttribute( 'id', 'infinite-handle' );
this.handle.innerHTML = '<span><button>' + text.replace( '\\', '' ) + '</button></span>';
// Footer settings
this.footer = {
el: document.getElementById( 'infinite-footer' ),
wrap: settings.footer,
};
// Bind methods used as callbacks
this.checkViewportOnLoadBound = self.checkViewportOnLoad.bind( this );
// Core's native MediaElement.js implementation needs special handling
this.wpMediaelement = null;
// We have two type of infinite scroll
// cases 'scroll' and 'click'
if ( type === 'scroll' ) {
// Bind refresh to the scroll event
// Throttle to check for such case every 300ms
// On event the case becomes a fact
this.window.addEventListener( 'scroll', function () {
self.throttle = true;
} );
// Go back top method
self.gotop();
setInterval( function () {
if ( self.throttle ) {
// Once the case is the case, the action occurs and the fact is no more
self.throttle = false;
// Reveal or hide footer
self.thefooter();
// Fire the refresh
self.refresh();
self.determineURL(); // determine the url
}
}, 250 );
// Ensure that enough posts are loaded to fill the initial viewport, to compensate for short posts and large displays.
self.ensureFilledViewport();
this.body.addEventListener( 'is.post-load', self.checkViewportOnLoadBound );
} else if ( type === 'click' ) {
if ( this.click_handle ) {
this.element.appendChild( this.handle );
}
this.handle.addEventListener( 'click', function () {
// Handle the handle
if ( self.click_handle ) {
self.handle.parentNode.removeChild( self.handle );
}
// Fire the refresh
self.refresh();
} );
}
// Initialize any Core audio or video players loaded via IS
this.body.addEventListener( 'is.post-load', self.initializeMejs );
};
/**
* Normalize the access to the document scrollTop value.
*/
Scroller.prototype.getScrollTop = function () {
return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
};
/**
* Polyfill jQuery.extend.
*/
Scroller.prototype.extend = function ( out ) {
out = out || {};
for ( var i = 1; i < arguments.length; i++ ) {
if ( ! arguments[ i ] ) {
continue;
}
for ( var key in arguments[ i ] ) {
if ( Object.hasOwn( arguments[ i ], key ) ) {
out[ key ] = arguments[ i ][ key ];
}
}
}
return out;
};
/**
* Check whether we should fetch any additional posts.
*/
Scroller.prototype.check = function () {
var wrapperMeasurements = this.measure( this.element, [ this.wrapperClass ] );
// Fetch more posts when we're less than 2 screens away from the bottom.
return wrapperMeasurements.bottom < 2 * this.window.innerHeight;
};
/**
* Renders the results from a successful response.
*/
Scroller.prototype.render = function ( response ) {
var childrenToAppend = Array.prototype.slice.call( response.fragment.childNodes );
this.body.classList.add( 'infinity-success' );
// Render the retrieved nodes.
while ( childrenToAppend.length > 0 ) {
var currentNode = childrenToAppend.shift();
this.element.appendChild( currentNode );
}
this.trigger( this.body, 'is.post-load', {
jqueryEventName: 'post-load',
data: response,
} );
this.ready = true;
};
/**
* Returns the object used to query for new posts.
*/
Scroller.prototype.query = function () {
return {
page: this.page + this.offset, // Load the next page.
currentday: this.currentday,
order: this.order,
scripts: window.infiniteScroll.settings.scripts,
styles: window.infiniteScroll.settings.styles,
query_args: window.infiniteScroll.settings.query_args,
query_before: window.infiniteScroll.settings.query_before,
last_post_date: window.infiniteScroll.settings.last_post_date,
};
};
Scroller.prototype.animate = function ( cb, duration ) {
var start = performance.now();
requestAnimationFrame( function animate( time ) {
var timeFraction = Math.min( 1, ( time - start ) / duration );
cb( timeFraction );
if ( timeFraction < 1 ) {
requestAnimationFrame( animate );
}
} );
};
/**
* Scroll back to top.
*/
Scroller.prototype.gotop = function () {
var blog = document.getElementById( 'infinity-blog-title' );
var self = this;
if ( ! blog ) {
return;
}
blog.setAttribute( 'title', totop );
blog.addEventListener( 'click', function ( e ) {
var sourceScroll = self.window.pageYOffset;
e.preventDefault();
self.animate( function ( progress ) {
var currentScroll = sourceScroll - sourceScroll * progress;
document.documentElement.scrollTop = document.body.scrollTop = currentScroll;
}, 200 );
} );
};
/**
* The infinite footer.
*/
Scroller.prototype.thefooter = function () {
var self = this,
pageWrapper,
footerContainer,
width,
sourceBottom,
targetBottom,
footerEnabled = this.footer && this.footer.el;
if ( ! footerEnabled ) {
return;
}
// Check if we have an id for the page wrapper
if ( 'string' === typeof this.footer.wrap ) {
try {
pageWrapper = document.getElementById( this.footer.wrap );
width = pageWrapper.getBoundingClientRect();
width = width.width;
} catch {
width = 0;
}
// Make the footer match the width of the page
if ( width > 479 ) {
footerContainer = this.footer.el.querySelector( '.container' );
if ( footerContainer ) {
footerContainer.style.width = width + 'px';
}
}
}
// Reveal footer
sourceBottom = parseInt( self.footer.el.style.bottom || -50, 10 );
targetBottom = this.window.pageYOffset >= 350 ? 0 : -50;
if ( sourceBottom !== targetBottom ) {
self.animate( function ( progress ) {
var currentBottom = sourceBottom + ( targetBottom - sourceBottom ) * progress;
self.footer.el.style.bottom = currentBottom + 'px';
if ( 1 === progress ) {
sourceBottom = targetBottom;
}
}, 200 );
}
};
/**
* Recursively convert a JS object into URL encoded data.
*/
Scroller.prototype.urlEncodeJSON = function ( obj, prefix ) {
var params = [],
encodedKey,
newPrefix;
for ( var key in obj ) {
encodedKey = encodeURIComponent( key );
newPrefix = prefix ? prefix + '[' + encodedKey + ']' : encodedKey;
if ( 'object' === typeof obj[ key ] ) {
if ( ! Array.isArray( obj[ key ] ) || obj[ key ].length > 0 ) {
params.push( this.urlEncodeJSON( obj[ key ], newPrefix ) );
} else {
// Explicitly expose empty arrays with no values
params.push( newPrefix + '[]=' );
}
} else {
params.push( newPrefix + '=' + encodeURIComponent( obj[ key ] ) );
}
}
return params.join( '&' );
};
/**
* Controls the flow of the refresh. Don't mess.
*/
Scroller.prototype.refresh = function () {
var self = this,
query,
xhr,
loader,
customized;
// If we're disabled, ready, or don't pass the check, bail.
if ( this.disabled || ! this.ready || ! this.check() ) {
return;
}
// Let's get going -- set ready to false to prevent
// multiple refreshes from occurring at once.
this.ready = false;
// Create a loader element to show it's working.
if ( this.click_handle ) {
if ( ! loader ) {
document.getElementById( 'infinite-aria' ).textContent = loading_text;
loader = document.createElement( 'div' );
loader.classList.add( 'infinite-loader' );
loader.setAttribute( 'role', 'progress' );
loader.innerHTML =
'<div class="spinner"><div class="spinner-inner"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div></div>';
}
this.element.appendChild( loader );
}
// Generate our query vars.
query = self.extend(
{
action: 'infinite_scroll',
},
this.query()
);
// Inject Customizer state.
if ( 'undefined' !== typeof wp && wp.customize && wp.customize.settings.theme ) {
customized = {};
query.wp_customize = 'on';
query.theme = wp.customize.settings.theme.stylesheet;
wp.customize.each( function ( setting ) {
if ( setting._dirty ) {
customized[ setting.id ] = setting();
}
} );
query.customized = JSON.stringify( customized );
query.nonce = wp.customize.settings.nonce.preview;
}
// Fire the ajax request.
xhr = new XMLHttpRequest();
xhr.open( 'POST', infiniteScroll.settings.ajaxurl, true );
xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' );
xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8' );
xhr.send( self.urlEncodeJSON( query ) );
// Allow refreshes to occur again if an error is triggered.
xhr.onerror = function () {
if ( self.click_handle && loader.parentNode ) {
loader.parentNode.removeChild( loader );
}
self.ready = true;
};
// Success handler
xhr.onload = function () {
var response = JSON.parse( xhr.responseText ),
httpCheck = xhr.status >= 200 && xhr.status < 300,
responseCheck = 'undefined' !== typeof response.html;
if ( ! response || ! httpCheck || ! responseCheck ) {
if ( self.click_handle && loader.parentNode ) {
loader.parentNode.removeChild( loader );
}
return;
}
// On success, let's hide the loader circle.
if ( self.click_handle && loader.parentNode ) {
loader.parentNode.removeChild( loader );
}
// If additional scripts are required by the incoming set of posts, parse them
if ( response.scripts && Array.isArray( response.scripts ) ) {
response.scripts.forEach( function ( item ) {
var elementToAppendTo = item.footer ? 'body' : 'head';
// Add script handle to list of those already parsed
window.infiniteScroll.settings.scripts.push( item.handle );
// Output extra data, if present
if ( item.extra_data ) {
self.appendInlineScript( item.extra_data, elementToAppendTo );
}
if ( item.before_handle ) {
self.appendInlineScript( item.before_handle, elementToAppendTo );
}
// Build script tag and append to DOM in requested location
var script = document.createElement( 'script' );
script.type = 'text/javascript';
script.src = item.src;
script.id = item.handle;
// Dynamically loaded scripts are async by default.
// We don't want that, it breaks stuff, e.g. wp-mediaelement init.
script.async = false;
if ( item.after_handle ) {
script.onload = function () {
self.appendInlineScript( item.after_handle, elementToAppendTo );
};
}
// If MediaElement.js is loaded in by item set of posts, don't initialize the players a second time as it breaks them all
if ( 'wp-mediaelement' === item.handle ) {
self.body.removeEventListener( 'is.post-load', self.initializeMejs );
}
if ( 'wp-mediaelement' === item.handle && 'undefined' === typeof mejs ) {
self.wpMediaelement = {};
self.wpMediaelement.tag = script;
self.wpMediaelement.element = elementToAppendTo;
setTimeout( self.maybeLoadMejs.bind( self ), 250 );
} else {
document.getElementsByTagName( elementToAppendTo )[ 0 ].appendChild( script );
}
} );
}
// If additional stylesheets are required by the incoming set of posts, parse them
if ( response.styles && Array.isArray( response.styles ) ) {
response.styles.forEach( function ( item ) {
// Add stylesheet handle to list of those already parsed
window.infiniteScroll.settings.styles.push( item.handle );
// Build link tag
var style = document.createElement( 'link' );
style.rel = 'stylesheet';
style.href = item.src;
style.id = item.handle + '-css';
// Destroy link tag if a conditional statement is present and either the browser isn't IE, or the conditional doesn't evaluate true
if (
item.conditional &&
( ! isIE || ! eval( item.conditional.replace( /%ver/g, IEVersion ) ) )
) {
style = false;
}
// Append link tag if necessary
if ( style ) {
document.getElementsByTagName( 'head' )[ 0 ].appendChild( style );
}
} );
}
// Convert the response.html to a fragment element.
// Using a div instead of DocumentFragment, because the latter doesn't support innerHTML.
response.fragment = document.createElement( 'div' );
response.fragment.innerHTML = response.html;
// Increment the page number
self.page++;
// Record pageview in WP Stats, if available.
if ( stats ) {
new Image().src =
document.location.protocol +
'//pixel.wp.com/g.gif?' +
stats +
'&post=0&baba=' +
Math.random();
}
// Add new posts to the postflair object
if ( 'object' === typeof response.postflair && 'object' === typeof WPCOM_sharing_counts ) {
WPCOM_sharing_counts = self.extend( WPCOM_sharing_counts, response.postflair ); // eslint-disable-line no-global-assign
}
// Render the results
self.render.call( self, response );
// If 'click' type and there are still posts to fetch, add back the handle
if ( type === 'click' ) {
// add focus to new posts, only in button mode as we know where page focus currently is and only if we have a wrapper
if ( infiniteScroll.settings.wrapper ) {
document
.querySelector(
'#infinite-view-' + ( self.page + self.offset - 1 ) + ' a:first-of-type'
)
.focus( {
preventScroll: true,
} );
}
if ( response.lastbatch ) {
if ( self.click_handle ) {
// Update body classes
self.body.classList.add( 'infinity-end' );
self.body.classList.remove( 'infinity-success' );
} else {
self.trigger( this.body, 'infinite-scroll-posts-end' );
}
} else if ( self.click_handle ) {
self.element.appendChild( self.handle );
} else {
self.trigger( this.body, 'infinite-scroll-posts-more' );
}
} else if ( response.lastbatch ) {
self.disabled = true;
self.body.classList.add( 'infinity-end' );
self.body.classList.remove( 'infinity-success' );
}
// Update currentday to the latest value returned from the server
if ( response.currentday ) {
self.currentday = response.currentday;
}
// Fire Google Analytics pageview
if ( self.google_analytics ) {
var ga_url = self.history.path.replace( /%d/, self.page );
if ( 'object' === typeof _gaq ) {
_gaq.push( [ '_trackPageview', ga_url ] );
}
if ( 'function' === typeof ga ) {
ga( 'send', 'pageview', ga_url );
}
}
};
return xhr;
};
/**
* Given JavaScript blob and the name of a parent tag, this helper function will
* generate a script tag, insert the JavaScript blob, and append it to the parent.
*
* It's important to note that the JavaScript blob will be evaluated immediately. If
* you need a parent script to load first, use that script element's onload handler.
*
* @param {string} script The blob of JavaScript to run.
* @param {string} parentTag The tag name of the parent element.
*/
Scroller.prototype.appendInlineScript = function ( script, parentTag ) {
var element = document.createElement( 'script' ),
scriptContent = document.createTextNode( '//<![CDATA[ \n' + script + '\n//]]>' );
element.type = 'text/javascript';
element.appendChild( scriptContent );
document.getElementsByTagName( parentTag )[ 0 ].appendChild( element );
};
/**
* Core's native media player uses MediaElement.js
* The library's size is sufficient that it may not be loaded in time for Core's helper to invoke it, so we need to delay until `mejs` exists.
*/
Scroller.prototype.maybeLoadMejs = function () {
if ( null === this.wpMediaelement ) {
return;
}
if ( 'undefined' === typeof mejs ) {
setTimeout( this.maybeLoadMejs.bind( this ), 250 );
} else {
document
.getElementsByTagName( this.wpMediaelement.element )[ 0 ]
.appendChild( this.wpMediaelement.tag );
this.wpMediaelement = null;
// Ensure any subsequent IS loads initialize the players
this.body.addEventListener( 'is.post-load', this.initializeMejs );
}
};
/**
* Initialize the MediaElement.js player for any posts not previously initialized
*/
Scroller.prototype.initializeMejs = function ( e ) {
// Are there media players in the incoming set of posts?
if (
! e.detail ||
! e.detail.html ||
( -1 === e.detail.html.indexOf( 'wp-audio-shortcode' ) &&
-1 === e.detail.html.indexOf( 'wp-video-shortcode' ) )
) {
return;
}
// Don't bother if mejs isn't loaded for some reason
if ( 'undefined' === typeof mejs ) {
return;
}
// Adapted from wp-includes/js/mediaelement/wp-mediaelement.js
// Modified to not initialize already-initialized players, as Mejs doesn't handle that well
var settings = {};
var audioVideoElements;
if ( typeof _wpmejsSettings !== 'undefined' ) {
settings.pluginPath = _wpmejsSettings.pluginPath;
}
settings.success = function ( mejs ) {
var autoplay = mejs.attributes.autoplay && 'false' !== mejs.attributes.autoplay;
if ( 'flash' === mejs.pluginType && autoplay ) {
mejs.addEventListener(
'canplay',
function () {
mejs.play();
},
false
);
}
};
audioVideoElements = document.querySelectorAll( '.wp-audio-shortcode, .wp-video-shortcode' );
audioVideoElements = Array.prototype.slice.call( audioVideoElements );
// Only process already unprocessed shortcodes.
audioVideoElements = audioVideoElements.filter( function ( el ) {
while ( el.parentNode ) {
if ( el.classList.contains( 'mejs-container' ) ) {
return false;
}
el = el.parentNode;
}
return true;
} );
for ( var i = 0; i < audioVideoElements.length; i++ ) {
new MediaElementPlayer( audioVideoElements[ i ], settings );
}
};
/**
* Get element measurements relative to the viewport.
*
* @return {object}
*/
Scroller.prototype.measure = function ( element, expandClasses ) {
expandClasses = expandClasses || [];
var childrenToTest = Array.prototype.slice.call( element.children );
var currentChild,
minTop = Number.MAX_VALUE,
maxBottom = 0,
currentChildRect,
i;
while ( childrenToTest.length > 0 ) {
currentChild = childrenToTest.shift();
for ( i = 0; i < expandClasses.length; i++ ) {
// Expand (= measure) child elements of nodes with class names from expandClasses.
if ( currentChild.classList.contains( expandClasses[ i ] ) ) {
childrenToTest = childrenToTest.concat(
Array.prototype.slice.call( currentChild.children )
);
break;
}
}
currentChildRect = currentChild.getBoundingClientRect();
minTop = Math.min( minTop, currentChildRect.top );
maxBottom = Math.max( maxBottom, currentChildRect.bottom );
}
var viewportMiddle = Math.round( window.innerHeight / 2 );
// isActive = does the middle of the viewport cross the element?
var isActive = minTop <= viewportMiddle && maxBottom >= viewportMiddle;
/**
* Factor = percentage of viewport above the middle line occupied by the element.
*
* Negative factors are assigned for elements below the middle line. That's on purpose
* to only allow "page 2" to change the URL once it's in the middle of the viewport.
*/
var factor = ( Math.min( maxBottom, viewportMiddle ) - Math.max( minTop, 0 ) ) / viewportMiddle;
return {
top: minTop,
bottom: maxBottom,
height: maxBottom - minTop,
factor: factor,
isActive: isActive,
};
};
/**
* Trigger IS to load additional posts if the initial posts don't fill the window.
*
* On large displays, or when posts are very short, the viewport may not be filled with posts,
* so we overcome this by loading additional posts when IS initializes.
*/
Scroller.prototype.ensureFilledViewport = function () {
var self = this,
windowHeight = self.window.innerHeight,
wrapperMeasurements = self.measure( self.element, [ self.wrapperClass ] );
// Only load more posts once. This prevents infinite loops when there are no more posts.
self.body.removeEventListener( 'is.post-load', self.checkViewportOnLoadBound );
// Load more posts if space permits, otherwise stop checking for a full viewport.
if ( wrapperMeasurements.bottom !== 0 && wrapperMeasurements.bottom < windowHeight ) {
self.ready = true;
self.refresh();
}
};
/**
* Event handler for ensureFilledViewport(), tied to the post-load trigger.
* Necessary to ensure that the variable `this` contains the scroller when used in ensureFilledViewport(). Since this function is tied to an event, `this` becomes the DOM element the event is tied to.
*/
Scroller.prototype.checkViewportOnLoad = function () {
this.ensureFilledViewport();
};
function fullscreenState() {
return document.fullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement ||
document.msFullscreenElement
? 1
: 0;
}
var previousFullScrenState = fullscreenState();
/**
* Identify archive page that corresponds to majority of posts shown in the current browser window.
*/
Scroller.prototype.determineURL = function () {
var self = this,
pageNum = -1,
currentFullScreenState = fullscreenState(),
wrapperEls,
maxFactor = 0;
// xor - check if the state has changed
// eslint-disable-next-line no-bitwise
if ( previousFullScrenState ^ currentFullScreenState ) {
// If we just switched to/from fullscreen,
// don't do the div clearing/caching or the
// URL setting. Doing so can break video playback
// if the video goes to fullscreen.
previousFullScrenState = currentFullScreenState;
return;
}
previousFullScrenState = currentFullScreenState;
wrapperEls = document.querySelectorAll( '.' + self.wrapperClass );
for ( var i = 0; i < wrapperEls.length; i++ ) {
var setMeasurements = self.measure( wrapperEls[ i ] );
// If it exists, pick a set that is crossed by the middle of the viewport.
if ( setMeasurements.isActive ) {
pageNum = parseInt( wrapperEls[ i ].dataset.pageNum, 10 );
break;
}
// If there is such a set, pick the one that occupies the most space
// above the middle of the viewport.
if ( setMeasurements.factor > maxFactor ) {
pageNum = parseInt( wrapperEls[ i ].dataset.pageNum, 10 );
maxFactor = setMeasurements.factor;
}
// Otherwise default to -1
}
self.updateURL( pageNum );
};
/**
* Update address bar to reflect archive page URL for a given page number.
* Checks if URL is different to prevent pollution of browser history.
*/
Scroller.prototype.updateURL = function ( page ) {
// IE only supports pushState() in v10 and above, so don't bother if those conditions aren't met.
if ( ! window.history.pushState ) {
return;
}
var self = this,
pageSlug = self.origURL;
if ( -1 !== page ) {
pageSlug =
window.location.protocol +
'//' +
self.history.host +
self.history.path.replace( /%d/, page ) +
self.history.parameters;
}
if ( window.location.href !== pageSlug ) {
history.pushState( null, null, pageSlug );
}
};
/**
* Pause scrolling.
*/
Scroller.prototype.pause = function () {
this.disabled = true;
};
/**
* Resume scrolling.
*/
Scroller.prototype.resume = function () {
this.disabled = false;
};
/**
* Emits custom JS events.
*
* @param {Node} el
* @param {string} eventName
* @param {*} data
*/
Scroller.prototype.trigger = function ( el, eventName, opts ) {
opts = opts || {};
/**
* Emit the event in a jQuery way for backwards compatibility where necessary.
*/
if ( opts.jqueryEventName && 'undefined' !== typeof jQuery ) {
jQuery( el ).trigger( opts.jqueryEventName, opts.data || null );
}
/**
* Emit the event in a standard way.
*/
var e;
try {
e = new CustomEvent( eventName, {
bubbles: true,
cancelable: true,
detail: opts.data || null,
} );
} catch {
e = document.createEvent( 'CustomEvent' );
e.initCustomEvent( eventName, true, true, opts.data || null );
}
el.dispatchEvent( e );
};
/**
* Ready, set, go!
*/
var jetpackInfinityModule = function () {
// Check for our variables
if ( 'object' !== typeof infiniteScroll ) {
return;
}
var bodyClasses = infiniteScroll.settings.body_class.split( ' ' );
bodyClasses.forEach( function ( className ) {
if ( className ) {
document.body.classList.add( className );
}
} );
// Set stats, used for tracking stats
stats = infiniteScroll.settings.stats;
// Define what type of infinity we have, grab text for click-handle
type = infiniteScroll.settings.type;
text = infiniteScroll.settings.text;
totop = infiniteScroll.settings.totop;
// aria text
loading_text = infiniteScroll.settings.loading_text;
// Initialize the scroller (with the ID of the element from the theme)
infiniteScroll.scroller = new Scroller( infiniteScroll.settings );
/**
* Monitor user scroll activity to update URL to correspond to archive page for current set of IS posts
*/
if ( type === 'click' ) {
var timer = null;
window.addEventListener( 'scroll', function () {
// run the real scroll handler once every 250 ms.
if ( timer ) {
return;
}
timer = setTimeout( function () {
infiniteScroll.scroller.determineURL();
timer = null;
}, 250 );
} );
}
};
/**
* Ready, set, go!
*/
if ( document.readyState === 'interactive' || document.readyState === 'complete' ) {
jetpackInfinityModule();
} else {
document.addEventListener( 'DOMContentLoaded', jetpackInfinityModule );
}
} )(); // Close closure
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,45 @@
/* =Infinity Styles
-------------------------------------------------------------- */
.infinite-scroll #main:after {
clear: both;
content: '';
display: block;
}
.infinite-scroll #content {
margin-bottom: 40px;
}
.infinite-scroll.neverending #content {
margin-bottom: 70px;
}
.infinite-scroll .infinite-wrap {
border-top: none;
padding-top: 0;
}
.infinite-scroll .infinite-wrap .hentry:last-child {
border-bottom: 1px solid #dcdcde;
}
.infinite-scroll .infinite-wrap:last-of-type .hentry:last-child {
border-bottom: none;
}
/**
* Elements to hide:
* (footer widgets, post navigation, regular footer)
*/
.infinite-scroll.neverending #colophon #supplementary,
.infinite-scroll #nav-below,
.infinite-scroll.neverending #colophon {
display: none;
}
/* Hooks to infinity-end body class to restore footer */
.infinity-end.neverending #colophon {
display: block;
}
/* For responsive CSS */
@media (max-width: 800px) {
.infinite-scroll #infinite-handle {
padding-bottom: 40px;
}
}
@@ -0,0 +1,51 @@
<?php
/**
* Infinite Scroll Theme Assets
*
* Register support for @Twenty Eleven and enqueue relevant styles.
*
* @package jetpack
*/
/**
* Add theme support for infinity scroll
*/
function jetpack_twentyeleven_infinite_scroll_init() {
add_theme_support(
'infinite-scroll',
array(
'container' => 'content',
'footer' => 'page',
'footer_widgets' => jetpack_twentyeleven_has_footer_widgets(),
)
);
}
add_action( 'init', 'jetpack_twentyeleven_infinite_scroll_init' );
/**
* Enqueue CSS stylesheet with theme styles for infinity.
*/
function jetpack_twentyeleven_infinite_scroll_enqueue_styles() {
if ( wp_script_is( 'the-neverending-homepage' ) ) {
// Add theme specific styles.
wp_enqueue_style( 'infinity-twentyeleven', plugins_url( 'twentyeleven.css', __FILE__ ), array( 'the-neverending-homepage' ), '20121002' );
}
}
add_action( 'wp_enqueue_scripts', 'jetpack_twentyeleven_infinite_scroll_enqueue_styles', 25 );
/**
* Do we have footer widgets?
*/
function jetpack_twentyeleven_has_footer_widgets() {
// Are any of the "Footer Area" sidebars active?
if ( is_active_sidebar( 'sidebar-3' ) || is_active_sidebar( 'sidebar-4' ) || is_active_sidebar( 'sidebar-5' ) ) {
return true;
}
// If we're on mobile and the Main Sidebar has widgets, it falls below the content, so we have footer widgets.
if ( function_exists( 'jetpack_is_mobile' ) && jetpack_is_mobile() && is_active_sidebar( 'sidebar-1' ) ) {
return true;
}
return false;
}
@@ -0,0 +1,216 @@
/**
* Infinite Scroll
*/
.infinite-scroll .pagination,
.infinite-scroll.neverending .site-footer {
display: none;
}
.infinity-end.neverending .site-footer {
display: block;
}
/* Spinner */
.infinite-loader {
clear: both;
height: 24px;
margin: 24px 0;
}
.infinite-loader .spinner {
top: 50% !important;
right: 50% !important;
}
/* Click-to-load */
#infinite-handle {
clear: both;
margin: 7.6923%;
text-align: center;
}
#infinite-handle span {
background-color: #333;
font-family: "Noto Sans", sans-serif;
font-size: 12px;
font-size: 1.2rem;
font-weight: 700;
letter-spacing: 0.04em;
line-height: normal;
padding: 0.7917em;
text-transform: uppercase;
}
#infinite-handle span:hover,
#infinite-handle span:focus {
background-color: #707070;
background-color: rgba(51, 51, 51, 0.7);
color: #fff;
}
/* Footer */
#infinite-footer {
display: none;
z-index: 999;
}
#infinite-footer .container {
background-color: #fff;
background-color: rgba(255, 255, 255, 0.5);
border-color: #eaeaea;
border-color: rgba(51, 51, 51, 0.1);
padding: 0 0.8em;
width: 100% !important;
}
#infinite-footer .blog-info {
font-family: "Noto Sans", sans-serif;
}
#infinite-footer .blog-info,
#infinite-footer .blog-credits {
height: 24px;
line-height: 24px;
}
#infinite-footer .blog-info a,
#infinite-footer .blog-credits {
font-size: 12px;
font-size: 1.2rem;
}
#infinite-footer .blog-info a,
#infinite-footer .blog-credits a:hover,
#infinite-footer .blog-credits a:focus {
color: #333;
}
#infinite-footer .blog-info a:hover,
#infinite-footer .blog-info a:focus,
#infinite-footer .blog-credits,
#infinite-footer .blog-credits a {
color: #707070;
color: rgba(51, 51, 51, 0.7);
}
#infinite-footer .blog-info a:hover,
#infinite-footer .blog-info a:focus,
#infinite-footer .blog-credits a:hover,
#infinite-footer .blog-credits a:focus {
text-decoration: none;
}
@media screen and (min-width: 38.75em) {
.infinite-loader {
margin: 7.6923% 0;
}
.infinite-wrap {
margin-top: 7.6923%;
}
#infinite-handle {
margin-bottom: 0;
}
}
@media screen and (min-width: 46.25em) {
#infinite-handle span {
display: block;
font-size: 14px;
font-size: 1.4rem;
padding: 0.8214em
}
}
@media screen and (min-width: 55em) {
#infinite-handle span {
font-size: 16px;
font-size: 1.6rem;
padding: 0.8125em;
}
}
@media screen and (min-width: 59.6875em) {
.infinite-loader {
margin: 8.3333% 0;
}
.infinite-wrap {
margin-top: 8.3333%;
}
#infinite-handle {
margin: 8.3333% 8.3333% 0;
}
#infinite-handle span {
display: inline-block;
font-size: 12px;
font-size: 1.2rem;
padding: 0.7917em 1.5833em;
}
#infinite-footer {
display: block;
position: fixed;
}
}
@media screen and (min-width: 68.75em) {
#infinite-handle span {
display: inline-block;
font-size: 14px;
font-size: 1.4rem;
padding: 0.8214em 1.5714em;
}
#infinite-footer .container {
padding: 0 0.8235em;
}
#infinite-footer .blog-info,
#infinite-footer .blog-credits {
height: 27px;
line-height: 27px;
}
#infinite-footer .blog-info a {
font-size: 14px;
font-size: 1.4rem;
}
#infinite-footer .blog-credits {
font-size: 12px;
font-size: 1.2rem;
}
}
@media screen and (min-width: 77.5em) {
#infinite-handle span {
font-size: 16px;
font-size: 1.6rem;
padding: 0.8125em 1.625em;
}
#infinite-footer .container {
padding: 0 0.8421em;
}
#infinite-footer .blog-info,
#infinite-footer .blog-credits {
height: 32px;
line-height: 32px;
}
#infinite-footer .blog-info a {
font-size: 16px;
font-size: 1.6rem;
}
#infinite-footer .blog-credits {
font-size: 13px;
font-size: 1.3rem;
}
}
@@ -0,0 +1,216 @@
/**
* Infinite Scroll
*/
.infinite-scroll .pagination,
.infinite-scroll.neverending .site-footer {
display: none;
}
.infinity-end.neverending .site-footer {
display: block;
}
/* Spinner */
.infinite-loader {
clear: both;
height: 24px;
margin: 24px 0;
}
.infinite-loader .spinner {
top: 50% !important;
left: 50% !important;
}
/* Click-to-load */
#infinite-handle {
clear: both;
margin: 7.6923%;
text-align: center;
}
#infinite-handle span {
background-color: #333;
font-family: "Noto Sans", sans-serif;
font-size: 12px;
font-size: 1.2rem;
font-weight: 700;
letter-spacing: 0.04em;
line-height: normal;
padding: 0.7917em;
text-transform: uppercase;
}
#infinite-handle span:hover,
#infinite-handle span:focus {
background-color: #707070;
background-color: rgba(51, 51, 51, 0.7);
color: #fff;
}
/* Footer */
#infinite-footer {
display: none;
z-index: 999;
}
#infinite-footer .container {
background-color: #fff;
background-color: rgba(255, 255, 255, 0.5);
border-color: #eaeaea;
border-color: rgba(51, 51, 51, 0.1);
padding: 0 0.8em;
width: 100% !important;
}
#infinite-footer .blog-info {
font-family: "Noto Sans", sans-serif;
}
#infinite-footer .blog-info,
#infinite-footer .blog-credits {
height: 24px;
line-height: 24px;
}
#infinite-footer .blog-info a,
#infinite-footer .blog-credits {
font-size: 12px;
font-size: 1.2rem;
}
#infinite-footer .blog-info a,
#infinite-footer .blog-credits a:hover,
#infinite-footer .blog-credits a:focus {
color: #333;
}
#infinite-footer .blog-info a:hover,
#infinite-footer .blog-info a:focus,
#infinite-footer .blog-credits,
#infinite-footer .blog-credits a {
color: #707070;
color: rgba(51, 51, 51, 0.7);
}
#infinite-footer .blog-info a:hover,
#infinite-footer .blog-info a:focus,
#infinite-footer .blog-credits a:hover,
#infinite-footer .blog-credits a:focus {
text-decoration: none;
}
@media screen and (min-width: 38.75em) {
.infinite-loader {
margin: 7.6923% 0;
}
.infinite-wrap {
margin-top: 7.6923%;
}
#infinite-handle {
margin-bottom: 0;
}
}
@media screen and (min-width: 46.25em) {
#infinite-handle span {
display: block;
font-size: 14px;
font-size: 1.4rem;
padding: 0.8214em
}
}
@media screen and (min-width: 55em) {
#infinite-handle span {
font-size: 16px;
font-size: 1.6rem;
padding: 0.8125em;
}
}
@media screen and (min-width: 59.6875em) {
.infinite-loader {
margin: 8.3333% 0;
}
.infinite-wrap {
margin-top: 8.3333%;
}
#infinite-handle {
margin: 8.3333% 8.3333% 0;
}
#infinite-handle span {
display: inline-block;
font-size: 12px;
font-size: 1.2rem;
padding: 0.7917em 1.5833em;
}
#infinite-footer {
display: block;
position: fixed;
}
}
@media screen and (min-width: 68.75em) {
#infinite-handle span {
display: inline-block;
font-size: 14px;
font-size: 1.4rem;
padding: 0.8214em 1.5714em;
}
#infinite-footer .container {
padding: 0 0.8235em;
}
#infinite-footer .blog-info,
#infinite-footer .blog-credits {
height: 27px;
line-height: 27px;
}
#infinite-footer .blog-info a {
font-size: 14px;
font-size: 1.4rem;
}
#infinite-footer .blog-credits {
font-size: 12px;
font-size: 1.2rem;
}
}
@media screen and (min-width: 77.5em) {
#infinite-handle span {
font-size: 16px;
font-size: 1.6rem;
padding: 0.8125em 1.625em;
}
#infinite-footer .container {
padding: 0 0.8421em;
}
#infinite-footer .blog-info,
#infinite-footer .blog-credits {
height: 32px;
line-height: 32px;
}
#infinite-footer .blog-info a {
font-size: 16px;
font-size: 1.6rem;
}
#infinite-footer .blog-credits {
font-size: 13px;
font-size: 1.3rem;
}
}
@@ -0,0 +1,33 @@
<?php
/**
* Infinite Scroll Theme Assets
*
* Register support for Twenty Fifteen.
*
* @package jetpack
*/
/**
* Add theme support for infinite scroll
*/
function jetpack_twentyfifteen_infinite_scroll_init() {
add_theme_support(
'infinite-scroll',
array(
'container' => 'main',
'footer' => 'page',
)
);
}
add_action( 'after_setup_theme', 'jetpack_twentyfifteen_infinite_scroll_init' );
/**
* Enqueue CSS stylesheet with theme styles for Infinite Scroll.
*/
function jetpack_twentyfifteen_infinite_scroll_enqueue_styles() {
if ( wp_script_is( 'the-neverending-homepage' ) ) {
wp_enqueue_style( 'infinity-twentyfifteen', plugins_url( 'twentyfifteen.css', __FILE__ ), array( 'the-neverending-homepage' ), '20141022' );
wp_style_add_data( 'infinity-twentyfifteen', 'rtl', 'replace' );
}
}
add_action( 'wp_enqueue_scripts', 'jetpack_twentyfifteen_infinite_scroll_enqueue_styles', 25 );
@@ -0,0 +1,111 @@
/* Spinner */
.infinite-loader {
height: 36px;
padding: 24px 0;
}
.infinite-loader .spinner {
margin: 0 auto;
top: 18px !important;
left: 0 !important;
}
.rtl .infinite-loader .spinner {
right: 0 !important;
left: auto !important;
}
/* Click-to-load */
#infinite-handle {
padding: 24px 0;
text-align: center;
}
#infinite-handle span {
background: #24890d;
border-radius: 2px;
display: inline-block;
color: #fff;
font-size: 12px;
font-weight: 700;
line-height: 1;
padding: 12px 30px;
text-transform: uppercase;
}
#infinite-handle span:hover {
background-color: #41a62a;
}
#infinite-handle span:active {
background-color: #55d737;
}
/* Footer */
#infinite-footer {
z-index: 2;
}
#infinite-footer .container {
margin: 0;
padding: 4px 20px;
}
#infinite-footer .blog-info a {
color: #2b2b2b;
}
#infinite-footer .blog-credits,
#infinite-footer .blog-credits a {
color: #767676;
}
#infinite-footer .blog-info a:hover,
#infinite-footer .blog-credits a:hover {
color: #41a62a;
text-decoration: none;
}
/* Elements to hide: post navigation, normal footer. */
.infinite-scroll .paging-navigation,
.infinite-scroll.neverending #colophon {
display: none;
}
@media (max-width: 640px) {
#infinite-footer {
display: none;
}
}
/* Hooks to infinity-end body class to restore footer. */
.infinity-end.neverending #colophon {
display: block;
}
/* Reset top margin adjustment for subsequent posts added by Infinite Scroll */
.full-width .site-content .infinite-wrap .hentry.has-post-thumbnail:first-child {
margin-top: 0;
}
@media screen and (min-width: 401px) {
.infinite-loader,
#infinite-handle {
padding: 0 0 48px;
}
.list-view .site-content .infinite-wrap .hentry:first-of-type {
border-top: 1px solid rgba(0, 0, 0, 0.1);
padding-top: 48px;
}
.list-view .site-content .infinite-wrap .hentry.has-post-thumbnail {
border-top: 0;
padding-top: 0;
}
}
@@ -0,0 +1,56 @@
<?php
/**
* Infinite Scroll Theme Assets
*
* Register support for Twenty Fourteen.
*
* @package jetpack
*/
use Automattic\Jetpack\Device_Detection\User_Agent_Info;
/**
* Add theme support for infinite scroll
*/
function jetpack_twentyfourteen_infinite_scroll_init() {
add_theme_support(
'infinite-scroll',
array(
'container' => 'content',
'footer' => 'page',
'footer_widgets' => jetpack_twentyfourteen_has_footer_widgets(),
)
);
}
add_action( 'after_setup_theme', 'jetpack_twentyfourteen_infinite_scroll_init' );
/**
* Switch to the "click to load" type IS with the following cases
* 1. Viewed from iPad and the primary sidebar is active.
* 2. Viewed from mobile and either the primary or the content sidebar is active.
* 3. The footer widget is active.
*
* @return bool
*/
function jetpack_twentyfourteen_has_footer_widgets() {
if ( function_exists( 'jetpack_is_mobile' ) ) {
if ( ( User_Agent_Info::is_ipad() && is_active_sidebar( 'sidebar-1' ) )
|| ( jetpack_is_mobile( '', true ) && ( is_active_sidebar( 'sidebar-1' ) || is_active_sidebar( 'sidebar-2' ) ) )
|| is_active_sidebar( 'sidebar-3' ) ) {
return true;
}
}
return false;
}
/**
* Enqueue CSS stylesheet with theme styles for Infinite Scroll.
*/
function jetpack_twentyfourteen_infinite_scroll_enqueue_styles() {
if ( wp_script_is( 'the-neverending-homepage' ) ) {
wp_enqueue_style( 'infinity-twentyfourteen', plugins_url( 'twentyfourteen.css', __FILE__ ), array( 'the-neverending-homepage' ), '20131118' );
}
}
add_action( 'wp_enqueue_scripts', 'jetpack_twentyfourteen_infinite_scroll_enqueue_styles', 25 );
@@ -0,0 +1,168 @@
.infinite-scroll .pagination {
display: none;
}
.infinite-wrap > article:before,
.infinite-wrap > article:after {
content: "";
display: table;
}
.infinite-wrap > article:after {
clear: both;
}
.infinite-wrap > article {
padding-bottom: 2em;
}
/* Spinner */
.site-main .infinite-loader {
clear: both;
color: currentColor;
height: 42px;
margin-bottom: 3.5em;
}
.blog:not(.has-sidebar) .infinite-loader {
width: 100%;
}
.site-main .infinite-loader .spinner {
right: 50%!important;
}
/* Click-to-load */
#infinite-handle {
clear: both;
margin: 0 7.6923% 2em;
text-align: center;
}
/* Style "Load More" button */
.site-main #infinite-handle span {
background: #1a1a1a;
border-radius: 2px;
color: #fff;
font-family: "Libre Franklin", "Helvetica Neue", helvetica, arial, sans-serif;
font-size: inherit;
font-weight: 700;
letter-spacing: 0.046875em;
line-height: 1;
padding: 0.84375em 0.875em 0.78125em;
text-transform: uppercase;
}
#infinite-handle span:hover,
#infinite-handle span:focus {
background: #767676;
}
/* Style "Load More" button when dark color scheme is used */
.colors-dark .site-main #infinite-handle span {
background: #f8f8f8;
border-radius: 2px;
color: #222;
font-family: "Libre Franklin", "Helvetica Neue", helvetica, arial, sans-serif;
font-size: inherit;
font-weight: 700;
letter-spacing: 0.046875em;
line-height: 1;
padding: 0.84375em 0.875em 0.78125em;
text-transform: uppercase;
}
.colors-dark #infinite-handle span:hover,
.colors-dark #infinite-handle span:focus {
background: #bbb;
columns: #222;
}
/* Style Infinite Footer */
#infinite-footer {
position: fixed !important;
}
#infinite-footer .container {
background-color: #fff;
border-color: #d1d1d1;
padding: 0 7.6923%;
}
#infinite-footer .blog-info,
#infinite-footer .blog-credits {
font-family: "Libre Franklin", "Helvetica Neue", helvetica, arial, sans-serif;
text-align: center;
width: auto;
}
#infinite-footer .blog-info a,
#infinite-footer .blog-credits,
#infinite-footer .blog-credits a {
color: #222222;
}
#infinite-footer .blog-info a:hover,
#infinite-footer .blog-info a:focus,
#infinite-footer .blog-credits a:hover,
#infinite-footer .blog-credits a:focus {
color: #767676;
text-decoration: none;
}
.infinite-scroll #navigation,
.infinite-scroll.neverending .jetpack-mobile-link,
.infinite-scroll.neverending .site-footer {
display: none;
}
/* Shows the footer & mobile link again in case all posts have been loaded */
.infinity-end.neverending .jetpack-mobile-link,
.infinity-end.neverending .site-footer {
display: block;
}
@media screen and (min-width: 44.375em) {
#infinite-handle {
margin: 0 0 1em 0;
text-align: center;
}
.has-sidebar #infinite-handle {
text-align: right;
}
.site-main #infinite-handle span {
display: inline-block;
}
}
@media screen and (min-width: 48em) {
.infinite-wrap > article {
padding-bottom: 4em;
}
}
@media screen and (min-width: 48em) {
#infinite-footer .blog-info,
#infinite-footer .blog-credits {
line-height: 35px;
}
#infinite-footer .blog-info {
font-size: 1.1rem;
}
#infinite-footer .blog-credits {
font-size: 0.9rem;
}
.blog:not(.has-sidebar) .infinite-loader {
float: left;
width: 58%;
}
.site-main .infinite-loader .spinner {
margin-right: -17px;
}
}
@@ -0,0 +1,168 @@
.infinite-scroll .pagination {
display: none;
}
.infinite-wrap > article:before,
.infinite-wrap > article:after {
content: "";
display: table;
}
.infinite-wrap > article:after {
clear: both;
}
.infinite-wrap > article {
padding-bottom: 2em;
}
/* Spinner */
.site-main .infinite-loader {
clear: both;
color: currentColor;
height: 42px;
margin-bottom: 3.5em;
}
.blog:not(.has-sidebar) .infinite-loader {
width: 100%;
}
.site-main .infinite-loader .spinner {
left: 50%!important;
}
/* Click-to-load */
#infinite-handle {
clear: both;
margin: 0 7.6923% 2em;
text-align: center;
}
/* Style "Load More" button */
.site-main #infinite-handle span {
background: #1a1a1a;
border-radius: 2px;
color: #fff;
font-family: "Libre Franklin", "Helvetica Neue", helvetica, arial, sans-serif;
font-size: inherit;
font-weight: 700;
letter-spacing: 0.046875em;
line-height: 1;
padding: 0.84375em 0.875em 0.78125em;
text-transform: uppercase;
}
#infinite-handle span:hover,
#infinite-handle span:focus {
background: #767676;
}
/* Style "Load More" button when dark color scheme is used */
.colors-dark .site-main #infinite-handle span {
background: #f8f8f8;
border-radius: 2px;
color: #222;
font-family: "Libre Franklin", "Helvetica Neue", helvetica, arial, sans-serif;
font-size: inherit;
font-weight: 700;
letter-spacing: 0.046875em;
line-height: 1;
padding: 0.84375em 0.875em 0.78125em;
text-transform: uppercase;
}
.colors-dark #infinite-handle span:hover,
.colors-dark #infinite-handle span:focus {
background: #bbb;
columns: #222;
}
/* Style Infinite Footer */
#infinite-footer {
position: fixed !important;
}
#infinite-footer .container {
background-color: #fff;
border-color: #d1d1d1;
padding: 0 7.6923%;
}
#infinite-footer .blog-info,
#infinite-footer .blog-credits {
font-family: "Libre Franklin", "Helvetica Neue", helvetica, arial, sans-serif;
text-align: center;
width: auto;
}
#infinite-footer .blog-info a,
#infinite-footer .blog-credits,
#infinite-footer .blog-credits a {
color: #222222;
}
#infinite-footer .blog-info a:hover,
#infinite-footer .blog-info a:focus,
#infinite-footer .blog-credits a:hover,
#infinite-footer .blog-credits a:focus {
color: #767676;
text-decoration: none;
}
.infinite-scroll #navigation,
.infinite-scroll.neverending .jetpack-mobile-link,
.infinite-scroll.neverending .site-footer {
display: none;
}
/* Shows the footer & mobile link again in case all posts have been loaded */
.infinity-end.neverending .jetpack-mobile-link,
.infinity-end.neverending .site-footer {
display: block;
}
@media screen and (min-width: 44.375em) {
#infinite-handle {
margin: 0 0 1em 0;
text-align: center;
}
.has-sidebar #infinite-handle {
text-align: left;
}
.site-main #infinite-handle span {
display: inline-block;
}
}
@media screen and (min-width: 48em) {
.infinite-wrap > article {
padding-bottom: 4em;
}
}
@media screen and (min-width: 48em) {
#infinite-footer .blog-info,
#infinite-footer .blog-credits {
line-height: 35px;
}
#infinite-footer .blog-info {
font-size: 1.1rem;
}
#infinite-footer .blog-credits {
font-size: 0.9rem;
}
.blog:not(.has-sidebar) .infinite-loader {
float: right;
width: 58%;
}
.site-main .infinite-loader .spinner {
margin-left: -17px;
}
}
@@ -0,0 +1,63 @@
<?php
/**
* Infinite Scroll Theme Assets
*
* Register support for Twenty Seventeen.
*
* @package jetpack
*/
/**
* Add theme support for infinite scroll
*/
function jetpack_twentyseventeen_infinite_scroll_init() {
add_theme_support(
'infinite-scroll',
array(
'container' => 'main',
'render' => 'jetpack_twentyseventeen_infinite_scroll_render',
'footer' => 'content',
'footer_widgets' => jetpack_twentyseventeen_has_footer_widgets(),
)
);
}
add_action( 'init', 'jetpack_twentyseventeen_infinite_scroll_init' );
/**
* Custom render function for Infinite Scroll.
*/
function jetpack_twentyseventeen_infinite_scroll_render() {
while ( have_posts() ) {
the_post();
if ( is_search() ) {
get_template_part( 'template-parts/post/content', 'search' );
} else {
get_template_part( 'template-parts/post/content', get_post_format() );
}
}
}
/**
* Custom function to check for the presence of footer widgets or the social links menu
*/
function jetpack_twentyseventeen_has_footer_widgets() {
if ( is_active_sidebar( 'sidebar-2' ) ||
is_active_sidebar( 'sidebar-3' ) ||
has_nav_menu( 'social' ) ) {
return true;
}
return false;
}
/**
* Enqueue CSS stylesheet with theme styles for Infinite Scroll.
*/
function jetpack_twentyseventeen_infinite_scroll_enqueue_styles() {
if ( wp_script_is( 'the-neverending-homepage' ) ) {
wp_enqueue_style( 'infinity-twentyseventeen', plugins_url( 'twentyseventeen.css', __FILE__ ), array( 'the-neverending-homepage' ), '20161219' );
wp_style_add_data( 'infinity-twentyseventeen', 'rtl', 'replace' );
}
}
add_action( 'wp_enqueue_scripts', 'jetpack_twentyseventeen_infinite_scroll_enqueue_styles', 25 );
@@ -0,0 +1,161 @@
.infinite-scroll .pagination {
display: none;
}
.infinite-wrap > article:before,
.infinite-wrap > article:after {
content: "";
display: table;
}
.infinite-wrap > article:after {
clear: both;
}
.infinite-wrap > article {
margin-bottom: 3.5em;
}
/* Spinner */
.site-main .infinite-loader {
clear: both;
color: currentColor;
height: 42px;
margin-bottom: 3.5em;
}
.infinite-loader .spinner {
right: 50% !important;
top: 50% !important;
}
/* Click-to-load */
#infinite-handle {
clear: both;
margin-right: 7.6923%;
margin-left: 7.6923%;
text-align: center;
}
.site-main #infinite-handle span {
background: #1a1a1a;
border-radius: 2px;
color: #fff;
font-family: Montserrat, "Helvetica Neue", sans-serif;
font-size: inherit;
font-weight: 700;
letter-spacing: 0.046875em;
line-height: 1;
padding: 0.84375em 0.875em 0.78125em;
text-transform: uppercase;
}
#infinite-handle span:hover,
#infinite-handle span:focus {
background: #007acc;
}
#infinite-handle button:focus {
outline-offset: 0.375em;
}
/* Footer */
body #infinite-footer {
display: none;
z-index: 999;
}
body #infinite-footer .container {
background-color: #fff;
background-color: rgba(255, 255, 255, 0.8);
border-color: #d1d1d1;
padding: 0 7.6923%;
width: 100% !important;
}
body #infinite-footer .blog-info {
font-family: Montserrat, "Helvetica Neue", sans-serif;
height: 2.1875em;
line-height: 2.1875em;
}
body #infinite-footer .blog-info a {
color: #1a1a1a;
font-size: inherit
}
body #infinite-footer .blog-credits {
font-size: 13px;
font-size: 0.8125rem;
height: 2.692307692em;
line-height: 2.692307692em;
}
body #infinite-footer .blog-credits,
body #infinite-footer .blog-credits a {
color: #686868;
}
body #infinite-footer .blog-info a:hover,
body #infinite-footer .blog-info a:focus,
body #infinite-footer .blog-credits a:hover,
body #infinite-footer .blog-credits a:focus {
color: #007acc;
text-decoration: none;
}
@media screen and (min-width: 44.375em) {
.infinite-wrap > article,
.site-main .infinite-loader {
margin-bottom: 5.25em;
}
.infinite-loader .spinner {
right: 7.6923% !important;
margin-right: 12px;
}
#infinite-handle {
text-align: right;
}
.site-main #infinite-handle span {
display: inline-block;
}
body #infinite-footer .container {
padding: 0 0.761904762em;
width: -webkit-calc(100% - 42px) !important;
width: calc(100% - 42px) !important;
}
body:not(.custom-background-image) #infinite-footer {
bottom: 21px !important;
}
}
@media screen and (min-width: 56.875em) {
.infinite-loader .spinner {
right: 0 !important;
}
#infinite-handle {
margin: 0;
}
.no-sidebar .infinite-loader .spinner {
right: 50% !important;
margin: 0;
}
.no-sidebar #infinite-handle {
text-align: center;
}
}
@media screen and (min-width: 61.5625em) {
.infinite-wrap > article,
.site-main .infinite-loader {
margin-bottom: 7.0em;
}
}
@@ -0,0 +1,161 @@
.infinite-scroll .pagination {
display: none;
}
.infinite-wrap > article:before,
.infinite-wrap > article:after {
content: "";
display: table;
}
.infinite-wrap > article:after {
clear: both;
}
.infinite-wrap > article {
margin-bottom: 3.5em;
}
/* Spinner */
.site-main .infinite-loader {
clear: both;
color: currentColor;
height: 42px;
margin-bottom: 3.5em;
}
.infinite-loader .spinner {
left: 50% !important;
top: 50% !important;
}
/* Click-to-load */
#infinite-handle {
clear: both;
margin-right: 7.6923%;
margin-left: 7.6923%;
text-align: center;
}
.site-main #infinite-handle span {
background: #1a1a1a;
border-radius: 2px;
color: #fff;
font-family: Montserrat, "Helvetica Neue", sans-serif;
font-size: inherit;
font-weight: 700;
letter-spacing: 0.046875em;
line-height: 1;
padding: 0.84375em 0.875em 0.78125em;
text-transform: uppercase;
}
#infinite-handle span:hover,
#infinite-handle span:focus {
background: #007acc;
}
#infinite-handle button:focus {
outline-offset: 0.375em;
}
/* Footer */
body #infinite-footer {
display: none;
z-index: 999;
}
body #infinite-footer .container {
background-color: #fff;
background-color: rgba(255, 255, 255, 0.8);
border-color: #d1d1d1;
padding: 0 7.6923%;
width: 100% !important;
}
body #infinite-footer .blog-info {
font-family: Montserrat, "Helvetica Neue", sans-serif;
height: 2.1875em;
line-height: 2.1875em;
}
body #infinite-footer .blog-info a {
color: #1a1a1a;
font-size: inherit
}
body #infinite-footer .blog-credits {
font-size: 13px;
font-size: 0.8125rem;
height: 2.692307692em;
line-height: 2.692307692em;
}
body #infinite-footer .blog-credits,
body #infinite-footer .blog-credits a {
color: #686868;
}
body #infinite-footer .blog-info a:hover,
body #infinite-footer .blog-info a:focus,
body #infinite-footer .blog-credits a:hover,
body #infinite-footer .blog-credits a:focus {
color: #007acc;
text-decoration: none;
}
@media screen and (min-width: 44.375em) {
.infinite-wrap > article,
.site-main .infinite-loader {
margin-bottom: 5.25em;
}
.infinite-loader .spinner {
left: 7.6923% !important;
margin-left: 12px;
}
#infinite-handle {
text-align: left;
}
.site-main #infinite-handle span {
display: inline-block;
}
body #infinite-footer .container {
padding: 0 0.761904762em;
width: -webkit-calc(100% - 42px) !important;
width: calc(100% - 42px) !important;
}
body:not(.custom-background-image) #infinite-footer {
bottom: 21px !important;
}
}
@media screen and (min-width: 56.875em) {
.infinite-loader .spinner {
left: 0 !important;
}
#infinite-handle {
margin: 0;
}
.no-sidebar .infinite-loader .spinner {
left: 50% !important;
margin: 0;
}
.no-sidebar #infinite-handle {
text-align: center;
}
}
@media screen and (min-width: 61.5625em) {
.infinite-wrap > article,
.site-main .infinite-loader {
margin-bottom: 7.0em;
}
}
@@ -0,0 +1,48 @@
<?php
/**
* Infinite Scroll Theme Assets
*
* Register support for Twenty Sixteen.
*
* @package jetpack
*/
/**
* Add theme support for infinite scroll
*/
function jetpack_twentysixteen_infinite_scroll_init() {
add_theme_support(
'infinite-scroll',
array(
'container' => 'main',
'render' => 'jetpack_twentysixteen_infinite_scroll_render',
'footer' => 'content',
)
);
}
add_action( 'after_setup_theme', 'jetpack_twentysixteen_infinite_scroll_init' );
/**
* Custom render function for Infinite Scroll.
*/
function jetpack_twentysixteen_infinite_scroll_render() {
while ( have_posts() ) {
the_post();
if ( is_search() ) {
get_template_part( 'template-parts/content', 'search' );
} else {
get_template_part( 'template-parts/content', get_post_format() );
}
}
}
/**
* Enqueue CSS stylesheet with theme styles for Infinite Scroll.
*/
function jetpack_twentysixteen_infinite_scroll_enqueue_styles() {
if ( wp_script_is( 'the-neverending-homepage' ) ) {
wp_enqueue_style( 'infinity-twentysixteen', plugins_url( 'twentysixteen.css', __FILE__ ), array( 'the-neverending-homepage' ), '20151102' );
wp_style_add_data( 'infinity-twentysixteen', 'rtl', 'replace' );
}
}
add_action( 'wp_enqueue_scripts', 'jetpack_twentysixteen_infinite_scroll_enqueue_styles', 25 );
@@ -0,0 +1,25 @@
/* =Infinity Styles
-------------------------------------------------------------- */
.infinite-scroll #wrapper {
margin-bottom: 40px;
}
.infinite-scroll #content {
margin-bottom: 50px;
}
.infinite-scroll #content .infinite-wrap {
padding-top: 0;
border-top: 0;
}
/* Elements to hide */
.infinite-scroll #nav-above,
.infinite-scroll #nav-below,
.infinite-scroll.neverending #footer {
display: none;
}
/* Restore the footer when IS is finished */
.infinity-end.neverending #footer {
display: block;
}
#infinite-footer .blog-info a {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
@@ -0,0 +1,60 @@
<?php
/**
* Infinite Scroll Theme Assets
*
* Register support for @Twenty Ten and enqueue relevant styles.
*
* @package jetpack
*/
/**
* Add theme support for infinity scroll
*/
function jetpack_twentyten_infinite_scroll_init() {
add_theme_support(
'infinite-scroll',
array(
'container' => 'content',
'render' => 'jetpack_twentyten_infinite_scroll_render',
'footer' => 'wrapper',
'footer_widgets' => jetpack_twentyten_has_footer_widgets(),
)
);
}
add_action( 'init', 'jetpack_twentyten_infinite_scroll_init' );
/**
* Set the code to be rendered on for calling posts,
* hooked to template parts when possible.
*
* Note: must define a loop.
*/
function jetpack_twentyten_infinite_scroll_render() {
get_template_part( 'loop' );
}
/**
* Enqueue CSS stylesheet with theme styles for infinity.
*/
function jetpack_twentyten_infinite_scroll_enqueue_styles() {
if ( wp_script_is( 'the-neverending-homepage' ) ) {
// Add theme specific styles.
wp_enqueue_style( 'infinity-twentyten', plugins_url( 'twentyten.css', __FILE__ ), array( 'the-neverending-homepage' ), '20121002' );
}
}
add_action( 'wp_enqueue_scripts', 'jetpack_twentyten_infinite_scroll_enqueue_styles', 25 );
/**
* Do we have footer widgets?
*/
function jetpack_twentyten_has_footer_widgets() {
if ( is_active_sidebar( 'first-footer-widget-area' ) ||
is_active_sidebar( 'second-footer-widget-area' ) ||
is_active_sidebar( 'third-footer-widget-area' ) ||
is_active_sidebar( 'fourth-footer-widget-area' ) ) {
return true;
}
return false;
}
@@ -0,0 +1,90 @@
/* =Infinite Scroll
-------------------------------------------------------------- */
.infinite-wrap {
border-top: 0;
}
/* Spinner */
.infinite-loader {
background-color: #e8e5ce;
padding: 40px 0;
}
.infinite-loader .spinner {
margin: 0 auto;
width: 34px;
height: 34px;
}
.sidebar .infinite-loader .spinner {
padding-right: 376px;
}
.rtl.sidebar .infinite-loader .spinner {
padding-left: 376px;
padding-right: 0;
}
/* Click-to-load */
#infinite-handle {
background-color: #e8e5ce;
padding: 40px 0;
text-align: center;
}
.sidebar #infinite-handle {
padding-right: 376px;
}
.rtl.sidebar #infinite-handle {
padding-left: 376px;
padding-right: 0;
}
#infinite-handle span {
background: #e05d22; /* Old browsers */
background: -webkit-linear-gradient(top, #e05d22 0%, #d94412 100%); /* Chrome 10+, Safari 5.1+ */
background: linear-gradient(to bottom, #e05d22 0%, #d94412 100%); /* W3C */
border: none;
border-bottom: 3px solid #b93207;
border-radius: 2px;
display: inline-block;
color: #fff;
font-size: 100%;
padding: 11px 24px 10px;
text-decoration: none;
}
#infinite-handle span:hover {
background: #ed6a31; /* Old browsers */
background: -webkit-linear-gradient(top, #ed6a31 0%, #e55627 100%); /* Chrome 10+, Safari 5.1+ */
background: linear-gradient(to bottom, #ed6a31 0%, #e55627 100%); /* W3C */
outline: none;
}
#infinite-handle span:active {
background: #d94412; /* Old browsers */
background: -webkit-linear-gradient(top, #d94412 0%, #e05d22 100%); /* Chrome 10+, Safari 5.1+ */
background: linear-gradient(to bottom, #d94412 0%, #e05d22 100%); /* W3C */
border: none;
border-top: 3px solid #b93207;
padding: 10px 24px 11px;
}
/* Elements to hide: post navigation, normal footer. */
.infinite-scroll .paging-navigation,
.infinite-scroll.neverending #colophon {
display: none;
}
/* Hooks to infinity-end body class to restore footer. */
.infinity-end.neverending #colophon {
display: block;
}
/* For small viewports. */
@media (max-width: 999px) {
.sidebar .infinite-loader .spinner,
.rtl.sidebar .infinite-loader .spinner {
padding-right: 0;
padding-left: 0;
}
.infinite-scroll #infinite-handle,
.rtl.sidebar #infinite-handle {
padding-right: 0;
padding-left: 0;
}
}
@@ -0,0 +1,33 @@
<?php
/**
* Infinite Scroll Theme Assets
*
* Register support for Twenty Thirteen.
*
* @package jetpack
*/
/**
* Add theme support for infinite scroll
*/
function jetpack_twentythirteen_infinite_scroll_init() {
add_theme_support(
'infinite-scroll',
array(
'container' => 'content',
'footer' => 'page',
'footer_widgets' => array( 'sidebar-1' ),
)
);
}
add_action( 'after_setup_theme', 'jetpack_twentythirteen_infinite_scroll_init' );
/**
* Enqueue CSS stylesheet with theme styles for Infinite Scroll.
*/
function jetpack_twentythirteen_infinite_scroll_enqueue_styles() {
if ( wp_script_is( 'the-neverending-homepage' ) ) {
wp_enqueue_style( 'infinity-twentythirteen', plugins_url( 'twentythirteen.css', __FILE__ ), array( 'the-neverending-homepage' ), '20130409' );
}
}
add_action( 'wp_enqueue_scripts', 'jetpack_twentythirteen_infinite_scroll_enqueue_styles', 25 );
@@ -0,0 +1,33 @@
/* =Infinity Styles
-------------------------------------------------------------- */
.infinite-scroll .site-content:after {
clear: both;
content: '';
display: block;
}
.infinite-wrap {
border-top: 0;
}
.infinite-scroll.neverending .site-content {
margin-bottom: 48px;
margin-bottom: 3.428571429rem;
}
/* Elements to hide: post navigation, regular footer */
.infinite-scroll #nav-below,
.infinite-scroll.neverending #colophon {
display: none;
}
/* Hooks to infinity-end body class to restore footer */
.infinity-end.neverending #colophon {
display: block;
}
/* For responsive CSS */
@media (max-width: 599px) {
.infinite-scroll #infinite-handle {
padding-bottom: 48px;
padding-bottom: 3.428571429rem;
}
}
@@ -0,0 +1,49 @@
<?php
/**
* Infinite Scroll Theme Assets
*
* Register support for Twenty Twelve and enqueue relevant styles.
*
* @package jetpack
*/
/**
* Add theme support for infinite scroll
*/
function jetpack_twentytwelve_infinite_scroll_init() {
add_theme_support(
'infinite-scroll',
array(
'container' => 'content',
'footer' => 'page',
'footer_widgets' => jetpack_twentytwelve_has_footer_widgets(),
)
);
}
add_action( 'after_setup_theme', 'jetpack_twentytwelve_infinite_scroll_init' );
/**
* Enqueue CSS stylesheet with theme styles for infinity.
*/
function jetpack_twentytwelve_infinite_scroll_enqueue_styles() {
if ( wp_script_is( 'the-neverending-homepage' ) ) {
// Add theme specific styles.
wp_enqueue_style( 'infinity-twentytwelve', plugins_url( 'twentytwelve.css', __FILE__ ), array( 'the-neverending-homepage' ), '20120817' );
}
}
add_action( 'wp_enqueue_scripts', 'jetpack_twentytwelve_infinite_scroll_enqueue_styles', 25 );
/**
* Do we have footer widgets?
*/
function jetpack_twentytwelve_has_footer_widgets() {
if ( function_exists( 'jetpack_is_mobile' ) && jetpack_is_mobile() ) {
if ( is_front_page() && ( is_active_sidebar( 'sidebar-2' ) || is_active_sidebar( 'sidebar-3' ) ) ) {
return true;
} elseif ( is_active_sidebar( 'sidebar-1' ) ) {
return true;
}
}
return false;
}
@@ -0,0 +1,16 @@
<?php
/**
* Module Name: JSON API
* Module Description: Allow applications to securely access your content.
* Sort Order: 19
* First Introduced: 1.9
* Requires Connection: Yes
* Auto Activate: Public
* Module Tags: Writing, Developers
* Feature: General
* Additional Search Queries: api, rest, develop, developers, json, klout, oauth
*
* @package automattic/jetpack
*/
// Nothing fires here. Module status is checked on the WP.com-side to allow third-party applications.
@@ -0,0 +1,177 @@
<?php
/**
* Module Name: Beautiful Math
* Module Description: Use the LaTeX markup language to write mathematical equations and formulas
* Sort Order: 12
* First Introduced: 1.1
* Requires Connection: No
* Auto Activate: No
* Module Tags: Writing
* Feature: Writing
* Additional Search Queries: latex, math, equation, equations, formula, code
*
* @package automattic/jetpack
*/
/**
* LaTeX support.
*
* Backward compatibility requires support for both "[latex][/latex]", and
* "$latex $" shortcodes.
*
* $latex e^{\i \pi} + 1 = 0$ -> [latex]e^{\i \pi} + 1 = 0[/latex]
* $latex [a, b]$ -> [latex][a, b][/latex]
*/
/**
* Markup LaTeX content.
*
* @param string $content Post or comment contents to markup.
*/
function latex_markup( $content ) {
$textarr = wp_html_split( $content );
$regex = '%
\$latex(?:=\s*|\s+)
((?:
[^$]+ # Not a dollar
|
(?<=(?<!\\\\)\\\\)\$ # Dollar preceded by exactly one slash
)+)
(?<!\\\\)\$ # Dollar preceded by zero slashes
%ix';
foreach ( $textarr as &$element ) {
if ( '' === $element || '<' === $element[0] ) {
continue;
}
if ( false === stripos( $element, '$latex' ) ) {
continue;
}
$element = preg_replace_callback( $regex, 'latex_src', $element );
}
return implode( '', $textarr );
}
/**
* Process LaTeX string to rendered image.
*
* @param array $matches Matched regex results.
*/
function latex_src( $matches ) {
$latex = $matches[1];
$bg = latex_get_default_color( 'bg' );
$fg = latex_get_default_color( 'text', '000' );
$s = 0;
$latex = latex_entity_decode( $latex );
if ( preg_match( '/.+(&fg=[0-9a-f]{6}).*/i', $latex, $fg_matches ) ) {
$fg = substr( $fg_matches[1], 4 );
$latex = str_replace( $fg_matches[1], '', $latex );
}
if ( preg_match( '/.+(&bg=[0-9a-f]{6}).*/i', $latex, $bg_matches ) ) {
$bg = substr( $bg_matches[1], 4 );
$latex = str_replace( $bg_matches[1], '', $latex );
}
if ( preg_match( '/.+(&s=[0-9-]{1,2}).*/i', $latex, $s_matches ) ) {
$s = (int) substr( $s_matches[1], 3 );
$latex = str_replace( $s_matches[1], '', $latex );
}
return latex_render( $latex, $fg, $bg, $s );
}
/**
* Get the default color for an attribute.
*
* @param string $color Attribute to color (e.g. bg).
* @param string $default_color Default fallback color to use.
*/
function latex_get_default_color( $color, $default_color = 'ffffff' ) {
global $themecolors;
return isset( $themecolors[ $color ] ) ? $themecolors[ $color ] : $default_color;
}
/**
* Decode special characters in a LaTeX string.
*
* @param string $latex Character encoded content.
*/
function latex_entity_decode( $latex ) {
return str_replace( array( '&lt;', '&gt;', '&quot;', '&#039;', '&#038;', '&amp;', "\n", "\r" ), array( '<', '>', '"', "'", '&', '&', ' ', ' ' ), $latex );
}
/**
* Returns the URL for the server-side rendered image of LaTeX.
*
* @param string $latex LaTeX string.
* @param string $fg Foreground color.
* @param string $bg Background color.
* @param int $s Matches.
*
* @return string Image URL for the rendered LaTeX.
*/
function latex_render( $latex, $fg, $bg, $s = 0 ) {
$url = add_query_arg(
urlencode_deep(
array(
'latex' => $latex,
'bg' => $bg,
'fg' => $fg,
's' => $s,
'c' => '20201002', // cache buster. Added 2020-10-02 after server migration caused faulty rendering.
)
),
( is_ssl() ? 'https://' : 'http://' ) . 's0.wp.com/latex.php'
);
$alt = str_replace( '\\', '&#92;', esc_attr( $latex ) );
return sprintf(
'<img src="%1$s" alt="%2$s" class="latex" />',
esc_url( $url ),
$alt
);
}
/**
* The shortcode way. The attributes are the same as the old ones - 'fg' and 'bg', instead of foreground
* and background, and 's' is for the font size.
*
* Example: [latex s=4 bg=00f fg=ff0]\LaTeX[/latex]
*
* @param array $atts Shortcode attributes.
* @param string $content Content to format.
*/
function latex_shortcode( $atts, $content = '' ) {
$attr = shortcode_atts(
array(
's' => 0,
'bg' => latex_get_default_color( 'bg' ),
'fg' => latex_get_default_color( 'text', '000' ),
),
$atts,
'latex'
);
return latex_render( latex_entity_decode( $content ), $attr['fg'], $attr['bg'], $attr['s'] );
}
/**
* LaTeX needs to be untexturized.
*
* @param array $shortcodes Array of shortcodes not to texturize.
*/
function latex_no_texturize( $shortcodes ) {
$shortcodes[] = 'latex';
return $shortcodes;
}
add_filter( 'no_texturize_shortcodes', 'latex_no_texturize' );
add_filter( 'the_content', 'latex_markup', 9 ); // Before wptexturize.
add_filter( 'comment_text', 'latex_markup', 9 ); // Before wptexturize.
add_shortcode( 'latex', 'latex_shortcode' );
@@ -0,0 +1,565 @@
<?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Module Name: Likes
* Module Description: Give visitors an easy way to show they appreciate your content.
* First Introduced: 2.2
* Sort Order: 23
* Requires Connection: Yes
* Auto Activate: No
* Module Tags: Social
* Feature: Engagement
* Additional Search Queries: like, likes, wordpress.com
*
* @package automattic/jetpack
*/
/**
* NOTE: While the front-end behavior currently varies, try to keep the data
* model here the same as on wpcom to facilitate Simple→Atomic moves and
* possible future work to recombine the front-ends.
*/
// phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files.
use Automattic\Jetpack\Assets;
Assets::add_resource_hint(
array(
'//widgets.wp.com',
'//s0.wp.com',
'//0.gravatar.com',
'//1.gravatar.com',
'//2.gravatar.com',
),
'dns-prefetch'
);
require_once __DIR__ . '/likes/jetpack-likes-master-iframe.php';
require_once __DIR__ . '/likes/jetpack-likes-settings.php';
/**
* Jetpack Like Class
*/
class Jetpack_Likes {
/**
* Jetpack_Likes_Settings object
*
* @var Jetpack_Likes_Settings
*/
public $settings;
/**
* Initialize class
*/
public static function init() {
static $instance = null;
if ( ! $instance ) {
$instance = new Jetpack_Likes();
}
return $instance;
}
/**
* Constructs Likes class
*/
public function __construct() {
$this->settings = new Jetpack_Likes_Settings();
// We need to run on wp hook rather than init because we check is_amp_endpoint()
// when bootstrapping hooks.
add_action( 'wp', array( $this, 'action_init' ), 99 );
add_action( 'admin_init', array( $this, 'admin_init' ) );
add_action( 'jetpack_activate_module_likes', array( $this, 'set_social_notifications_like' ) );
add_action( 'jetpack_deactivate_module_likes', array( $this, 'delete_social_notifications_like' ) );
Jetpack::enable_module_configurable( __FILE__ );
add_filter( 'jetpack_module_configuration_url_likes', array( $this, 'jetpack_likes_configuration_url' ) );
add_action( 'admin_print_scripts-settings_page_sharing', array( $this, 'load_jp_css' ) );
add_filter( 'sharing_show_buttons_on_row_start', array( $this, 'configuration_target_area' ) );
$active = Jetpack::get_active_modules();
if ( in_array( 'publicize', $active, true ) && ! in_array( 'sharedaddy', $active, true ) ) {
// we have a sharing page but not the global options area.
add_action( 'pre_admin_screen_sharing', array( $this->settings, 'sharing_block' ), 20 );
add_action( 'pre_admin_screen_sharing', array( $this->settings, 'updated_message' ), -10 );
}
if ( ! in_array( 'sharedaddy', $active, true ) ) {
add_action( 'admin_init', array( $this->settings, 'process_update_requests_if_sharedaddy_not_loaded' ) );
add_action( 'sharing_global_options', array( $this->settings, 'admin_settings_showbuttonon_init' ), 19 );
add_action( 'sharing_admin_update', array( $this->settings, 'admin_settings_showbuttonon_callback' ), 19 );
add_action( 'admin_init', array( $this->settings, 'add_meta_box' ) );
} else {
add_filter( 'sharing_meta_box_title', array( $this->settings, 'add_likes_to_sharing_meta_box_title' ) );
add_action( 'start_sharing_meta_box_content', array( $this->settings, 'meta_box_content' ) );
}
add_action( 'admin_init', array( $this, 'admin_discussion_likes_settings_init' ) ); // Likes notifications.
add_action( 'wp_enqueue_scripts', array( $this, 'load_styles_register_scripts' ) );
add_action( 'save_post', array( $this->settings, 'meta_box_save' ) );
add_action( 'edit_attachment', array( $this->settings, 'meta_box_save' ) );
add_action( 'sharing_global_options', array( $this->settings, 'admin_settings_init' ), 20 );
add_action( 'sharing_admin_update', array( $this->settings, 'admin_settings_callback' ), 20 );
}
/**
* Set the social_notifications_like option to `on` when the Likes module is activated.
*
* @since 3.7.0
*/
public function set_social_notifications_like() {
update_option( 'social_notifications_like', 'on' );
}
/**
* Delete the social_notifications_like option that was set to `on` on module activation.
*
* @since 3.7.0
*/
public function delete_social_notifications_like() {
delete_option( 'social_notifications_like' );
}
/**
* Overrides default configuration url
*
* @uses admin_url
* @return string module settings URL
*/
public function jetpack_likes_configuration_url() {
return admin_url( 'options-general.php?page=sharing#likes' );
}
/**
* Loads Jetpack's CSS on the sharing page so we can use .jetpack-targetable
*/
public function load_jp_css() {
/**
* Do we really need `admin_styles`? With the new admin UI, it's breaking some bits.
* Jetpack::init()->admin_styles();
*/
}
/**
* Load scripts and styles for front end.
*/
public function load_styles_register_scripts() {
wp_enqueue_style( 'jetpack_likes', plugins_url( 'likes/style.css', __FILE__ ), array(), JETPACK__VERSION );
wp_register_script(
'jetpack_likes_queuehandler',
Assets::get_file_url_for_environment(
'_inc/build/likes/queuehandler.min.js',
'modules/likes/queuehandler.js'
),
array(),
JETPACK__VERSION,
true
);
}
/**
* Adds in the jetpack-targetable class so when we visit sharing#likes our like settings get highlighted by a yellow box
*
* @param string $html row heading for the sharedaddy "which page" setting.
* @return string $html with the jetpack-targetable class and likes id. tbody gets closed after the like settings
*/
public function configuration_target_area( $html = '' ) {
$html = "<tbody id='likes' class='jetpack-targetable'>" . $html;
return $html;
}
/**
* Options to be added to the discussion page (see also admin_settings_init, etc below for Sharing settings page)
*/
public function admin_discussion_likes_settings_init() {
// Add a temporary section, until we can move the setting out of there and with the rest of the email notification settings.
add_settings_section( 'likes-notifications', __( 'Likes Notifications', 'jetpack' ), array( $this, 'admin_discussion_likes_settings_section' ), 'discussion' );
add_settings_field( 'social-notifications', __( 'Email me whenever', 'jetpack' ), array( $this, 'admin_discussion_likes_settings_field' ), 'discussion', 'likes-notifications' );
// Register the setting.
register_setting( 'discussion', 'social_notifications_like', array( $this, 'admin_discussion_likes_settings_validate' ) );
}
/** Add email notification options to WordPress discussion settings */
public function admin_discussion_likes_settings_section() {
// Atypical usage here. We emit jquery to move likes notification checkbox to be with the rest of the email notification settings.
?>
<script type="text/javascript">
jQuery( function( $ ) {
var table = $( '#social_notifications_like' ).parents( 'table:first' ),
header = table.prevAll( 'h2:first' ),
newParent = $( '#moderation_notify' ).parent( 'label' ).parent();
if ( !table.length || !header.length || !newParent.length ) {
return;
}
newParent.append( '<br/>' ).append( table.end().parent( 'label' ).siblings().andSelf() );
header.remove();
table.remove();
} );
</script>
<?php
}
/** Check if email notifications for likes is on or off.
*
* @param string $option - which option we're checking (social_notifications_like).
*/
public function admin_likes_get_option( $option ) {
$option_setting = get_option( $option, 'on' );
return (int) ( 'on' === $option_setting );
}
/** Display email notification for likes setting in WordPress' discussion settings. */
public function admin_discussion_likes_settings_field() {
$like = $this->admin_likes_get_option( 'social_notifications_like' );
?>
<label><input type="checkbox" id="social_notifications_like" name="social_notifications_like" value="1" <?php checked( $like ); ?> /> <?php esc_html_e( 'Someone likes one of my posts', 'jetpack' ); ?></label>
<?php
}
/**
* Validate email notification settings.
*
* @param string $input - determines if checbox is on or off.
*/
public function admin_discussion_likes_settings_validate( $input ) {
// If it's not set (was unchecked during form submission) or was set to off (during option update), return 'off'.
if ( ! $input || 'off' === $input ) {
return 'off';
}
// Otherwise return 'on'.
return 'on';
}
/** Initialize admin settings */
public function admin_init() {
add_filter( 'manage_posts_columns', array( $this, 'add_like_count_column' ) );
add_filter( 'manage_pages_columns', array( $this, 'add_like_count_column' ) );
add_action( 'manage_posts_custom_column', array( $this, 'likes_edit_column' ), 10, 2 );
add_action( 'manage_pages_custom_column', array( $this, 'likes_edit_column' ), 10, 2 );
add_action( 'admin_print_styles-edit.php', array( $this, 'load_admin_css' ) );
add_action( 'admin_print_scripts-edit.php', array( $this, 'enqueue_admin_scripts' ) );
}
/** Initialize action */
public function action_init() {
/*
* Only check if the module is enabled here because
* we are not currently in The Loop and do not yet have access to check
* the switch_like_status post meta flag for the post to be loaded.
*/
if ( is_admin() || ! $this->settings->is_likes_module_enabled() ) {
return;
}
if ( ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) ||
( defined( 'APP_REQUEST' ) && APP_REQUEST ) ||
( defined( 'REST_API_REQUEST' ) && REST_API_REQUEST ) ||
( defined( 'COOKIE_AUTH_REQUEST' ) && COOKIE_AUTH_REQUEST ) ||
( defined( 'JABBER_SERVER' ) && JABBER_SERVER ) ) {
return;
}
if (
class_exists( 'Jetpack_AMP_Support' )
&& Jetpack_AMP_Support::is_amp_request()
) {
return;
}
add_filter( 'the_content', array( $this, 'post_likes' ), 30, 1 );
add_filter( 'the_excerpt', array( $this, 'post_likes' ), 30, 1 );
}
/**
* Load the CSS needed for the wp-admin area.
*/
public function load_admin_css() {
?>
<style type="text/css">
.vers img { display: none; }
.metabox-prefs .vers img { display: inline; }
.fixed .column-likes { width: 5.5em; padding: 8px 0; text-align: left; }
.fixed .column-stats { width: 5em; }
.fixed .column-likes .post-com-count {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
display: inline-block;
padding: 0 8px;
height: 2em;
margin-top: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
background-color: #787c82;
color: #FFF;
font-size: 11px;
line-height: 21px;
}
.fixed .column-likes .post-com-count::after { border: none !important; }
.fixed .column-likes .post-com-count:hover { background-color: #2271b1; }
.fixed .column-likes .vers:before {
font: normal 20px/1 dashicons;
content: '\f155';
speak: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@media screen and (max-width: 782px) {
.fixed .column-likes {
display: none;
}
}
</style>
<?php
}
/**
* Load the JS required for loading the like counts.
*/
public function enqueue_admin_scripts() {
if ( empty( $_GET['post_type'] ) || 'post' === $_GET['post_type'] || 'page' === $_GET['post_type'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
wp_enqueue_script(
'likes-post-count',
Assets::get_file_url_for_environment(
'_inc/build/likes/post-count.min.js',
'modules/likes/post-count.js'
),
array( 'jquery' ),
JETPACK__VERSION,
$in_footer = false
);
wp_enqueue_script(
'likes-post-count-jetpack',
Assets::get_file_url_for_environment(
'_inc/build/likes/post-count-jetpack.min.js',
'modules/likes/post-count-jetpack.js'
),
array( 'jquery', 'likes-post-count' ),
JETPACK__VERSION,
$in_footer = false
);
}
}
/**
* Add "Likes" column data to the post edit table in wp-admin.
*
* @param string $column_name - name of the column.
* @param int $post_id - the post id.
*/
public function likes_edit_column( $column_name, $post_id ) {
if ( 'likes' === $column_name ) {
$blog_id = Jetpack_Options::get_option( 'id' );
$permalink = get_permalink( get_the_ID() );
?>
<a title="" data-post-id="<?php echo (int) $post_id; ?>" class="post-com-count post-like-count" id="post-like-count-<?php echo (int) $post_id; ?>" data-blog-id="<?php echo (int) $blog_id; ?>" href="<?php echo esc_url( $permalink ); ?>#like-<?php echo (int) $post_id; ?>">
<span class="comment-count">0</span>
</a>
<?php
}
}
/**
* Add a "Likes" column header to the post edit table in wp-admin.
*
* @param array $columns - array of columns in wp-admin.
*/
public function add_like_count_column( $columns ) {
$date = $columns['date'];
unset( $columns['date'] );
$columns['likes'] = '<span class="vers"><img title="' . esc_attr__( 'Likes', 'jetpack' ) . '" alt="' . esc_attr__( 'Likes', 'jetpack' ) . '" src="//s0.wordpress.com/i/like-grey-icon.png" /><span class="screen-reader-text">' . __( 'Likes', 'jetpack' ) . '</span></span>';
$columns['date'] = $date;
return $columns;
}
/**
* Append like button to content.
*
* @param string $content - content of the page.
*/
public function post_likes( $content ) {
global $wp_current_filter;
$post_id = get_the_ID();
if ( ! is_numeric( $post_id ) || ! $this->settings->is_likes_visible() ) {
return $content;
}
// Do not output Likes on requests for ActivityPub requests.
if (
function_exists( '\Activitypub\is_activitypub_request' )
&& \Activitypub\is_activitypub_request()
) {
return $content;
}
// Ensure we don't display like button on post excerpts that are hooked inside the post content
if ( in_array( 'the_excerpt', (array) $wp_current_filter, true ) &&
in_array( 'the_content', (array) $wp_current_filter, true ) ) {
return $content;
}
$blog_id = Jetpack_Options::get_option( 'id' );
$url = home_url();
$url_parts = wp_parse_url( $url );
$domain = $url_parts['host'];
// Make sure to include the scripts before the iframe otherwise weird things happen.
add_action( 'wp_footer', 'jetpack_likes_master_iframe', 21 );
/**
* If the same post appears more then once on a page the page goes crazy
* we need a slightly more unique id / name for the widget wrapper.
*/
$uniqid = uniqid();
/**
* Enable an alternate Likes layout.
*
* @since 12.9
*
* @module likes
*
* @param bool $new_layout Enable the new Likes layout. False by default.
*/
$new_layout = apply_filters( 'likes_new_layout', true ) ? '&amp;n=1' : '';
$src = sprintf( 'https://widgets.wp.com/likes/?ver=%1$s#blog_id=%2$d&amp;post_id=%3$d&amp;origin=%4$s&amp;obj_id=%2$d-%3$d-%5$s%6$s', rawurlencode( JETPACK__VERSION ), $blog_id, $post_id, $domain, $uniqid, $new_layout );
$name = sprintf( 'like-post-frame-%1$d-%2$d-%3$s', $blog_id, $post_id, $uniqid );
$wrapper = sprintf( 'like-post-wrapper-%1$d-%2$d-%3$s', $blog_id, $post_id, $uniqid );
$headline = sprintf(
/** This filter is already documented in modules/sharedaddy/sharing-service.php */
apply_filters( 'jetpack_sharing_headline_html', '<h3 class="sd-title">%s</h3>', esc_html__( 'Like this:', 'jetpack' ), 'likes' ),
esc_html__( 'Like this:', 'jetpack' )
);
$title = esc_html__( 'Like or Reblog', 'jetpack' );
/** This filter is documented in modules/likes/jetpack-likes-master-iframe.php */
$src = apply_filters( 'jetpack_likes_iframe_src', $src );
$html = "<div class='sharedaddy sd-block sd-like jetpack-likes-widget-wrapper jetpack-likes-widget-unloaded' id='$wrapper' data-src='$src' data-name='$name' data-title='$title'>";
$html .= $headline;
$html .= "<div class='likes-widget-placeholder post-likes-widget-placeholder' style='height: 55px;'><span class='button'><span>" . esc_html__( 'Like', 'jetpack' ) . '</span></span> <span class="loading">' . esc_html__( 'Loading...', 'jetpack' ) . '</span></div>';
$html .= "<span class='sd-text-color'></span><a class='sd-link-color'></a>";
$html .= '</div>';
// Let's make sure that the script is enqueued.
wp_enqueue_script( 'jetpack_likes_queuehandler' );
return $content . $html;
}
}
/**
* Callback to get the value for the jetpack_likes_enabled field.
*
* Warning: this behavior is somewhat complicated!
* When the switch_like_status post_meta is unset, we follow the global setting in Sharing.
* When it is set to 0, we disable likes on the post, regardless of the global setting.
* When it is set to 1, we enable likes on the post, regardless of the global setting.
*
* @param array $post - post data we're checking.
*
* @return bool
*/
function jetpack_post_likes_get_value( array $post ) {
if ( ! isset( $post['id'] ) ) {
return false;
}
$post_likes_switched = get_post_meta( $post['id'], 'switch_like_status', true );
/** This filter is documented in modules/jetpack-likes-settings.php */
$sitewide_likes_enabled = (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
// An empty string: post meta was not set, so go with the global setting.
if ( '' === $post_likes_switched ) {
return $sitewide_likes_enabled;
} elseif ( '0' === $post_likes_switched ) { // User overrode the global setting to disable likes.
return false;
} elseif ( '1' === $post_likes_switched ) { // User overrode the global setting to enable likes.
return true;
}
// No default fallback, let's stay explicit.
}
/**
* Callback to set switch_like_status post_meta when jetpack_likes_enabled is updated.
*
* Warning: this behavior is somewhat complicated!
* When the switch_like_status post_meta is unset, we follow the global setting in Sharing.
* When it is set to 0, we disable likes on the post, regardless of the global setting.
* When it is set to 1, we enable likes on the post, regardless of the global setting.
*
* @param bool $enable_post_likes - checks if post likes are enabled.
* @param object $post_object - object containing post data.
*/
function jetpack_post_likes_update_value( $enable_post_likes, $post_object ) {
/** This filter is documented in modules/jetpack-likes-settings.php */
$sitewide_likes_enabled = (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
$should_switch_status = $enable_post_likes !== $sitewide_likes_enabled;
if ( $should_switch_status ) {
// Set the meta to 0 if the user wants to disable likes, 1 if user wants to enable.
$switch_like_status = ( $enable_post_likes ? 1 : 0 );
return update_post_meta( $post_object->ID, 'switch_like_status', $switch_like_status );
} else {
// Unset the meta otherwise.
return delete_post_meta( $post_object->ID, 'switch_like_status' );
}
}
/**
* Add Likes post_meta to the REST API Post response.
*
* @action rest_api_init
* @uses register_rest_field
* @link https://developer.wordpress.org/rest-api/extending-the-rest-api/modifying-responses/
*/
function jetpack_post_likes_register_rest_field() {
$post_types = get_post_types( array( 'public' => true ) );
foreach ( $post_types as $post_type ) {
register_rest_field(
$post_type,
'jetpack_likes_enabled',
array(
'get_callback' => 'jetpack_post_likes_get_value',
'update_callback' => 'jetpack_post_likes_update_value',
'schema' => array(
'description' => __( 'Are Likes enabled?', 'jetpack' ),
'type' => 'boolean',
),
)
);
/**
* Ensures all public internal post-types support `likes`
* This feature support flag is used by the REST API and Gutenberg.
*/
add_post_type_support( $post_type, 'jetpack-post-likes' );
}
}
// Add Likes post_meta to the REST API Post response.
add_action( 'rest_api_init', 'jetpack_post_likes_register_rest_field' );
// Some CPTs (e.g. Jetpack portfolios and testimonials) get registered with
// restapi_theme_init because they depend on theme support, so let's also hook to that.
add_action( 'restapi_theme_init', 'jetpack_post_likes_register_rest_field', 20 );
Jetpack_Likes::init();
@@ -0,0 +1,59 @@
<?php
/**
* Jetpack likes iframe.
*
* @package jetpack
*/
/**
* This function needs to get loaded after the like scripts get added to the page.
*/
function jetpack_likes_master_iframe() {
$version = gmdate( 'Ymd' );
$_locale = get_locale();
if ( ! defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) || ! file_exists( JETPACK__GLOTPRESS_LOCALES_PATH ) ) {
return false;
}
require_once JETPACK__GLOTPRESS_LOCALES_PATH;
$gp_locale = GP_Locales::by_field( 'wp_locale', $_locale );
$_locale = isset( $gp_locale->slug ) ? $gp_locale->slug : '';
$likes_locale = ( '' === $_locale || 'en' === $_locale ) ? '' : '&amp;lang=' . strtolower( $_locale );
/** This filter is documented in projects/plugins/jetpack/modules/likes.php */
$new_layout = apply_filters( 'likes_new_layout', true ) ? '&amp;n=1' : '';
$new_layout_class = $new_layout ? 'wpl-new-layout' : '';
$src = sprintf(
'https://widgets.wp.com/likes/master.html?ver=%1$s#ver=%1$s%2$s%3$s',
$version,
$likes_locale,
$new_layout
);
/**
* Filters the Likes iframe src, if any parameters need to be overridden or tracked.
*
* @module likes
*
* @since 14.1
*
* @param string URL to https://widgets.wp.com/ with various arguments appended to the get string and fragment.
*/
$src = apply_filters( 'jetpack_likes_iframe_src', $src );
if ( $new_layout ) {
// The span content is replaced by queuehandler when showOtherGravatars is called.
$likers_text = wp_kses( '<span>%d</span>', array( 'span' => array() ) );
} else {
/* translators: The value of %d is not available at the time of output */
$likers_text = wp_kses( __( '<span>%d</span> bloggers like this:', 'jetpack' ), array( 'span' => array() ) );
}
?>
<iframe src='<?php echo esc_url( $src ); ?>' scrolling='no' id='likes-master' name='likes-master' style='display:none;'></iframe>
<div id='likes-other-gravatars' class='<?php echo esc_attr( $new_layout_class ); ?>' role="dialog" aria-hidden="true" tabindex="-1"><div class="likes-text"><?php echo $likers_text; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div><ul class="wpl-avatars sd-like-gravatars"></ul></div>
<?php
}
@@ -0,0 +1,817 @@
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
use Automattic\Jetpack\Sync\Settings;
/**
* Jetpack likes settings class.
*/
class Jetpack_Likes_Settings {
/**
* False if running on WPCOM Simple
*
* @var bool
*/
public $in_jetpack;
/**
* Constructor function.
*/
public function __construct() {
$this->in_jetpack = ! ( defined( 'IS_WPCOM' ) && IS_WPCOM );
}
/**
* Replaces the "Sharing" title for the post screen metabox with "Likes and Shares"
*/
public function add_likes_to_sharing_meta_box_title() {
return __( 'Likes and Shares', 'jetpack' );
}
/**
* Adds a metabox to the post screen if the sharing one doesn't currently exist.
*/
public function add_meta_box() {
if (
/**
* Allow disabling of the Likes metabox on the post editor screen.
*
* @module likes
*
* @since 2.2.0
*
* @param bool false Should the Likes metabox be disabled? Default to false.
*/
apply_filters( 'post_flair_disable', false )
) {
return;
}
$post_types = get_post_types( array( 'public' => true ) );
/**
* Filters the Likes metabox title.
*
* @module likes
*
* @since 2.2.0
*
* @param string Likes metabox title. Default to "Likes".
*/
$title = apply_filters( 'likes_meta_box_title', __( 'Likes', 'jetpack' ) );
foreach ( $post_types as $post_type ) {
add_meta_box( 'likes_meta', $title, array( $this, 'meta_box_content' ), $post_type, 'side', 'default', array( '__back_compat_meta_box' => true ) );
}
}
/**
* Shows the likes option in the post screen metabox.
*
* @param object $post - the post object.
*/
public function meta_box_content( $post ) {
$post_id = ! empty( $post->ID ) ? (int) $post->ID : get_the_ID();
$checked = true;
$disabled = ! $this->is_enabled_sitewide();
$switched_status = get_post_meta( $post_id, 'switch_like_status', true );
if ( $disabled && empty( $switched_status ) || ! $disabled && $switched_status === '0' ) {
$checked = false;
}
/**
* Fires before the Likes meta box content in the post editor.
*
* @module likes
*
* @since 2.2.0
*
* @param WP_Post|array|null $post Post data.
*/
do_action( 'start_likes_meta_box_content', $post );
?>
<p>
<label for="wpl_enable_post_likes">
<input type="checkbox" name="wpl_enable_post_likes" id="wpl_enable_post_likes" value="1" <?php checked( $checked ); ?>>
<?php esc_html_e( 'Show likes.', 'jetpack' ); ?>
</label>
<input type="hidden" name="wpl_like_status_hidden" value="1" />
<?php wp_nonce_field( 'likes-and-shares', '_likesharenonce' ); ?>
</p>
<?php
/**
* Fires after the Likes meta box content in the post editor.
*
* @module likes
*
* @since 2.2.0
*
* @param WP_Post|array|null $post Post data.
*/
do_action( 'end_likes_meta_box_content', $post );
}
/**
* Returns the current state of the "WordPress.com Likes are" option.
*
* @return boolean true if enabled sitewide, false if not
*/
public function is_enabled_sitewide() {
/**
* Filters whether Likes are enabled by default on all posts.
* true if enabled sitewide, false if not.
*
* @module likes
*
* @since 2.2.0
*
* @param bool $option Are Likes enabled sitewide.
*/
return (bool) apply_filters( 'wpl_is_enabled_sitewide', ! Jetpack_Options::get_option_and_ensure_autoload( 'disabled_likes', 0 ) );
}
/**
* Handle meta box saving.
*
* @param int $post_id - the post ID.
*/
public function meta_box_save( $post_id ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return $post_id;
}
if ( empty( $_POST['wpl_like_status_hidden'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- we're not changing anything on the site.
return $post_id;
}
if ( ! isset( $_POST['_likesharenonce'] ) || ! wp_verify_nonce( $_POST['_likesharenonce'], 'likes-and-shares' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WordPress core doesn't unslash or verify nonces either.
return $post_id;
}
// Record sharing disable. Only needs to be done for WPCOM.
if ( ! $this->in_jetpack ) {
if ( isset( $_POST['post_type'] ) && in_array( $_POST['post_type'], get_post_types( array( 'public' => true ) ), true ) ) {
if ( ! isset( $_POST['wpl_enable_post_sharing'] ) ) {
update_post_meta( $post_id, 'sharing_disabled', 1 );
} else {
delete_post_meta( $post_id, 'sharing_disabled' );
}
}
}
if ( 'post' === $_POST['post_type'] ) {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
}
// Record a change in like status for this post - only if it contradicts the
// site like setting. If it doesn't contradict, then we delete the new individual status.
if ( ! $this->is_enabled_sitewide() && ! empty( $_POST['wpl_enable_post_likes'] ) ) {
// Likes turned on for individual posts. User wants to add the button to a single post.
update_post_meta( $post_id, 'switch_like_status', 1 );
} elseif ( $this->is_enabled_sitewide() && empty( $_POST['wpl_enable_post_likes'] ) ) {
// Likes turned on for all posts. User wants to remove the button from a single post.
update_post_meta( $post_id, 'switch_like_status', 0 );
} elseif (
( ! $this->is_enabled_sitewide() && empty( $_POST['wpl_enable_post_likes'] ) ) ||
( $this->is_enabled_sitewide() && ! empty( $_POST['wpl_enable_post_likes'] ) )
) {
// User wants to update the likes button status for an individual post, but the new status
// is the same as if they're asking for the default behavior according to the current Likes setting.
// So we delete the meta.
delete_post_meta( $post_id, 'switch_like_status' );
}
return $post_id;
}
/**
* WordPress.com: Metabox option for sharing (sharedaddy will handle this on the JP blog).
*
* @param object $post - the post object.
*/
public function sharing_meta_box_content( $post ) {
$post_id = ! empty( $post->ID ) ? (int) $post->ID : get_the_ID();
$disabled = get_post_meta( $post_id, 'sharing_disabled', true );
?>
<p>
<label for="wpl_enable_post_sharing">
<input type="checkbox" name="wpl_enable_post_sharing" id="wpl_enable_post_sharing" value="1" <?php checked( ! $disabled ); ?>>
<?php esc_html_e( 'Show sharing buttons.', 'jetpack' ); ?>
</label>
<input type="hidden" name="wpl_sharing_status_hidden" value="1" />
</p>
<?php
}
/**
* Adds the 'sharing' menu to the settings menu.
* Only ran if sharedaddy and publicize are not already active.
*
* @deprecated 13.2
*/
public function sharing_menu() {
add_submenu_page( 'options-general.php', esc_html__( 'Sharing Settings', 'jetpack' ), esc_html__( 'Sharing', 'jetpack' ), 'manage_options', 'sharing', array( $this, 'sharing_page' ) );
}
/**
* Provides a sharing page with the sharing_global_options hook
* so we can display the setting.
* Only ran if sharedaddy and publicize are not already active.
*
* @deprecated 13.2
*/
public function sharing_page() {
$this->updated_message();
?>
<div class="wrap">
<div class="icon32" id="icon-options-general"><br /></div>
<h1><?php esc_html_e( 'Sharing Settings', 'jetpack' ); ?></h1>
<?php
/** This action is documented in modules/sharedaddy/sharing.php */
do_action( 'pre_admin_screen_sharing' );
?>
<?php $this->sharing_block(); ?>
</div>
<?php
}
/**
* Returns the settings have been saved message.
*
* @deprecated 13.2
*/
public function updated_message() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- ignoring since we are just displaying that the settings have been saved and not making any other changes to the site.
if ( isset( $_GET['update'] ) && 'saved' === $_GET['update'] ) {
echo '<div class="updated"><p>' . esc_html__( 'Settings have been saved', 'jetpack' ) . '</p></div>';
}
}
/**
* Returns just the "sharing buttons" w/ like option block, so it can be inserted into different sharing page contexts
*
* @deprecated 13.2
*/
public function sharing_block() {
?>
<h2><?php esc_html_e( 'Sharing Buttons', 'jetpack' ); ?></h2>
<form method="post" action="">
<table class="form-table">
<tbody>
<?php
/** This action is documented in modules/sharedaddy/sharing.php */
do_action( 'sharing_global_options' );
?>
</tbody>
</table>
<p class="submit">
<input type="submit" name="submit" class="button-primary" value="<?php esc_attr_e( 'Save Changes', 'jetpack' ); ?>" />
<?php wp_nonce_field( 'sharing-options' ); ?>
</form>
<?php
}
/**
* Are likes enabled for this post?
*
* @param int $post_id - the post ID.
* @return bool
*/
public function is_post_likeable( $post_id = 0 ) {
$post = get_post( $post_id );
if ( ! $post || is_wp_error( $post ) ) {
return false;
}
$sitewide_likes_enabled = (bool) $this->is_enabled_sitewide();
$post_likes_switched = get_post_meta( $post->ID, 'switch_like_status', true );
/*
* On WPCOM, headstart was inserting bad data for post_likes_switched.
* it was wrapping the boolean value in an array. The array is always truthy regardless of its contents.
* There was another bug where truthy values were ignored if the global like setting was false.
* So in effect, the values for headstart never had an inpact.
* Delete the $post_likes_switched flag in this case in order to keep the behaviour as it was.
*/
if ( is_array( $post_likes_switched ) ) {
$post_likes_switched = null;
}
/*
* on WPCOM, we need to look at post edit date so we don't break old posts
* if post edit date predates this code, stick with the former (buggy) behavior
* see: p7DVsv-64H-p2
*/
$last_modified_time = strtotime( $post->post_modified_gmt );
$behavior_was_changed_at = strtotime( '2019-02-22 00:40:42' );
if ( $this->in_jetpack || $last_modified_time > $behavior_was_changed_at ) {
/*
* the new and improved behavior on Jetpack and recent WPCOM posts:
* $post_likes_switched is empty to follow site setting,
* 0 if we want likes disabled, 1 if we want likes enabled.
*/
return $post_likes_switched || ( $sitewide_likes_enabled && $post_likes_switched !== '0' );
}
// implicit else (old behavior): $post_likes_switched simply inverts the global setting.
return ( (bool) $post_likes_switched ) xor $sitewide_likes_enabled;
}
/**
* Is the like button itself visible (as opposed to the reblog button)
*
* If called from within The Loop or if called with a $post_id set, then the post will be checked.
* Otherwise the sitewide setting will be used.
*
* @param int $post_id The ID of the post being rendered. Defaults to the current post if called from within The Loop.
* @return bool
*/
public function is_likes_button_visible( $post_id = 0 ) {
if ( in_the_loop() || $post_id ) {
// If in The Loop, is_post_likeable will check the current post.
return $this->is_post_likeable( $post_id );
} else {
// Otherwise, check and see if likes are enabled sitewide.
return $this->is_enabled_sitewide();
}
}
/**
* Are likes visible in this context?
*
* Some of this code was taken and modified from sharing_display() to ensure
* similar logic and filters apply here, too.
*/
public function is_likes_visible() {
if ( Settings::is_syncing() ) {
return false;
}
return $this->is_likes_button_visible() && $this->is_likes_module_enabled();
}
/**
* Apply filters to determine if the likes module itself is enabled
*
* @return bool
*/
public function is_likes_module_enabled() {
global $wp_current_filter; // Used to apply 'sharing_show' filter.
$post = get_post();
$enabled = true;
// Never show on feeds or previews.
if ( is_feed() || is_preview() ) {
$enabled = false;
// Not a feed or preview, so what is it?
} else {
if ( post_password_required() ) {
$enabled = false;
}
if ( in_array( 'get_the_excerpt', (array) $wp_current_filter, true ) ) {
$enabled = false;
}
// Sharing Setting Overrides ****************************************
// Single post including custom post types.
if ( is_single() ) {
if ( ! $this->is_single_post_enabled( ( $post instanceof WP_Post ) ? $post->post_type : 'post' ) ) {
$enabled = false;
}
// Single page.
} elseif ( is_page() && ! is_front_page() ) {
if ( ! $this->is_single_page_enabled() ) {
$enabled = false;
}
// Attachment.
} elseif ( is_attachment() ) {
if ( ! $this->is_attachment_enabled() ) {
$enabled = false;
}
// All other loops.
} elseif ( ! $this->is_index_enabled() ) {
$enabled = false;
}
}
if ( $post instanceof WP_Post ) {
// Check that the post is a public, published post.
if ( 'attachment' === $post->post_type ) {
$post_status = get_post_status( $post->post_parent );
} else {
$post_status = $post->post_status;
}
if ( 'publish' !== $post_status ) {
$enabled = false;
}
}
// Run through the sharing filters.
/** This filter is documented in modules/sharedaddy/sharing-service.php */
$enabled = apply_filters( 'sharing_show', $enabled, $post );
/**
* Filters whether the Likes should be visible or not.
* Allows overwriting the options set in Settings > Sharing.
*
* @module likes
*
* @since 2.2.0
*
* @param bool $enabled Should the Likes be visible?
*/
return (bool) apply_filters( 'wpl_is_likes_visible', $enabled );
}
/**
* Are Post Likes enabled on single posts?
*
* @param string $post_type custom post type identifier.
* @return bool
*/
public function is_single_post_enabled( $post_type = 'post' ) {
$options = $this->get_options();
return (bool) apply_filters(
/**
* Filters whether Likes should be enabled on single posts.
*
* The dynamic part of the filter, {$post_type}, allows you to specific the post type where Likes should be enabled.
*
* @module likes
*
* @since 2.2.0
*
* @param bool $enabled Are Post Likes enabled on single posts?
*/
"wpl_is_single_{$post_type}_disabled",
(bool) in_array( $post_type, $options['show'], true )
);
}
/**
* Get the 'disabled_likes' option from the DB of the current blog.
*
* @return array
*/
public function get_options() {
$setting = array();
$setting['disabled'] = get_option( 'disabled_likes' );
$sharing = get_option( 'sharing-options', array() );
// Default visibility settings
if ( ! isset( $sharing['global']['show'] ) ) {
$sharing['global']['show'] = array( 'post', 'page' );
// Scalar check
} elseif ( is_scalar( $sharing['global']['show'] ) ) {
switch ( $sharing['global']['show'] ) {
case 'posts':
$sharing['global']['show'] = array( 'post', 'page' );
break;
case 'index':
$sharing['global']['show'] = array( 'index' );
break;
case 'posts-index':
$sharing['global']['show'] = array( 'post', 'page', 'index' );
break;
}
}
// Ensure it's always an array (even if not previously empty or scalar)
$setting['show'] = ! empty( $sharing['global']['show'] ) ? (array) $sharing['global']['show'] : array();
/**
* Filters where the Likes are displayed.
*
* @module likes
*
* @since 2.2.0
*
* @param array $setting Array of Likes display settings.
*/
return apply_filters( 'wpl_get_options', $setting );
}
/**
* Are Post Likes enabled on archive/front/search pages?
*
* @return bool
*/
public function is_index_enabled() {
$options = $this->get_options();
/**
* Filters whether Likes should be enabled on archive/front/search pages.
*
* @module likes
*
* @since 2.2.0
*
* @param bool $enabled Are Post Likes enabled on archive/front/search pages?
*/
return (bool) apply_filters( 'wpl_is_index_disabled', (bool) in_array( 'index', $options['show'], true ) );
}
/**
* Are Post Likes enabled on single pages?
*
* @return bool
*/
public function is_single_page_enabled() {
$options = $this->get_options();
/**
* Filters whether Likes should be enabled on single pages.
*
* @module likes
*
* @since 2.2.0
*
* @param bool $enabled Are Post Likes enabled on single pages?
*/
return (bool) apply_filters( 'wpl_is_single_page_disabled', (bool) in_array( 'page', $options['show'], true ) );
}
/**
* Are Media Likes enabled on single pages?
*
* @return bool
*/
public function is_attachment_enabled() {
$options = $this->get_options();
/**
* Filters whether Likes should be enabled on attachment pages.
*
* @module likes
*
* @since 2.2.0
*
* @param bool $enabled Are Post Likes enabled on attachment pages?
*/
return (bool) apply_filters( 'wpl_is_attachment_disabled', (bool) in_array( 'attachment', $options['show'], true ) );
}
/**
* The actual options block to be inserted into the sharing page.
*/
public function admin_settings_init() {
?>
<tr>
<th scope="row">
<label><?php esc_html_e( 'WordPress.com Likes are', 'jetpack' ); ?></label>
</th>
<td>
<div>
<label>
<input type="radio" class="code" name="wpl_default" value="on" <?php checked( $this->is_enabled_sitewide(), true ); ?> />
<?php esc_html_e( 'On for all posts', 'jetpack' ); ?>
</label>
</div>
<div>
<label>
<input type="radio" class="code" name="wpl_default" value="off" <?php checked( $this->is_enabled_sitewide(), false ); ?> />
<?php esc_html_e( 'Turned on per post', 'jetpack' ); ?>
</label>
<div>
</td>
</tr>
<?php if ( ! $this->in_jetpack ) : ?>
<tr>
<th scope="row">
<label><?php esc_html_e( 'WordPress.com Reblog Button', 'jetpack' ); ?></label>
</th>
<td>
<div>
<label>
<input type="radio" class="code" name="jetpack_reblogs_enabled" value="on" <?php checked( $this->reblogs_enabled_sitewide(), true ); ?> />
<?php esc_html_e( 'Show the Reblog button on posts', 'jetpack' ); ?>
</label>
</div>
<div>
<label>
<input type="radio" class="code" name="jetpack_reblogs_enabled" value="off" <?php checked( $this->reblogs_enabled_sitewide(), false ); ?> />
<?php esc_html_e( 'Don\'t show the Reblog button on posts', 'jetpack' ); ?>
</label>
</div>
</td>
</tr>
<!-- WPCOM only: Comment Likes -->
<?php if ( ! $this->in_jetpack ) : ?>
<tr>
<th scope="row">
<label><?php esc_html_e( 'Comment Likes are', 'jetpack' ); ?></label>
</th>
<td>
<div>
<label>
<input type="checkbox" class="code" name="jetpack_comment_likes_enabled" value="1" <?php checked( $this->is_comments_enabled(), true ); ?> />
<?php esc_html_e( 'On for all comments', 'jetpack' ); ?>
</label>
</div>
</td>
</tr>
<?php endif; ?>
<?php endif; ?>
</tbody> <?php // closes the tbody attached to sharing_show_buttons_on_row_start... ?>
<?php
}
/**
* Returns the current state of the "WordPress.com Reblogs are" option.
*
* @return bool true if enabled sitewide, false if not
*/
public function reblogs_enabled_sitewide() {
/**
* Filters whether Reblogs are enabled by default on all posts.
* true if enabled sitewide, false if not.
*
* @module likes
*
* @since 3.0.0
*
* @param bool $option Are Reblogs enabled sitewide.
*/
return (bool) apply_filters( 'wpl_reblogging_enabled_sitewide', ! get_option( 'disabled_reblogs' ) );
}
/**
* Used for WPCOM ONLY. Comment likes are in their own module in Jetpack.
* Returns if comment likes are enabled. Defaults to 'off'
*
* @return boolean true if we should show comment likes, false if not
*/
public function is_comments_enabled() {
/**
* Filters whether Comment Likes are enabled.
* true if enabled, false if not.
*
* @module comment-likes
*
* @since 2.2.0
*
* @param bool $option Are Comment Likes enabled sitewide.
*/
return (bool) apply_filters( 'jetpack_comment_likes_enabled', get_option( 'jetpack_comment_likes_enabled', false ) );
}
/**
* Saves the setting in the database.
*/
public function admin_settings_callback() {
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'sharing-options' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WordPress core doesn't unslash or verify nonces either.
return;
}
// We're looking for these, and doing a dance to set some stats and save
// them together in array option.
if ( ! empty( $_POST['wpl_default'] ) ) {
$new_state = sanitize_text_field( wp_unslash( $_POST['wpl_default'] ) );
} else {
$new_state = 'on';
}
if ( ! empty( $_POST['jetpack_reblogs_enabled'] ) ) {
$reblogs_new_state = sanitize_text_field( wp_unslash( $_POST['jetpack_reblogs_enabled'] ) );
} else {
$reblogs_new_state = 'on';
}
// Checked (enabled)
switch ( $new_state ) {
case 'off':
update_option( 'disabled_likes', 1 );
break;
case 'on':
default:
delete_option( 'disabled_likes' );
break;
}
switch ( $reblogs_new_state ) {
case 'off':
update_option( 'disabled_reblogs', 1 );
break;
case 'on':
default:
delete_option( 'disabled_reblogs' );
break;
}
// WPCOM only: Comment Likes
if ( ! $this->in_jetpack ) {
if ( ! empty( $_POST['jetpack_comment_likes_enabled'] ) ) {
$new_comments_state = sanitize_text_field( wp_unslash( $_POST['jetpack_comment_likes_enabled'] ) );
} else {
$new_comments_state = false;
}
switch ( (bool) $new_comments_state ) {
case true:
update_option( 'jetpack_comment_likes_enabled', 1 );
break;
case false:
default:
update_option( 'jetpack_comment_likes_enabled', 0 );
break;
}
}
}
/**
* Adds the admin update hook so we can save settings even if Sharedaddy is not enabled.
*/
public function process_update_requests_if_sharedaddy_not_loaded() {
if ( isset( $_GET['page'] ) && ( $_GET['page'] === 'sharing.php' || $_GET['page'] === 'sharing' ) ) {
if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'sharing-options' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WordPress core doesn't unslash or verify nonces either.
/** This action is documented in modules/sharedaddy/sharing.php */
do_action( 'sharing_admin_update' );
wp_safe_redirect( admin_url( 'options-general.php?page=sharing&update=saved' ) );
die( 0 );
}
}
}
/**
* If sharedaddy is not loaded, we don't have the "Show buttons on" yet, so we need to add that since it affects likes too.
*/
public function admin_settings_showbuttonon_init() {
/** This action is documented in modules/sharedaddy/sharing.php */
echo apply_filters( 'sharing_show_buttons_on_row_start', '<tr valign="top">' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
?>
<th scope="row"><label><?php esc_html_e( 'Show buttons on', 'jetpack' ); ?></label></th>
<td>
<?php
$br = false;
$shows = array_values( get_post_types( array( 'public' => true ) ) );
array_unshift( $shows, 'index' );
$global = $this->get_options();
foreach ( $shows as $show ) :
if ( 'index' === $show ) {
$label = __( 'Front Page, Archive Pages, and Search Results', 'jetpack' );
} else {
$post_type_object = get_post_type_object( $show );
$label = $post_type_object->labels->name;
}
if ( $br ) {
echo '<br />';
}
?>
<label><input type="checkbox"<?php checked( in_array( $show, $global['show'], true ) ); ?> name="show[]" value="<?php echo esc_attr( $show ); ?>" /> <?php echo esc_html( $label ); ?></label>
<?php
$br = true;
endforeach;
?>
</td>
<?php
/** This action is documented in modules/sharedaddy/sharing.php */
echo apply_filters( 'sharing_show_buttons_on_row_end', '</tr>' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* If sharedaddy is not loaded, we still need to save the the settings of the "Show buttons on" option.
*/
public function admin_settings_showbuttonon_callback() {
$options = get_option( 'sharing-options' );
if ( ! is_array( $options ) ) {
$options = array();
}
$shows = array_values( get_post_types( array( 'public' => true ) ) );
$shows[] = 'index';
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- triggered due to the 'sharing_admin_update' action, but the code in sharing.php checks for the nonce before firing the action.
$data = $_POST;
if ( isset( $data['show'] ) ) {
if ( is_scalar( $data['show'] ) ) {
switch ( $data['show'] ) {
case 'posts':
$data['show'] = array( 'post', 'page' );
break;
case 'index':
$data['show'] = array( 'index' );
break;
case 'posts-index':
$data['show'] = array( 'post', 'page', 'index' );
break;
}
}
$data['show'] = array_intersect( $data['show'], $shows );
if ( $data['show'] ) {
$options['global']['show'] = $data['show'];
}
} else {
$options['global']['show'] = array();
}
update_option( 'sharing-options', $options );
}
}
@@ -0,0 +1,20 @@
window.wpPostLikeCount = window.wpPostLikeCount || {};
( function ( $ ) {
window.wpPostLikeCount = jQuery.extend( window.wpPostLikeCount, {
request: function ( options ) {
return $.ajax( {
type: 'GET',
url: window.wpPostLikeCount.jsonAPIbase + options.path,
dataType: 'jsonp',
data: options.data,
success: function ( response ) {
options.success( response );
},
error: function ( response ) {
options.error( response );
},
} );
},
} );
} )( jQuery );
@@ -0,0 +1,64 @@
window.wpPostLikeCount = window.wpPostLikeCount || {};
( function ( $ ) {
window.wpPostLikeCount = jQuery.extend( window.wpPostLikeCount, {
jsonAPIbase: 'https://public-api.wordpress.com/rest/v1',
APIqueue: [],
wpPostLikeCount: function () {
$( '.post-like-count' ).each( function () {
var post_id = $( this ).attr( 'data-post-id' );
var blog_id = $( this ).attr( 'data-blog-id' );
window.wpPostLikeCount.APIqueue.push(
'/sites/' + blog_id + '/posts/' + post_id + '/likes'
);
} );
window.wpPostLikeCount.getCounts();
},
showCount: function ( post_id, count ) {
if ( count > 0 ) {
$( '#post-like-count-' + post_id )
.find( '.comment-count' )
.hide();
$( '#post-like-count-' + post_id )
.find( '.comment-count' )
.text( count );
$( '#post-like-count-' + post_id )
.find( '.comment-count' )
.fadeIn();
}
},
getCounts: function () {
var batchRequest = {
path: '/batch',
data: '',
success: function ( response ) {
for ( var path in response ) {
if ( ! response[ path ].error_data ) {
var urlPieces = path.split( '/' ); // pieces[4] = post id;
var post_id = urlPieces[ 4 ];
window.wpPostLikeCount.showCount( post_id, response[ path ].found );
}
}
},
error: function ( /*response*/ ) {},
};
var amp = '';
for ( var i = 0; i < window.wpPostLikeCount.APIqueue.length; i++ ) {
if ( i > 0 ) {
amp = '&';
}
batchRequest.data += amp + 'urls[]=' + window.wpPostLikeCount.APIqueue[ i ];
}
window.wpPostLikeCount.request( batchRequest );
},
} );
} )( jQuery );
jQuery( document ).ready( function ( /*$*/ ) {
window.wpPostLikeCount.wpPostLikeCount();
} );
@@ -0,0 +1,548 @@
/* global wpcom_reblog */
var jetpackLikesWidgetBatch = [];
var jetpackLikesMasterReady = false;
// Due to performance problems on pages with a large number of widget iframes that need to be loaded,
// we are limiting the processing at any instant to unloaded widgets that are currently in viewport,
// plus this constant that will allow processing of widgets above and bellow the current fold.
// This aim of it is to improve the UX and hide the transition from unloaded to loaded state from users.
var jetpackLikesLookAhead = 2000; // pixels
// Keeps track of loaded comment likes widget so we can unload them when they are scrolled out of view.
var jetpackCommentLikesLoadedWidgets = [];
var jetpackLikesDocReadyPromise = new Promise( resolve => {
if ( document.readyState !== 'loading' ) {
resolve();
} else {
window.addEventListener( 'DOMContentLoaded', () => resolve() );
}
} );
function JetpackLikesPostMessage( message, target ) {
if ( typeof message === 'string' ) {
try {
message = JSON.parse( message );
} catch {
return;
}
}
if ( target && typeof target.postMessage === 'function' ) {
try {
target.postMessage(
JSON.stringify( {
type: 'likesMessage',
data: message,
} ),
'*'
);
} catch {
// Ignore error
}
}
}
function JetpackLikesBatchHandler() {
const requests = [];
document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' ).forEach( widget => {
if ( jetpackLikesWidgetBatch.indexOf( widget.id ) > -1 ) {
return;
}
if ( ! jetpackIsScrolledIntoView( widget ) ) {
return;
}
jetpackLikesWidgetBatch.push( widget.id );
var regex = /like-(post|comment)-wrapper-(\d+)-(\d+)-(\w+)/,
match = regex.exec( widget.id ),
info;
if ( ! match || match.length !== 5 ) {
return;
}
info = {
blog_id: match[ 2 ],
width: widget.width,
};
if ( 'post' === match[ 1 ] ) {
info.post_id = match[ 3 ];
} else if ( 'comment' === match[ 1 ] ) {
info.comment_id = match[ 3 ];
}
info.obj_id = match[ 4 ];
requests.push( info );
} );
if ( requests.length > 0 ) {
JetpackLikesPostMessage(
{ event: 'initialBatch', requests: requests },
window.frames[ 'likes-master' ]
);
}
}
function JetpackLikesMessageListener( event ) {
let message = event && event.data;
if ( typeof message === 'string' ) {
try {
message = JSON.parse( message );
} catch {
return;
}
}
const type = message && message.type;
const data = message && message.data;
if ( type !== 'likesMessage' || typeof data.event === 'undefined' ) {
return;
}
// We only allow messages from one origin
const allowedOrigin = 'https://widgets.wp.com';
if ( allowedOrigin !== event.origin ) {
return;
}
switch ( data.event ) {
case 'masterReady':
jetpackLikesDocReadyPromise.then( () => {
jetpackLikesMasterReady = true;
const stylesData = {
event: 'injectStyles',
};
const sdTextColor = document.querySelector( '.sd-text-color' );
const sdLinkColor = document.querySelector( '.sd-link-color' );
const sdTextColorStyles = ( sdTextColor && getComputedStyle( sdTextColor ) ) || {};
const sdLinkColorStyles = ( sdLinkColor && getComputedStyle( sdLinkColor ) ) || {};
// enable reblogs if we're on a single post page
if ( document.body.classList.contains( 'single' ) ) {
JetpackLikesPostMessage( { event: 'reblogsEnabled' }, window.frames[ 'likes-master' ] );
}
stylesData.textStyles = {
color: sdTextColorStyles.color,
fontFamily: sdTextColorStyles[ 'font-family' ],
fontSize: sdTextColorStyles[ 'font-size' ],
direction: sdTextColorStyles.direction,
fontWeight: sdTextColorStyles[ 'font-weight' ],
fontStyle: sdTextColorStyles[ 'font-style' ],
textDecoration: sdTextColorStyles[ 'text-decoration' ],
};
stylesData.linkStyles = {
color: sdLinkColorStyles.color,
fontFamily: sdLinkColorStyles[ 'font-family' ],
fontSize: sdLinkColorStyles[ 'font-size' ],
textDecoration: sdLinkColorStyles[ 'text-decoration' ],
fontWeight: sdLinkColorStyles[ 'font-weight' ],
fontStyle: sdLinkColorStyles[ 'font-style' ],
};
JetpackLikesPostMessage( stylesData, window.frames[ 'likes-master' ] );
JetpackLikesBatchHandler();
} );
break;
case 'showLikeWidget': {
const placeholder = document.querySelector( `#${ data.id } .likes-widget-placeholder` );
if ( placeholder ) {
placeholder.style.display = 'none';
}
// Add a `liked` class to the wrapper if the post already has likes.
if ( data.total > 0 ) {
document.querySelector( `#${ data.id }` ).classList.add( 'liked' );
}
break;
}
case 'showCommentLikeWidget': {
const placeholder = document.querySelector( `#${ data.id } .likes-widget-placeholder` );
if ( placeholder ) {
placeholder.style.display = 'none';
}
break;
}
case 'killCommentLikes':
// If kill switch for comment likes is enabled remove all widgets wrappers and `Loading...` placeholders.
document
.querySelectorAll( '.jetpack-comment-likes-widget-wrapper' )
.forEach( wrapper => wrapper.remove() );
break;
case 'clickReblogFlair':
if ( wpcom_reblog && typeof wpcom_reblog.toggle_reblog_box_flair === 'function' ) {
wpcom_reblog.toggle_reblog_box_flair( data.obj_id );
}
break;
case 'hideOtherGravatars': {
hideLikersPopover();
break;
}
case 'clickPostLike':
// Add or remove the wrapper `liked` class based on the total likes.
if ( data.total > 0 ) {
document.querySelector( `#${ data.id }` ).classList.add( 'liked' );
} else {
document.querySelector( `#${ data.id }` ).classList.remove( 'liked' );
}
break;
case 'showOtherGravatars': {
const container = document.querySelector( '#likes-other-gravatars' );
if ( ! container ) {
break;
}
const newLayout = container.classList.contains( 'wpl-new-layout' );
const list = container.querySelector( 'ul' );
container.style.display = 'none';
list.innerHTML = '';
if ( newLayout ) {
container
.querySelectorAll( '.likes-text span' )
.forEach( item => ( item.textContent = data.totalLikesLabel ) );
} else {
container
.querySelectorAll( '.likes-text span' )
.forEach( item => ( item.textContent = data.total ) );
}
( data.likers || [] ).forEach( async ( liker, index ) => {
if ( liker.profile_URL.substr( 0, 4 ) !== 'http' ) {
// We only display gravatars with http or https schema
return;
}
const element = document.createElement( 'li' );
list.append( element );
if ( newLayout ) {
element.innerHTML = `
<a href="${ encodeURI( liker.profile_URL ) }" rel="nofollow" target="_parent" class="wpl-liker">
<img src="${ encodeURI( liker.avatar_URL ) }"
alt=""
style="width: 28px; height: 28px;" />
<span></span>
</a>
`;
} else {
element.innerHTML = `
<a href="${ encodeURI( liker.profile_URL ) }" rel="nofollow" target="_parent" class="wpl-liker">
<img src="${ encodeURI( liker.avatar_URL ) }"
alt=""
style="width: 30px; height: 30px; padding-right: 3px;" />
</a>
`;
}
// Add some extra attributes through native methods, to ensure strings are sanitized.
element.classList.add( liker.css_class );
element.querySelector( 'img' ).alt = data.avatarAltTitle.replace( '%s', liker.name );
if ( newLayout ) {
element.querySelector( 'span' ).innerText = liker.name;
}
if ( index === data.likers.length - 1 ) {
element.addEventListener( 'keydown', e => {
if ( e.key === 'Tab' && ! e.shiftKey ) {
e.preventDefault();
hideLikersPopover();
JetpackLikesPostMessage(
{ event: 'focusLikesCount', parent: data.parent },
window.frames[ 'likes-master' ]
);
}
} );
}
} );
const positionPopup = function () {
const containerStyle = getComputedStyle( container );
const isRtl = containerStyle.direction === 'rtl';
const el = document.querySelector( `*[name='${ data.parent }']` );
const rect = el.getBoundingClientRect();
const win = el.ownerDocument.defaultView;
const offset = {
top: rect.top + win.pageYOffset,
left: rect.left + win.pageXOffset,
};
let containerLeft = 0;
if ( newLayout ) {
container.style.top = offset.top + data.position.top - 1 + 'px';
if ( isRtl ) {
const visibleAvatarsCount = data && data.likers ? Math.min( data.likers.length, 5 ) : 0;
// 24px is the width of the avatar + 4px is the padding between avatars
containerLeft = offset.left + data.position.left + 24 * visibleAvatarsCount + 4;
container.style.transform = 'translateX(-100%)';
} else {
containerLeft = offset.left + data.position.left;
}
container.style.left = containerLeft + 'px';
} else {
container.style.left = offset.left + data.position.left - 10 + 'px';
container.style.top = offset.top + data.position.top - 33 + 'px';
}
// Container width - padding
const initContainerWidth = data.width - 20;
const rowLength = Math.floor( initContainerWidth / 37 );
// # of rows + (avatar + avatar padding) + text above + container padding
let height = Math.ceil( data.likers.length / rowLength ) * 37 + 17 + 22;
if ( height > 204 ) {
height = 204;
}
if ( ! newLayout ) {
// Avatars + padding
const containerWidth = rowLength * 37 + 13;
container.style.height = height + 'px';
container.style.width = containerWidth + 'px';
const listWidth = rowLength * 37;
list.style.width = listWidth + 'px';
}
// If the popup is overflows viewport width, we should show it on the next line
if ( newLayout ) {
// Push it offscreen to calculated rendered width
container.style.left = '-9999px';
container.style.display = 'block';
// If the popup exceeds the viewport width,
// flip the position of the popup.
const containerWidth = container.offsetWidth;
const containerRight = containerLeft + containerWidth;
if ( containerRight > win.innerWidth ) {
containerLeft = rect.right - containerWidth;
}
// Set the container left
container.style.left = containerLeft + 'px';
} else {
container.style.display = 'block';
}
container.setAttribute( 'aria-hidden', 'false' );
};
positionPopup();
container.focus();
const debounce = function ( func, wait ) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout( timeout );
timeout = setTimeout( function () {
func.apply( context, args );
}, wait );
};
};
const debouncedPositionPopup = debounce( positionPopup, 100 );
// Keep a reference of this function in the element itself
// so that we can destroy it later
container.__resizeHandler = debouncedPositionPopup;
// When window is resized, resize the popup.
window.addEventListener( 'resize', debouncedPositionPopup );
container.focus();
}
}
}
window.addEventListener( 'message', JetpackLikesMessageListener );
function hideLikersPopover() {
const container = document.querySelector( '#likes-other-gravatars' );
if ( container ) {
container.style.display = 'none';
container.setAttribute( 'aria-hidden', 'true' );
// Remove the resize event listener and cleanup.
const resizeHandler = container.__resizeHandler;
if ( resizeHandler ) {
window.removeEventListener( 'resize', resizeHandler );
delete container.__resizeHandler;
}
}
}
document.addEventListener( 'click', hideLikersPopover );
function JetpackLikesWidgetQueueHandler() {
var wrapperID;
if ( ! jetpackLikesMasterReady ) {
setTimeout( JetpackLikesWidgetQueueHandler, 500 );
return;
}
// Restore widgets to initial unloaded state when they are scrolled out of view.
jetpackUnloadScrolledOutWidgets();
var unloadedWidgetsInView = jetpackGetUnloadedWidgetsInView();
if ( unloadedWidgetsInView.length > 0 ) {
// Grab any unloaded widgets for a batch request
JetpackLikesBatchHandler();
}
for ( var i = 0, length = unloadedWidgetsInView.length; i <= length - 1; i++ ) {
wrapperID = unloadedWidgetsInView[ i ].id;
if ( ! wrapperID ) {
continue;
}
jetpackLoadLikeWidgetIframe( wrapperID );
}
}
function jetpackLoadLikeWidgetIframe( wrapperID ) {
if ( typeof wrapperID === 'undefined' ) {
return;
}
const wrapper = document.querySelector( '#' + wrapperID );
wrapper.querySelectorAll( 'iframe' ).forEach( iFrame => iFrame.remove() );
const placeholder = wrapper.querySelector( '.likes-widget-placeholder' );
// Post like iframe
if ( placeholder && placeholder.classList.contains( 'post-likes-widget-placeholder' ) ) {
const postLikesFrame = document.createElement( 'iframe' );
postLikesFrame.classList.add( 'post-likes-widget', 'jetpack-likes-widget' );
postLikesFrame.name = wrapper.dataset.name;
postLikesFrame.src = wrapper.dataset.src;
postLikesFrame.height = '55px';
postLikesFrame.width = '100%';
postLikesFrame.frameBorder = '0';
postLikesFrame.scrolling = 'no';
postLikesFrame.title = wrapper.dataset.title;
placeholder.after( postLikesFrame );
}
// Comment like iframe
if ( placeholder.classList.contains( 'comment-likes-widget-placeholder' ) ) {
const commentLikesFrame = document.createElement( 'iframe' );
commentLikesFrame.class = 'comment-likes-widget-frame jetpack-likes-widget-frame';
commentLikesFrame.name = wrapper.dataset.name;
commentLikesFrame.src = wrapper.dataset.src;
commentLikesFrame.height = '18px';
commentLikesFrame.width = '100%';
commentLikesFrame.frameBorder = '0';
commentLikesFrame.scrolling = 'no';
wrapper.querySelector( '.comment-like-feedback' ).after( commentLikesFrame );
jetpackCommentLikesLoadedWidgets.push( commentLikesFrame );
}
wrapper.classList.remove( 'jetpack-likes-widget-unloaded' );
wrapper.classList.add( 'jetpack-likes-widget-loading' );
wrapper.querySelector( 'iframe' ).addEventListener( 'load', e => {
JetpackLikesPostMessage(
{ event: 'loadLikeWidget', name: e.target.name, width: e.target.width },
window.frames[ 'likes-master' ]
);
wrapper.classList.remove( 'jetpack-likes-widget-loading' );
wrapper.classList.add( 'jetpack-likes-widget-loaded' );
} );
}
function jetpackGetUnloadedWidgetsInView() {
const unloadedWidgets = document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' );
return [ ...unloadedWidgets ].filter( item => jetpackIsScrolledIntoView( item ) );
}
function jetpackIsScrolledIntoView( element ) {
const top = element.getBoundingClientRect().top;
const bottom = element.getBoundingClientRect().bottom;
// Allow some slack above and bellow the fold with jetpackLikesLookAhead,
// with the aim of hiding the transition from unloaded to loaded widget from users.
return top + jetpackLikesLookAhead >= 0 && bottom <= window.innerHeight + jetpackLikesLookAhead;
}
function jetpackUnloadScrolledOutWidgets() {
for ( let i = jetpackCommentLikesLoadedWidgets.length - 1; i >= 0; i-- ) {
const currentWidgetIframe = jetpackCommentLikesLoadedWidgets[ i ];
if ( ! jetpackIsScrolledIntoView( currentWidgetIframe ) ) {
const widgetWrapper =
currentWidgetIframe &&
currentWidgetIframe.parentElement &&
currentWidgetIframe.parentElement.parentElement;
// Restore parent class to 'unloaded' so this widget can be picked up by queue manager again if needed.
widgetWrapper.classList.remove( 'jetpack-likes-widget-loaded' );
widgetWrapper.classList.remove( 'jetpack-likes-widget-loading' );
widgetWrapper.classList.add( 'jetpack-likes-widget-unloaded' );
// Bring back the loading placeholder into view.
widgetWrapper
.querySelectorAll( '.comment-likes-widget-placeholder' )
.forEach( item => ( item.style.display = 'block' ) );
// Remove it from the list of loaded widgets.
jetpackCommentLikesLoadedWidgets.splice( i, 1 );
// Remove comment like widget iFrame.
currentWidgetIframe.remove();
}
}
}
var jetpackWidgetsDelayedExec = function ( after, fn ) {
var timer;
return function () {
clearTimeout( timer );
timer = setTimeout( fn, after );
};
};
var jetpackOnScrollStopped = jetpackWidgetsDelayedExec( 250, JetpackLikesWidgetQueueHandler );
// Load initial batch of widgets, prior to any scrolling events.
JetpackLikesWidgetQueueHandler();
// Add event listener to execute queue handler after scroll.
window.addEventListener( 'scroll', jetpackOnScrollStopped, true );
@@ -0,0 +1,267 @@
/**
* Like Button toolbar button, loading text & container styles
*/
/* Master container */
#jp-post-flair {
padding-top: .5em;
}
/* Overall Sharedaddy block title */
div.sharedaddy,
#content div.sharedaddy,
#main div.sharedaddy {
clear: both;
}
div.sharedaddy h3.sd-title {
margin: 0 0 1em 0;
display: inline-block;
line-height: 1.2;
font-size: 9pt;
font-weight: bold;
}
div.sharedaddy h3.sd-title:before {
content: "";
display: block;
width: 100%;
min-width: 30px;
border-top: 1px solid #dcdcde;
margin-bottom: 1em;
}
/* Toolbar */
div.jetpack-likes-widget-wrapper {
width: 100%;
min-height: 50px; /* Previous height, 60px */
position: relative; /* Need to abs position placeholder and iframe so there isn't a jarring jump */
}
div.jetpack-likes-widget-wrapper .sd-link-color {
font-size: 12px;
}
div.jetpack-comment-likes-widget-wrapper {
width: 100%;
position: relative;
min-height: 31px;
}
div.jetpack-comment-likes-widget-wrapper iframe {
margin-bottom: 0;
}
#likes-other-gravatars {
display: none;
position: absolute;
padding: 10px 10px 12px 10px;
background-color: #2e4453;
border-width: 0;
box-shadow: 0 0 10px #2e4453;
box-shadow: 0 0 10px rgba(46,68,83,.6);
min-width: 130px;
z-index: 1000;
}
#likes-other-gravatars.wpl-new-layout {
display: none;
position: absolute;
padding: 9px 12px 10px 12px;
background-color: #fff;
border: solid 1px #dcdcde;
border-radius: 4px;
box-shadow: none;
min-width: 220px;
max-height: 240px;
height: auto;
overflow: auto;
z-index: 1000;
}
#likes-other-gravatars * {
line-height: normal;
}
#likes-other-gravatars .likes-text {
color: white;
font-size: 12px;
padding-bottom: 8px;
}
#likes-other-gravatars.wpl-new-layout .likes-text {
color: #101517;
font-size: 12px;
font-weight: 500;
padding-bottom: 8px;
}
#likes-other-gravatars ul,
#likes-other-gravatars li {
margin: 0;
padding: 0;
text-indent: 0;
list-style-type: none;
}
#likes-other-gravatars li::before {
content: "";
}
#likes-other-gravatars ul.wpl-avatars {
overflow: auto;
display: block;
max-height: 190px;
}
#likes-other-gravatars ul.wpl-avatars li {
width: 32px;
height: 32px;
float: left;
margin: 0 5px 5px 0;
}
#likes-other-gravatars.wpl-new-layout ul.wpl-avatars li {
width: 196px;
height: 28px;
float: none;
margin: 0 0 4px 0;
}
#likes-other-gravatars ul.wpl-avatars li a {
margin: 0 2px 0 0;
border-bottom: none !important;
display: block;
}
#likes-other-gravatars.wpl-new-layout ul.wpl-avatars li a {
margin: 0 2px 0 0;
border-bottom: none !important;
display: flex;
align-items: center;
gap: 8px;
text-decoration: none;
}
#likes-other-gravatars.wpl-new-layout ul.wpl-avatars li a span {
font-size: 12px;
color: #2C3338;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#likes-other-gravatars ul.wpl-avatars li a img {
background: none;
border: none;
margin: 0 !important;
padding: 0 !important;
position: static;
box-sizing: border-box;
}
#likes-other-gravatars.wpl-new-layout ul.wpl-avatars li a img {
background: none;
border: none;
border-radius: 50%;
margin: 0 !important;
padding: 1px !important;
position: static;
}
div.sd-box {
border-top: 1px solid #dcdcde;
border-top: 1px solid rgba(0,0,0,.13);
}
.entry-content .post-likes-widget, .post-likes-widget,
.comment-likes-widget {
margin: 0;
border-width: 0;
display: block;
}
/* Loading text */
.post-likes-widget-placeholder,
.comment-likes-widget-placeholder {
margin: 0;
border-width: 0;
position: relative;
}
.comment-likes-widget-placeholder {
height: 18px;
position: absolute;
display: flex;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
}
.comment-likes-widget-placeholder::before {
color: #2EA2CC;
width: 16px;
height: 16px;
content: '';
display: inline-block;
position: relative;
top: 3px;
padding-right: 5px;
background-repeat: no-repeat;
background-size: 16px 16px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect x='0' fill='none' width='24' height='24'/%3E%3Cg%3E%3Cpath fill='%232EA2CC' d='M12 2l2.582 6.953L22 9.257l-5.822 4.602L18.18 21 12 16.89 5.82 21l2.002-7.14L2 9.256l7.418-.304'/%3E%3C/g%3E%3C/svg%3E");
}
.post-likes-widget-placeholder .button {
display: none; /* Let's not show a dummy like button, let's just make a great button experience once it's loaded */
}
.post-likes-widget-placeholder .button span {
}
.post-likes-widget-placeholder .loading,
.comment-likes-widget-placeholder .loading {
color: #999;
font-size: 12px;
}
.comment-likes-widget-placeholder .loading {
padding-left: 5px;
margin-top: 4px;
align-self: center;
color: #4E4E4E;
}
/* Like Special cases (display on it's own) */
div.sharedaddy.sd-like-enabled .sd-like h3 {
display: none;
}
div.sharedaddy.sd-like-enabled .sd-like .post-likes-widget {
width: 100%;
float: none;
position: absolute; /* Need to abs position placeholder and iframe so there isn't a jarring jump */
top: 0;
}
.comment-likes-widget {
width: 100%;
}
/* Make ratings block. @todo: make !important unnecessary by removing inline style */
.pd-rating,
.cs-rating {
display: block !important;
}
/* Hide G+ title */
.sd-gplus .sd-title {
display: none;
}
@media print {
.jetpack-likes-widget-wrapper {
display: none;
}
}
@@ -0,0 +1,31 @@
<?php
/**
* Module Name: Markdown
* Module Description: Write posts or pages in plain-text Markdown syntax
* Sort Order: 31
* First Introduced: 2.8
* Requires Connection: No
* Auto Activate: No
* Module Tags: Writing
* Feature: Writing
* Additional Search Queries: md, markdown
*
* @package automattic/jetpack
*/
// Require the markdown class file.
require __DIR__ . '/markdown/easy-markdown.php';
/**
* Remove checkbox set in modules/markdown/easy-markdown.php.
* We don't just remove the register_setting call there because the checkbox is
* needed on WordPress.com, where the file is sync'ed verbatim.
*/
function jetpack_markdown_posting_always_on() {
// why oh why isn't there a remove_settings_field?
global $wp_settings_fields;
if ( isset( $wp_settings_fields['writing']['default'][ WPCom_Markdown::POST_OPTION ] ) ) {
unset( $wp_settings_fields['writing']['default'][ WPCom_Markdown::POST_OPTION ] );
}
}
add_action( 'admin_init', 'jetpack_markdown_posting_always_on', 11 );
@@ -0,0 +1,884 @@
<?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Plugin URI: https://automattic.com/
* Plugin Name: Easy Markdown
* Description: Write in Markdown, publish in WordPress
* Version: 0.1
* Author: Matt Wiebe
* Author URI: https://automattic.com/
* Text Domain: jetpack
*
* @package automattic/jetpack
*/
/**
* Copyright (c) Automattic. All rights reserved.
*
* Released under the GPL license
* https://www.opensource.org/licenses/gpl-license.php
*
* This is an add-on for WordPress
* https://wordpress.org/
*
* **********************************************************************
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* **********************************************************************
*/
/**
* WPCom_Markdown class.
*/
class WPCom_Markdown {
const POST_OPTION = 'wpcom_publish_posts_with_markdown';
const COMMENT_OPTION = 'wpcom_publish_comments_with_markdown';
const POST_TYPE_SUPPORT = 'wpcom-markdown';
const IS_MD_META = '_wpcom_is_markdown';
/**
* Our markdown parser.
*
* @var WPCom_GHF_Markdown_Parser
*/
private static $parser;
/**
* An instance of the markdown class.
*
* @var WPCom_Markdown
*/
private static $instance;
/**
* To ensure that our munged posts over xml-rpc are removed from the cache.
*
* @var array
*/
public $posts_to_uncache = array();
/**
* Posts and parents to monitor.
*
* @var array
*/
private $monitoring = array(
'post' => array(),
'parent' => array(),
);
/**
* Yay singletons!
*
* @return object WPCom_Markdown instance
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Kicks things off on `init` action
*/
public function load() {
$this->add_default_post_type_support();
$this->maybe_load_actions_and_filters();
if ( defined( 'REST_API_REQUEST' ) && REST_API_REQUEST ) {
// phpcs:ignore WPCUT.SwitchBlog.SwitchBlog -- wpcom flags **every** use of switch_blog, apparently expecting valid instances to ignore or suppress the sniff.
add_action( 'switch_blog', array( $this, 'maybe_load_actions_and_filters' ), 10, 2 );
}
add_action( 'admin_init', array( $this, 'register_setting' ) );
add_action( 'admin_init', array( $this, 'maybe_unload_for_bulk_edit' ) );
if ( current_theme_supports( 'o2' ) || class_exists( 'P2' ) ) {
$this->add_o2_helpers();
}
}
/**
* If we're in a bulk edit session, unload so that we don't lose our markdown metadata
*/
public function maybe_unload_for_bulk_edit() {
if ( isset( $_REQUEST['bulk_edit'] ) && $this->is_posting_enabled() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$this->unload_markdown_for_posts();
}
}
/**
* Called on init and fires on switch_blog to decide if our actions and filters
* should be running.
*
* @param int|null $new_blog_id New blog ID.
* @param int|null $old_blog_id Old blog ID.
* @return null
*/
public function maybe_load_actions_and_filters( $new_blog_id = null, $old_blog_id = null ) {
// When WP sites are being installed, the options table is not available yet.
if ( function_exists( 'wp_installing' ) && wp_installing() ) {
return;
}
// If this is a switch_to_blog call, and the blog isn't changing, we'll already be loaded.
if ( $new_blog_id && $new_blog_id === $old_blog_id ) {
return;
}
if ( $this->is_posting_enabled() ) {
$this->load_markdown_for_posts();
} else {
$this->unload_markdown_for_posts();
}
if ( $this->is_commenting_enabled() ) {
$this->load_markdown_for_comments();
} else {
$this->unload_markdown_for_comments();
}
}
/**
* Set up hooks for enabling Markdown conversion on posts
*/
public function load_markdown_for_posts() {
add_filter( 'wp_kses_allowed_html', array( $this, 'wp_kses_allowed_html' ), 10, 2 );
add_action( 'after_wp_tiny_mce', array( $this, 'after_wp_tiny_mce' ) );
add_action( 'wp_insert_post', array( $this, 'wp_insert_post' ) );
add_filter( 'wp_insert_post_data', array( $this, 'wp_insert_post_data' ), 10, 2 );
add_filter( 'edit_post_content', array( $this, 'edit_post_content' ), 10, 2 );
add_filter( 'edit_post_content_filtered', array( $this, 'edit_post_content_filtered' ), 10, 2 );
add_action( 'wp_restore_post_revision', array( $this, 'wp_restore_post_revision' ), 10, 2 );
add_filter( '_wp_post_revision_fields', array( $this, 'wp_post_revision_fields' ) );
add_action( 'xmlrpc_call', array( $this, 'xmlrpc_actions' ) );
add_filter( 'content_save_pre', array( $this, 'preserve_code_blocks' ), 1 );
if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) {
$this->check_for_early_methods();
}
}
/**
* Removes hooks to disable Markdown conversion on posts
*/
public function unload_markdown_for_posts() {
remove_filter( 'wp_kses_allowed_html', array( $this, 'wp_kses_allowed_html' ) );
remove_action( 'after_wp_tiny_mce', array( $this, 'after_wp_tiny_mce' ) );
remove_action( 'wp_insert_post', array( $this, 'wp_insert_post' ) );
remove_filter( 'wp_insert_post_data', array( $this, 'wp_insert_post_data' ), 10 );
remove_filter( 'edit_post_content', array( $this, 'edit_post_content' ), 10 );
remove_filter( 'edit_post_content_filtered', array( $this, 'edit_post_content_filtered' ), 10 );
remove_action( 'wp_restore_post_revision', array( $this, 'wp_restore_post_revision' ), 10 );
remove_filter( '_wp_post_revision_fields', array( $this, 'wp_post_revision_fields' ) );
remove_action( 'xmlrpc_call', array( $this, 'xmlrpc_actions' ) );
remove_filter( 'content_save_pre', array( $this, 'preserve_code_blocks' ), 1 );
}
/**
* Set up hooks for enabling Markdown conversion on comments
*/
protected function load_markdown_for_comments() {
// Use priority 9 so that Markdown runs before KSES, which can clean up
// any munged HTML.
add_filter( 'pre_comment_content', array( $this, 'pre_comment_content' ), 9 );
}
/**
* Removes hooks to disable Markdown conversion
*/
protected function unload_markdown_for_comments() {
remove_filter( 'pre_comment_content', array( $this, 'pre_comment_content' ), 9 );
}
/**
* The o2 plugin does some of what we do. Let's take precedence.
*/
public function add_o2_helpers() {
if ( $this->is_posting_enabled() ) {
add_filter( 'content_save_pre', array( $this, 'o2_escape_lists' ), 1 );
}
add_filter( 'o2_preview_post', array( $this, 'o2_preview_post' ) );
add_filter( 'o2_preview_comment', array( $this, 'o2_preview_comment' ) );
add_filter( 'wpcom_markdown_transform_pre', array( $this, 'o2_unescape_lists' ) );
add_filter( 'wpcom_untransformed_content', array( $this, 'o2_unescape_lists' ) );
}
/**
* If Markdown is enabled for posts on this blog, filter the text for o2 previews
*
* @param string $text Post text.
* @return string Post text transformed through the magic of Markdown
*/
public function o2_preview_post( $text ) {
if ( $this->is_posting_enabled() ) {
$text = $this->transform( $text, array( 'unslash' => false ) );
}
return $text;
}
/**
* If Markdown is enabled for comments on this blog, filter the text for o2 previews
*
* @param string $text Comment text.
* @return string Comment text transformed through the magic of Markdown
*/
public function o2_preview_comment( $text ) {
if ( $this->is_commenting_enabled() ) {
$text = $this->transform( $text, array( 'unslash' => false ) );
}
return $text;
}
/**
* Escapes lists so that o2 doesn't trounce them
*
* @param string $text Post/comment text.
* @return string Text escaped with HTML entity for asterisk.
*/
public function o2_escape_lists( $text ) {
return preg_replace( '/^\\* /um', '&#42; ', $text );
}
/**
* Unescapes the token we inserted on o2_escape_lists
*
* @param string $text Post/comment text with HTML entities for asterisks.
* @return string Text with the HTML entity removed
*/
public function o2_unescape_lists( $text ) {
return preg_replace( '/^[&]\#042; /um', '* ', $text );
}
/**
* Preserve code blocks from being munged by KSES before they have a chance
*
* @param string $text post content.
* @return string post content with code blocks escaped.
*/
public function preserve_code_blocks( $text ) {
return $this->get_parser()->codeblock_preserve( $text );
}
/**
* Remove KSES if it's there. Store the result to manually invoke later if needed.
*/
public function maybe_remove_kses() {
// Filters return true if they existed before you removed them.
if ( $this->is_posting_enabled() ) {
$this->kses = remove_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' ) && remove_filter( 'content_save_pre', 'wp_filter_post_kses' );
}
}
/**
* Add our Writing and Discussion settings.
*/
public function register_setting() {
add_settings_field( self::POST_OPTION, __( 'Markdown', 'jetpack' ), array( $this, 'post_field' ), 'writing' );
register_setting( 'writing', self::POST_OPTION, array( $this, 'sanitize_setting' ) );
add_settings_field( self::COMMENT_OPTION, __( 'Markdown', 'jetpack' ), array( $this, 'comment_field' ), 'discussion' );
register_setting( 'discussion', self::COMMENT_OPTION, array( $this, 'sanitize_setting' ) );
}
/**
* Sanitize setting. Don't really want to store "on" value, so we'll store "1" instead!
*
* @param string $input Value received by settings API via $_POST.
* @return bool Cast to boolean.
*/
public function sanitize_setting( $input ) {
return (bool) $input;
}
/**
* Prints HTML for the Writing setting
*/
public function post_field() {
printf(
'<label><input name="%1$s" id="%1$s" type="checkbox"%2$s /> %3$s</label><p class="description">%4$s</p>',
esc_attr( self::POST_OPTION ),
checked( $this->is_posting_enabled(), true, false ),
esc_html__( 'Use Markdown for posts and pages.', 'jetpack' ),
sprintf( '<a href="%s">%s</a>', esc_url( $this->get_support_url() ), esc_html__( 'Learn more about Markdown.', 'jetpack' ) )
);
}
/**
* Prints HTML for the Discussion setting
*/
public function comment_field() {
printf(
'<label><input name="%1$s" id="%1$s" type="checkbox"%2$s /> %3$s</label><p class="description">%4$s</p>',
esc_attr( self::COMMENT_OPTION ),
checked( $this->is_commenting_enabled(), true, false ),
esc_html__( 'Use Markdown for comments.', 'jetpack' ),
sprintf( '<a href="%s">%s</a>', esc_url( $this->get_support_url() ), esc_html__( 'Learn more about Markdown.', 'jetpack' ) )
);
}
/**
* Get the support url for Markdown
*
* @uses apply_filters
* @return string support url
*/
protected function get_support_url() {
/**
* Filter the Markdown support URL.
*
* @module markdown
*
* @since 2.8.0
*
* @param string $url Markdown support URL.
*/
return apply_filters( 'easy_markdown_support_url', 'https://en.support.wordpress.com/markdown-quick-reference/' );
}
/**
* Is Mardown conversion for posts enabled?
*
* @return boolean
*/
public function is_posting_enabled() {
return (bool) Jetpack_Options::get_option_and_ensure_autoload( self::POST_OPTION, '' );
}
/**
* Is Markdown conversion for comments enabled?
*
* @return boolean
*/
public function is_commenting_enabled() {
return (bool) Jetpack_Options::get_option_and_ensure_autoload( self::COMMENT_OPTION, '' );
}
/**
* Check if a $post_id has Markdown enabled
*
* @param int $post_id A post ID.
* @return boolean
*/
public function is_markdown( $post_id ) {
return get_metadata( 'post', $post_id, self::IS_MD_META, true );
}
/**
* Set Markdown as enabled on a post_id. We skip over update_postmeta so we
* can sneakily set metadata on post revisions, which we need.
*
* @param int $post_id A post ID.
* @return bool The metadata was successfully set.
*/
protected function set_as_markdown( $post_id ) {
return update_metadata( 'post', $post_id, self::IS_MD_META, true );
}
/**
* Get our Markdown parser object, optionally requiring all of our needed classes and
* instantiating our parser.
*
* @return object WPCom_GHF_Markdown_Parser instance.
*/
public function get_parser() {
if ( ! self::$parser ) {
require_once JETPACK__PLUGIN_DIR . '/_inc/lib/markdown.php';
self::$parser = new WPCom_GHF_Markdown_Parser();
}
return self::$parser;
}
/**
* We don't want Markdown conversion all over the place.
*/
public function add_default_post_type_support() {
add_post_type_support( 'post', self::POST_TYPE_SUPPORT );
add_post_type_support( 'page', self::POST_TYPE_SUPPORT );
add_post_type_support( 'revision', self::POST_TYPE_SUPPORT );
}
/**
* Figure out the post type of the post screen we're on
*
* @deprecated since 10.8
* @return string Current post_type
*/
protected function get_post_screen_post_type() {
_deprecated_function( __METHOD__, 'jetpack-10.8', '' );
global $pagenow;
$post_type = filter_input( INPUT_GET, 'post_type', FILTER_UNSAFE_RAW );
$post_id = filter_input( INPUT_GET, 'post', FILTER_SANITIZE_NUMBER_INT );
if ( 'post-new.php' === $pagenow ) {
return ! empty( $post_type ) ? $post_type : 'post';
}
if ( $post_id ) {
$post_type = get_post_type( $post_id );
}
return ! empty( $post_type ) ? $post_type : 'post';
}
/**
* Swap post_content and post_content_filtered for editing
*
* @param string $content Post content.
* @param int $id post ID.
* @return string Swapped content
*/
public function edit_post_content( $content, $id ) {
if ( $this->is_markdown( $id ) ) {
$post = get_post( $id );
if ( $post && ! empty( $post->post_content_filtered ) ) {
$post = $this->swap_for_editing( $post );
return $post->post_content;
}
}
return $content;
}
/**
* Swap post_content_filtered and post_content for editing
*
* @param string $content Post content_filtered.
* @param int $id post ID.
* @return string Swapped content
*/
public function edit_post_content_filtered( $content, $id ) {
// if markdown was disabled, let's turn this off.
if ( ! $this->is_posting_enabled() && $this->is_markdown( $id ) ) {
$post = get_post( $id );
if ( $post && ! empty( $post->post_content_filtered ) ) {
$content = '';
}
}
return $content;
}
/**
* Some tags are allowed to have a 'markdown' attribute, allowing them to contain Markdown.
* We need to tell KSES about those tags.
*
* @param array $tags List of tags that KSES allows.
* @param string $context The context that KSES is allowing these tags.
* @return array The tags that KSES allows, with our extra 'markdown' parameter where necessary.
*/
public function wp_kses_allowed_html( $tags, $context ) {
if ( 'post' !== $context ) {
return $tags;
}
$re = '/' . $this->get_parser()->contain_span_tags_re . '/';
foreach ( $tags as $tag => $attributes ) {
// In case other filters have changed the value to a non-array, we skip it.
if ( ! is_array( $attributes ) ) {
continue;
}
if ( preg_match( $re, $tag ) ) {
$attributes['markdown'] = true;
$tags[ $tag ] = $attributes;
}
}
return $tags;
}
/**
* TinyMCE needs to know not to strip the 'markdown' attribute. Unfortunately, it doesn't
* really offer a nice API for allowed attributes, so we have to manually add it
* to the schema instead.
*/
public function after_wp_tiny_mce() {
?>
<script type="text/javascript">
jQuery( function() {
( 'undefined' !== typeof tinymce ) && tinymce.on( 'AddEditor', function( event ) {
event.editor.on( 'BeforeSetContent', function( event ) {
var editor = event.target;
Object.keys( editor.schema.elements ).forEach( function( key, index ) {
editor.schema.elements[ key ].attributes['markdown'] = {};
editor.schema.elements[ key ].attributesOrder.push( 'markdown' );
} );
} );
}, true );
} );
</script>
<?php
}
/**
* Magic happens here. Markdown is converted and stored on post_content. Original Markdown is stored
* in post_content_filtered so that we can continue editing as Markdown.
*
* @param array $post_data The post data that will be inserted into the DB. Slashed.
* @param array $postarr All the stuff that was in $_POST.
* @return array $post_data with post_content and post_content_filtered modified
*/
public function wp_insert_post_data( $post_data, $postarr ) {
// $post_data array is slashed!
$post_id = isset( $postarr['ID'] ) ? $postarr['ID'] : false;
// bail early if markdown is disabled or this post type is unsupported.
if ( ! $this->is_posting_enabled() || ! post_type_supports( $post_data['post_type'], self::POST_TYPE_SUPPORT ) ) {
// it's disabled, but maybe this *was* a markdown post before.
if ( $this->is_markdown( $post_id ) && ! empty( $post_data['post_content_filtered'] ) ) {
$post_data['post_content_filtered'] = '';
}
// we have no context to determine supported post types in the `post_content_pre` hook,
// which already ran to sanitize code blocks. Undo that.
$post_data['post_content'] = $this->get_parser()->codeblock_restore( $post_data['post_content'] );
return $post_data;
}
// rejigger post_content and post_content_filtered
// revisions are already in the right place, except when we're restoring, but that's taken care of elsewhere
// also prevent quick edit feature from overriding already-saved markdown (issue https://github.com/Automattic/jetpack/issues/636).
if ( 'revision' !== $post_data['post_type'] && ! isset( $_POST['_inline_edit'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
/**
* Filter the original post content passed to Markdown.
*
* @module markdown
*
* @since 2.8.0
*
* @param string $post_data['post_content'] Untransformed post content.
*/
$post_data['post_content_filtered'] = apply_filters( 'wpcom_untransformed_content', $post_data['post_content'] );
$post_data['post_content'] = $this->transform( $post_data['post_content'], array( 'id' => $post_id ) );
/** This filter is already documented in core/wp-includes/default-filters.php */
$post_data['post_content'] = apply_filters( 'content_save_pre', $post_data['post_content'] );
} elseif ( str_starts_with( $post_data['post_name'], $post_data['post_parent'] . '-autosave' ) ) {
// autosaves for previews are weird.
/** This filter is already documented in modules/markdown/easy-markdown.php */
$post_data['post_content_filtered'] = apply_filters( 'wpcom_untransformed_content', $post_data['post_content'] );
$post_data['post_content'] = $this->transform( $post_data['post_content'], array( 'id' => $post_data['post_parent'] ) );
/** This filter is already documented in core/wp-includes/default-filters.php */
$post_data['post_content'] = apply_filters( 'content_save_pre', $post_data['post_content'] );
}
// set as markdown on the wp_insert_post hook later.
if ( $post_id ) {
$this->monitoring['post'][ $post_id ] = true;
} else {
$this->monitoring['content'] = wp_unslash( $post_data['post_content'] );
}
if ( 'revision' === $postarr['post_type'] && $this->is_markdown( $postarr['post_parent'] ) ) {
$this->monitoring['parent'][ $postarr['post_parent'] ] = true;
}
return $post_data;
}
/**
* Calls on wp_insert_post action, after wp_insert_post_data. This way we can
* still set postmeta on our revisions after it's all been deleted.
*
* @param int $post_id The post ID that has just been added/updated.
*/
public function wp_insert_post( $post_id ) {
$post_parent = get_post_field( 'post_parent', $post_id );
// this didn't have an ID yet. Compare the content that was just saved.
if ( isset( $this->monitoring['content'] ) && get_post_field( 'post_content', $post_id ) === $this->monitoring['content'] ) {
unset( $this->monitoring['content'] );
$this->set_as_markdown( $post_id );
}
if ( isset( $this->monitoring['post'][ $post_id ] ) ) {
unset( $this->monitoring['post'][ $post_id ] );
$this->set_as_markdown( $post_id );
} elseif ( isset( $this->monitoring['parent'][ $post_parent ] ) ) {
unset( $this->monitoring['parent'][ $post_parent ] );
$this->set_as_markdown( $post_id );
}
}
/**
* Run a comment through Markdown. Easy peasy.
*
* @param string $content - the content.
* @return string
*/
public function pre_comment_content( $content ) {
return $this->transform(
$content,
array(
'id' => $this->comment_hash( $content ),
)
);
}
/**
* Return a comment hash.
*
* @param string $content - the content of the comment.
*/
protected function comment_hash( $content ) {
return 'c-' . substr( md5( $content ), 0, 8 );
}
/**
* Markdown conversion. Some DRYness for repetitive tasks.
*
* @param string $text Content to be run through Markdown.
* @param array $args Arguments, with keys:
* id: provide a string to prefix footnotes with a unique identifier
* unslash: when true, expects and returns slashed data
* decode_code_blocks: when true, assume that text in fenced code blocks is already
* HTML encoded and should be decoded before being passed to Markdown, which does
* its own encoding.
* @return string Markdown-processed content
*/
public function transform( $text, $args = array() ) {
// If this contains Gutenberg content, let's keep it intact.
if ( has_blocks( $text ) ) {
return $text;
}
$args = wp_parse_args(
$args,
array(
'id' => false,
'unslash' => true,
'decode_code_blocks' => ! $this->get_parser()->use_code_shortcode,
)
);
// probably need to unslash.
if ( $args['unslash'] ) {
$text = wp_unslash( $text );
}
/**
* Filter the content to be run through Markdown, before it's transformed by Markdown.
*
* @module markdown
*
* @since 2.8.0
*
* @param string $text Content to be run through Markdown
* @param array $args Array of Markdown options.
*/
$text = apply_filters( 'wpcom_markdown_transform_pre', $text, $args );
// ensure our paragraphs are separated.
$text = str_replace( array( '</p><p>', "</p>\n<p>" ), "</p>\n\n<p>", $text );
// visual editor likes to add <p>s. Buh-bye.
$text = $this->get_parser()->unp( $text );
// sometimes we get an encoded > at start of line, breaking blockquotes.
$text = preg_replace( '/^&gt;/m', '>', $text );
// prefixes are because we need to namespace footnotes by post_id.
$this->get_parser()->fn_id_prefix = $args['id'] ? $args['id'] . '-' : '';
// If we're not using the code shortcode, prevent over-encoding.
if ( $args['decode_code_blocks'] ) {
$text = $this->get_parser()->codeblock_restore( $text );
}
// Transform it!
$text = $this->get_parser()->transform( $text );
// Fix footnotes - kses doesn't like the : IDs it supplies.
$text = preg_replace( '/((id|href)="#?fn(ref)?):/', '$1-', $text );
// Markdown inserts extra spaces to make itself work. Buh-bye.
$text = rtrim( $text );
/**
* Filter the content to be run through Markdown, after it was transformed by Markdown.
*
* @module markdown
*
* @since 2.8.0
*
* @param string $text Content to be run through Markdown
* @param array $args Array of Markdown options.
*/
$text = apply_filters( 'wpcom_markdown_transform_post', $text, $args );
// probably need to re-slash.
if ( $args['unslash'] ) {
$text = wp_slash( $text );
}
return $text;
}
/**
* Shows Markdown in the Revisions screen, and ensures that post_content_filtered
* is maintained on revisions
*
* @param array $fields Post fields pertinent to revisions.
*/
public function wp_post_revision_fields( $fields ) {
$fields['post_content_filtered'] = __( 'Markdown content', 'jetpack' );
return $fields;
}
/**
* Do some song and dance to keep all post_content and post_content_filtered content
* in the expected place when a post revision is restored.
*
* @param int $post_id The post ID have a restore done to it.
* @param int $revision_id The revision ID being restored.
*/
public function wp_restore_post_revision( $post_id, $revision_id ) {
if ( $this->is_markdown( $revision_id ) ) {
$revision = get_post( $revision_id, ARRAY_A );
$post = get_post( $post_id, ARRAY_A );
$post['post_content'] = $revision['post_content_filtered']; // Yes, we put it in post_content, because our wp_insert_post_data() expects that.
// set this flag so we can restore the post_content_filtered on the last revision later.
$this->monitoring['restore'] = true;
// let's not make a revision of our fixing update.
add_filter( 'wp_revisions_to_keep', '__return_false', 99 );
wp_update_post( $post );
$this->fix_latest_revision_on_restore( $post_id );
remove_filter( 'wp_revisions_to_keep', '__return_false', 99 );
}
}
/**
* We need to ensure the last revision has Markdown, not HTML in its post_content_filtered
* column after a restore.
*
* @param int $post_id The post ID that was just restored.
*/
protected function fix_latest_revision_on_restore( $post_id ) {
global $wpdb;
$post = get_post( $post_id );
$last_revision = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_type = 'revision' AND post_parent = %d ORDER BY ID DESC", $post->ID ) );
$last_revision->post_content_filtered = $post->post_content_filtered;
wp_insert_post( (array) $last_revision );
}
/**
* Kicks off magic for an XML-RPC session. We want to keep editing Markdown
* and publishing HTML.
*
* @param string $xmlrpc_method The current XML-RPC method.
*/
public function xmlrpc_actions( $xmlrpc_method ) {
switch ( $xmlrpc_method ) {
case 'metaWeblog.getRecentPosts':
case 'wp.getPosts':
case 'wp.getPages':
add_action( 'parse_query', array( $this, 'make_filterable' ), 10, 1 );
break;
case 'wp.getPost':
$this->prime_post_cache();
break;
}
}
/**
* Function metaWeblog.getPost and wp.getPage fire xmlrpc_call action *after* get_post() is called.
* So, we have to detect those methods and prime the post cache early.
*
* @return null
*/
protected function check_for_early_methods() {
$raw_post_data = file_get_contents( 'php://input' );
if ( ! str_contains( $raw_post_data, 'metaWeblog.getPost' )
&& ! str_contains( $raw_post_data, 'wp.getPage' ) ) {
return;
}
include_once ABSPATH . WPINC . '/class-IXR.php';
$message = new IXR_Message( $raw_post_data );
$message->parse();
$post_id_position = 'metaWeblog.getPost' === $message->methodName ? 0 : 1; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$this->prime_post_cache( $message->params[ $post_id_position ] );
}
/**
* Prime the post cache with swapped post_content. This is a sneaky way of getting around
* the fact that there are no good hooks to call on the *.getPost xmlrpc methods.
*
* @param bool $post_id - the post ID that we're priming.
*/
private function prime_post_cache( $post_id = false ) {
global $wp_xmlrpc_server;
if ( ! $post_id ) {
$post_id = $wp_xmlrpc_server->message->params[3];
}
// prime the post cache.
if ( $this->is_markdown( $post_id ) ) {
$post = get_post( $post_id );
if ( ! empty( $post->post_content_filtered ) ) {
wp_cache_delete( $post->ID, 'posts' );
$post = $this->swap_for_editing( $post );
wp_cache_add( $post->ID, $post, 'posts' );
$this->posts_to_uncache[] = $post_id;
}
}
// uncache munged posts if using a persistent object cache.
if ( wp_using_ext_object_cache() ) {
add_action( 'shutdown', array( $this, 'uncache_munged_posts' ) );
}
}
/**
* Swaps `post_content_filtered` back to `post_content` for editing purposes.
*
* @param object $post WP_Post object.
* @return object WP_Post object with swapped `post_content_filtered` and `post_content`.
*/
protected function swap_for_editing( $post ) {
$markdown = $post->post_content_filtered;
// unencode encoded code blocks.
$markdown = $this->get_parser()->codeblock_restore( $markdown );
// restore beginning of line blockquotes.
$markdown = preg_replace( '/^&gt; /m', '> ', $markdown );
$post->post_content_filtered = $post->post_content;
$post->post_content = $markdown;
return $post;
}
/**
* We munge the post cache to serve proper markdown content to XML-RPC clients.
* Uncache these after the XML-RPC session ends.
*/
public function uncache_munged_posts() {
// $this context gets lost in testing sometimes. Weird.
foreach ( self::get_instance()->posts_to_uncache as $post_id ) {
wp_cache_delete( $post_id, 'posts' );
}
}
/**
* Since *.(get)?[Rr]ecentPosts calls get_posts with suppress filters on, we need to
* turn them back on so that we can swap things for editing.
*
* @param object $wp_query WP_Query object.
*/
public function make_filterable( $wp_query ) {
$wp_query->set( 'suppress_filters', false );
add_action( 'the_posts', array( $this, 'the_posts' ), 10, 2 );
}
/**
* Swaps post_content and post_content_filtered for editing.
*
* @param array $posts Posts returned by the just-completed query.
* @return array Modified $posts
*/
public function the_posts( $posts ) {
foreach ( $posts as $key => $post ) {
if ( $this->is_markdown( $post->ID ) && ! empty( $posts[ $key ]->post_content_filtered ) ) {
$markdown = $posts[ $key ]->post_content_filtered;
$posts[ $key ]->post_content_filtered = $posts[ $key ]->post_content;
$posts[ $key ]->post_content = $markdown;
}
}
return $posts;
}
/**
* Singleton silence is golden
*/
private function __construct() {}
}
add_action( 'init', array( WPCom_Markdown::get_instance(), 'load' ) );
@@ -0,0 +1,11 @@
<?php
/**
* The WordPress.com Toolbar and Dashboard customizations Module has been deprecated and this file
* will be removed in an upcoming Jetpack release.
*
* @deprecated $$$next-version$
*
* @package automattic/jetpack
*/
_deprecated_file( __FILE__, 'jetpack-13.9' );
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,81 @@
<?php
/**
* Load module code that is needed even when a module isn't active.
* For example, if a module shouldn't be activatable unless certain conditions are met,
* the code belongs in this file.
*
* @package automattic/jetpack
*/
/**
* Features available all the time:
* - When in offline mode.
* - When connected to WordPress.com.
*/
$tools = array(
// Always loaded, but only registered if theme supports it.
'geo-location.php',
// Those oEmbed providers are always available.
'shortcodes/facebook.php',
'shortcodes/others.php',
// Theme Tools.
'theme-tools.php',
'theme-tools/social-links.php',
'theme-tools/featured-content.php',
'theme-tools/responsive-videos.php',
'theme-tools/site-logo.php',
'theme-tools/site-breadcrumbs.php',
'theme-tools/social-menu.php',
'theme-tools/content-options.php',
// Needed for VideoPress, so videos keep working in existing posts/pages when the module is deactivated.
'videopress/class.videopress-gutenberg.php',
);
// Some features are only available when connected to WordPress.com.
$connected_tools = array(
'plugin-search.php',
'scan/scan.php', // Shows Jetpack Scan alerts in the admin bar if threats found.
'simple-payments/simple-payments.php',
'wpcom-tos/wpcom-tos.php',
// These oEmbed providers are available when connected to WordPress.com.
// Starting from 2020-10-24, they need an authentication token, and that token is stored on WordPress.com.
// More information: https://developers.facebook.com/docs/instagram/oembed/.
'shortcodes/instagram.php',
);
// Add connected features to our existing list if the site is currently connected.
if ( Jetpack::is_connection_ready() ) {
$tools = array_merge( $tools, $connected_tools );
}
/**
* Filter extra tools (not modules) to include.
*
* @since 2.4.0
* @since 5.4.0 can be used in multisite when Jetpack is not connected to WordPress.com and not in offline mode.
*
* @param array $tools Array of extra tools to include.
*/
$jetpack_tools_to_include = apply_filters( 'jetpack_tools_to_include', $tools );
if ( ! empty( $jetpack_tools_to_include ) ) {
foreach ( $jetpack_tools_to_include as $tool ) {
if ( file_exists( JETPACK__PLUGIN_DIR . '/modules/' . $tool ) ) {
require_once JETPACK__PLUGIN_DIR . '/modules/' . $tool;
}
}
}
/**
* Add the "(Jetpack)" suffix to the widget names
*
* @param string $widget_name Widget name.
*/
function jetpack_widgets_add_suffix( $widget_name ) {
return sprintf(
/* Translators: Placeholder is the name of a widget. */
__( '%s (Jetpack)', 'jetpack' ),
$widget_name
);
}
add_filter( 'jetpack_widget_name', 'jetpack_widgets_add_suffix' );
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,861 @@
<?php
/**
* "Learn More" information blocks for all modules live in this file.
*
* Each module must include 2 functions:
* - The first one creates a button where users can find more information about the module.
* It is hooked into `jetpack_learn_more_button_ . $module`
* - The second creates a information block.
* It is hooked into `jetpack_module_more_info_ . $module`
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Redirect;
/**
* VaultPress (stub) support link.
*/
function vaultpress_jetpack_load_more_link() {
echo 'https://help.vaultpress.com/get-to-know/';
}
add_filter( 'jetpack_learn_more_button_vaultpress', 'vaultpress_jetpack_load_more_link' );
/**
* VaultPress (stub) description.
*/
function vaultpress_jetpack_more_info() {
esc_html_e(
'We keep a daily or real-time backup of your site so that when mistakes or accidents occur, restoring your
site to any location takes a matter of minutes. Your sites files are regularly scanned for unauthorized or
suspicious modifications that could compromise your security and data. In many cases, we can fix them
automatically (and will notify you). When we cant, we provide you with expert support.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_vaultpress', 'vaultpress_jetpack_more_info' );
/**
* Gravatar Hovercards support link.
*/
function grofiles_load_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-gravatar-hovercards' ) );
}
add_filter( 'jetpack_learn_more_button_gravatar-hovercards', 'grofiles_load_more_link' );
/**
* Gravatar Hovercards description.
*/
function grofiles_more_info() {
esc_html_e(
'Enhance plain Gravatar images with information about a person (including a name,
bio, pictures, and contact info) when they leave a comment on one of your posts.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_gravatar-hovercards', 'grofiles_more_info' );
/**
* Shortcodes support link.
*/
function jetpack_shortcodes_load_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-shortcode-embeds' ) );
}
add_filter( 'jetpack_learn_more_button_shortcodes', 'jetpack_shortcodes_load_more_link' );
/**
* Shortcodes description.
*/
function jetpack_shortcodes_more_info() {
esc_html_e(
'Easily and safely embed media from YouTube, Facebook, Flickr, Vimeo, Instagram,
Google Maps, SlideShare, Vine, SoundCloud, and more. Just enter the appropriate shortcode directly into the
editor and click “Publish.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_shortcodes', 'jetpack_shortcodes_more_info' );
/**
* Shortlinks support link.
*/
function wpme_load_more_link() {
echo 'https://wp.me/p1moTy-DL';
}
add_filter( 'jetpack_learn_more_button_shortlinks', 'wpme_load_more_link' );
/**
* Shortlinks description
*/
function wpme_more_info() {
esc_html_e(
'Grab short and simple links to your posts and pages using the compact wp.me domain name. Perfect
for use on Twitter, Facebook, and in text messages where every character counts.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_shortlinks', 'wpme_more_info' );
/**
* Jetpack Stats support link.
*/
function stats_load_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-wordpress-com-stats' ) );
}
add_filter( 'jetpack_learn_more_button_stats', 'stats_load_more_link' );
/**
* Jetpack Stats description.
*/
function stats_more_info() {
esc_html_e(
'Simple and concise statistics about your traffic. Jetpack Stats collects data on page views, likes, comments,
locations, and top posts. View them in your dashboard or on WordPress.com.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_stats', 'stats_more_info' );
/**
* Publicize support link.
*/
function publicize_load_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-publicize' ) );
}
add_filter( 'jetpack_learn_more_button_publicize', 'publicize_load_more_link' );
/**
* Publicize description.
*/
function publicize_more_info() {
esc_html_e(
'Automatically share and promote newly published posts to Facebook, Tumblr,
and LinkedIn. You can add connections for yourself or for all users on your site.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_publicize', 'publicize_more_info' );
/**
* Notifications
*/
function notes_load_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-notifications' ) );
}
add_filter( 'jetpack_learn_more_button_notes', 'notes_load_more_link' );
/**
* Notifications description.
*/
function notes_more_info() {
esc_html_e(
'You will receive instant notifications in your dashboard or your mobile device when somebody comments
on any of your sites. Reply directly wherever you are to keep the conversation going.',
'jetpack'
);
}
add_filter( 'jetpack_module_more_info_notes', 'notes_more_info' );
/**
* LaTeX support link.
*/
function latex_load_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-beautiful-math-with-latex' ) );
}
add_filter( 'jetpack_learn_more_button_latex', 'latex_load_more_link' );
/**
* LaTeX description.
*/
function latex_more_info() {
esc_html_e(
'LaTeX is a powerful markup language for writing complex mathematical equations and formulas.
Jetpack combines the power of LaTeX and the simplicity of WordPress to give you the ultimate
in math blogging platforms. Use $latex your latex code here$ or [latex]your latex code here[/latex]
to include in your posts and comments. Enjoy all sorts of options and embrace your inner nerd.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_latex', 'latex_more_info' );
/**
* Sharing support link.
*/
function sharedaddy_load_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-sharing' ) );
}
add_filter( 'jetpack_learn_more_button_sharedaddy', 'sharedaddy_load_more_link' );
/**
* Sharing description.
*/
function sharedaddy_more_info() {
esc_html_e(
'Visitors can share your posts with Twitter, Facebook, Reddit, Digg, LinkedIn, print,
and email. You can configure services to appear as icons, text, or both and some services like Twitter
have additional options.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_sharedaddy', 'sharedaddy_more_info' );
/**
* Extra Sidebar Widgets support link.
*/
function jetpack_widgets_load_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-extra-sidebar-widgets' ) );
}
add_filter( 'jetpack_learn_more_button_widgets', 'jetpack_widgets_load_more_link' );
/**
* Extra Sidebar Widgets description.
*/
function jetpack_widgets_more_info() {
esc_html_e(
'Add as many custom widgets as you like by dragging and dropping and customize each to fit your needs,
including, Twitter streams, Facebook like boxes, custom images, Gravatars, tiled galleries, recent posts,
or social icons.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_widgets', 'jetpack_widgets_more_info' );
/**
* Subscriptions support link.
*/
function jetpack_subscriptions_load_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-subscriptions' ) );
}
add_action( 'jetpack_learn_more_button_subscriptions', 'jetpack_subscriptions_load_more_link' );
/**
* Subscriptions description.
*/
function jetpack_subscriptions_more_info() {
esc_html_e(
'A widget in your sidebar allows visitors to subscribe to your site so that they receive an email
each time you publish new content. Your visitors can also subscribe to a post\'s comments to keep up with the conversation.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_subscriptions', 'jetpack_subscriptions_more_info' );
/**
* Protect support link.
*/
function jetpack_protect_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-protect' ) );
}
add_action( 'jetpack_learn_more_button_protect', 'jetpack_protect_more_link' );
/**
* Protect description.
*/
function jetpack_protect_more_info() {
esc_html_e(
'Most sites will come under attack from automated bots that attempt to log in for malicious purposes.
We protect you automatically from unauthorized access by using data from millions of sites.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_protect', 'jetpack_protect_more_info' );
/**
* JSON API support link.
*/
function jetpack_json_api_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-json-api' ) );
}
add_action( 'jetpack_learn_more_button_json-api', 'jetpack_json_api_more_link' );
/**
* JSON API description.
*/
function jetpack_json_api_more_info() {
esc_html_e(
'Authorize applications and services to securely connect to your site. Developers can use WordPress.com\'s OAuth2
authentication system and WordPress.com REST API to manage and access your site\'s content.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_json-api', 'jetpack_json_api_more_info' );
/**
* Contact Form support link.
*/
function jetpack_contact_form_learn_more_button() {
echo esc_url( Redirect::get_url( 'jetpack-support-contact-form' ) );
}
add_action( 'jetpack_learn_more_button_contact-form', 'jetpack_contact_form_learn_more_button' );
/**
* Contact Form description.
*/
function jetpack_contact_form_more_info() {
esc_html_e(
'Create simple contact forms without any coding. You can have multiple forms and when
a user submits it, their feedback will be emailed directly to you. If Akismet is active, submissions will be
automatically filtered for spam.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_contact-form', 'jetpack_contact_form_more_info' );
/**
* Comments support link.
*/
function jetpack_comments_learn_more_button() {
echo esc_url( Redirect::get_url( 'jetpack-support-comments' ) );
}
add_action( 'jetpack_learn_more_button_comments', 'jetpack_comments_learn_more_button' );
/**
* Comments description.
*/
function jetpack_comments_more_info() {
esc_html_e(
'Allow visitors to use their WordPress.com or Facebook accounts when commenting on
your site. Jetpack will match your site\'s color scheme automatically (but you can adjust that).',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_comments', 'jetpack_comments_more_info' );
/**
* Carousel support link.
*/
function jetpack_carousel_learn_more_button() {
echo esc_url( Redirect::get_url( 'jetpack-support-carousel' ) );
}
add_action( 'jetpack_learn_more_button_carousel', 'jetpack_carousel_learn_more_button' );
/**
* Carousel description.
*/
function jetpack_carousel_more_info() {
esc_html_e(
'With Carousel active, any standard WordPress galleries or single images you have embedded in posts or pages will
launch a full-screen photo browsing experience with comments and EXIF metadata.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_carousel', 'jetpack_carousel_more_info' );
/**
* Infinite Scroll support link.
*/
function jetpack_infinite_scroll_more_button() {
echo esc_url( Redirect::get_url( 'jetpack-support-infinite-scroll' ) );
}
add_action( 'jetpack_learn_more_button_infinite-scroll', 'jetpack_infinite_scroll_more_button' );
/**
* Infinite Scroll description.
*/
function jetpack_infinite_scroll_more_info() {
esc_html_e(
'Infinite scrolling pulls the next set of posts automatically into view when the reader approaches
the bottom of the page. This helps you reader see more of your content.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_infinite-scroll', 'jetpack_infinite_scroll_more_info' );
/**
* Post by Email support link.
*/
function jetpack_post_by_email_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-post-by-email' ) );
}
add_action( 'jetpack_learn_more_button_post-by-email', 'jetpack_post_by_email_more_link' );
/**
* Post by Email description.
*/
function jetpack_post_by_email_more_info() {
esc_html_e(
'Publish posts on your site by writing and sending an email from any email client instead of using the post editor.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_post-by-email', 'jetpack_post_by_email_more_info' );
/**
* Photon support link.
*/
function jetpack_photon_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-photon' ) );
}
add_action( 'jetpack_learn_more_button_photon', 'jetpack_photon_more_link' );
/**
* Photon description.
*/
function jetpack_photon_more_info() {
esc_html_e(
'Jetpack will optimize your images and serve them from the server location nearest
to your visitors. Using our global content delivery network will boost the loading speed of your site.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_photon', 'jetpack_photon_more_info' );
/**
* Tiled Galleries support link.
*/
function jetpack_tiled_gallery_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-tiled-galleries' ) );
}
add_action( 'jetpack_learn_more_button_tiled-gallery', 'jetpack_tiled_gallery_more_link' );
/**
* Tiled Galleries description.
*/
function jetpack_tiled_gallery_more_info() {
esc_html_e(
'When adding an image gallery, you will have the option to create elegant magazine-style mosaic layouts for your photos,
including mosaic (default), square, and circular layouts.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_tiled-gallery', 'jetpack_tiled_gallery_more_info' );
/**
* Likes support link.
*/
function jetpack_likes_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-likes' ) );
}
add_action( 'jetpack_learn_more_button_likes', 'jetpack_likes_more_link' );
/**
* Likes description.
*/
function jetpack_likes_more_info() {
esc_html_e(
'Allow your readers to show their appreciation for your posts and other content. Likes show up
below each post and your readers will also be able to review their liked posts from WordPress.com.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_likes', 'jetpack_likes_more_info' );
/**
* Widget Visibility support link.
*/
function jetpack_widget_visibility_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-widget-visibility' ) );
}
add_action( 'jetpack_learn_more_button_widget-visibility', 'jetpack_widget_visibility_more_link' );
/**
* Widget Visibility description.
*/
function jetpack_widget_visibility_more_info() {
esc_html_e(
'Choose from a set of visibility options for sidebar widgets such as showing them only certain categories,
only on error pages, or only search results pages. You can also do the reverse and choose to hide them on certain pages.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_widget-visibility', 'jetpack_widget_visibility_more_info' );
/**
* VideoPress support link.
*/
function jetpack_videopress_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-videopress' ) );
}
add_action( 'jetpack_learn_more_button_videopress', 'jetpack_videopress_more_link' );
/**
* VideoPress description.
*/
function jetpack_videopress_more_info() {
esc_html_e(
'The easiest way to upload ad-free and unbranded videos to your site. You get stats on video
playback and shares and the player is lightweight and responsive.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_videopress', 'jetpack_videopress_more_info' );
/**
* SSO support link.
*/
function jetpack_sso_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-sso' ) );
}
add_action( 'jetpack_learn_more_button_sso', 'jetpack_sso_more_link' );
/**
* SSO description.
*/
function jetpack_sso_more_info() {
esc_html_e(
'Your users will be able to log in to your site with their WordPress.com account.
This includes two-factor authentication making it the safest login mechanism for your site.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_sso', 'jetpack_sso_more_info' );
/**
* Monitor support link.
*/
function jetpack_monitor_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-monitor' ) );
}
add_action( 'jetpack_learn_more_button_monitor', 'jetpack_monitor_more_link' );
/**
* Monitor description.
*/
function jetpack_monitor_more_info() {
esc_html_e(
'Jetpack checks your site every five minutes and if any downtime is detected you will receive an email
notification alerting you to the issue, so you can act quickly and get your site back online.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_monitor', 'jetpack_monitor_more_info' );
/**
* Related Posts support link.
*/
function jetpack_related_posts_more_button() {
echo esc_url( Redirect::get_url( 'jetpack-support-related-posts' ) );
}
add_action( 'jetpack_learn_more_button_related-posts', 'jetpack_related_posts_more_button' );
/**
* Related Posts description.
*/
function jetpack_related_posts_more_info() {
esc_html_e(
'Show visitors related content from your site at the bottom of your posts. This encourages them
to browse more content, explore your site, and transform them into regular readers.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_related-posts', 'jetpack_related_posts_more_info' );
/**
* Markdown support link.
*/
function jetpack_markdown_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-markdown' ) );
}
add_action( 'jetpack_learn_more_button_markdown', 'jetpack_markdown_more_link' );
/**
* Markdown description.
*/
function jetpack_markdown_more_info() {
esc_html_e(
'Compose posts and comments with links, lists, and other styles using regular characters and
punctuation marks. A quick and easy way to format text without needing any HTML or coding.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_markdown', 'jetpack_markdown_more_info' );
/**
* Site Verification Tools support link.
*/
function jetpack_verification_tools_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-site-verification-tools' ) );
}
add_action( 'jetpack_learn_more_button_verification-tools', 'jetpack_verification_tools_more_link' );
/**
* Site Verification Tools description.
*/
function jetpack_verification_tools_more_info() {
esc_html_e(
'Verify your site ownership with services like Google, Bing, Pinterest, Yandex, and Facebook. This gives you access to
advanced features on these services and get verification badges.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_verification-tools', 'jetpack_verification_tools_more_info' );
/**
* SEO Tools support link.
*/
function jetpack_seo_tools_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-seo-tools' ) );
}
add_action( 'jetpack_learn_more_button_seo-tools', 'jetpack_seo_tools_more_link' );
/**
* SEO Tools description.
*/
function jetpack_seo_tools_more_info() {
esc_html_e(
'Better results on search engines and social media.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_seo-tools', 'jetpack_seo_tools_more_info' );
/**
* Custom Content Types support link.
*/
function jetpack_custom_content_types_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-custom-content-types' ) );
}
add_action( 'jetpack_learn_more_button_custom-content-types', 'jetpack_custom_content_types_more_link' );
/**
* Custom Content Types description.
*/
function jetpack_custom_content_types_more_info() {
esc_html_e(
'Add and organize content that doesnt necessarily fit into a post or static page such as portfolios
or testimonials. Custom content can be visible at specific URLs, or you may add them with shortcodes.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_custom-content-types', 'jetpack_custom_content_types_more_info' );
/**
* Manage support link.
*/
function jetpack_manage_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-site-management' ) );
}
add_action( 'jetpack_learn_more_button_manage', 'jetpack_manage_more_link' );
/**
* Manage description.
*/
function jetpack_custom_jetpack_manage() {
esc_html_e(
'Manage and update this and other WordPress sites from one simple dashboard on WordPress.com. You can update
plugins, set them to automatically update, and (de)activate them on a per-site basis or in bulk from
wordpress.com/plugins. You can also use the brand new and mobile-friendly post editor on WordPress.com as well
as view and activate installed themes and create or edit site menus.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_manage', 'jetpack_custom_jetpack_manage' );
/**
* Post list info.
*/
function jetpack_post_list_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-post-list' ) );
}
add_action( 'jetpack_learn_more_button_post-list', 'jetpack_post_list_link' );
/**
* Post List description.
*/
function jetpack_post_list_info() {
esc_html_e(
'Display extra information alongside each post in your dashboards Posts screen.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_post-list', 'jetpack_post_list_info' );
/**
* Sitemaps support link.
*/
function jetpack_sitemaps_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-sitemaps' ) );
}
add_action( 'jetpack_learn_more_button_sitemaps', 'jetpack_sitemaps_more_link' );
/**
* Sitemaps description.
*/
function jetpack_xml_sitemap_more_info() {
esc_html_e(
'Automatically create two sitemap files that list the URLs of posts and pages in your site.
This makes it easier for search engines (like Google) to include your site in relevant search results.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_sitemaps', 'jetpack_xml_sitemap_more_info' );
/**
* WordAds support link.
*/
function jetpack_wordads_more_link() {
echo 'https://wordads.co/';
}
add_action( 'jetpack_learn_more_button_wordads', 'jetpack_wordads_more_link' );
/**
* WordAds description.
*/
function jetpack_wordads_more_info() {
esc_html_e(
'By default ads are shown at the end of every page, post, or the first article on your front page. You can also add them to the top of your site and to any widget area to increase your earnings!',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_wordads', 'jetpack_wordads_more_info' );
/**
* Google Analytics support link.
*/
function jetpack_google_analytics_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-google-analytics' ) );
}
add_action( 'jetpack_learn_more_button_google-analytics', 'jetpack_google_analytics_more_link' );
/**
* Google Analytics description.
*/
function jetpack_google_analytics_more_info() {
esc_html_e(
'Track website statistics with Google Analytics for a deeper understanding of your website visitors and customers.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_google-analytics', 'jetpack_google_analytics_more_info' );
/**
* WooCommerce Analytics support link.
*/
function jetpack_woocommerce_analytics_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-woocommerce-analytics' ) );
}
add_action( 'jetpack_learn_more_button_woocommerce-analytics', 'jetpack_woocommerce_analytics_more_link' );
/**
* WooCommerce Analytics description.
*/
function jetpack_woocommerce_analytics_more_info() {
esc_html_e(
'Enhanced analytics for WooCommerce and Jetpack users.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_woocommerce-analytics', 'jetpack_woocommerce_analytics_more_info' );
/**
* Search support link.
*/
function jetpack_search_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-search' ) );
}
add_action( 'jetpack_learn_more_button_search', 'jetpack_search_more_link' );
/**
* Search description.
*/
function jetpack_search_more_info() {
esc_html_e(
'Help visitors quickly find answers with highly relevant instant search results and powerful filtering.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_search', 'jetpack_search_more_info' );
/**
* Comment Likes support link.
*/
function jetpack_comment_likes_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-comment-likes' ) );
}
add_action( 'jetpack_learn_more_button_comment-likes', 'jetpack_comment_likes_more_link' );
/**
* Comment Likes description.
*/
function jetpack_comment_likes_more_info() {
esc_html_e(
'Increase visitor engagement by adding a Like button to comments.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_comment-likes', 'jetpack_comment_likes_more_info' );
/**
* Asset CDN support link.
*/
function jetpack_assetcdn_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-asset-cdn' ) );
}
add_action( 'jetpack_learn_more_button_photon-cdn', 'jetpack_assetcdn_more_link' );
/**
* Asset CDN description.
*/
function jetpack_assetcdn_more_info() {
esc_html_e(
'Our asset CDN is a site acceleration service.
That means that we host static assets like JavaScript and CSS shipped with WordPress Core and Jetpack from our servers, alleviating the load on your server.',
'jetpack'
);
}
add_action( 'jetpack_module_more_info_photon-cdn', 'jetpack_assetcdn_more_info' );
/**
* Copy Post support link.
*/
function jetpack_copy_post_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-copy-post' ) );
}
add_action( 'jetpack_learn_more_button_copy-post', 'jetpack_copy_post_more_link' );
/**
* Copy Post description.
*/
function jetpack_more_info_copy_post() {
esc_html_e( 'Create a new post based on an existing post.', 'jetpack' );
}
add_action( 'jetpack_module_more_info_copy-post', 'jetpack_more_info_copy_post' );
/**
* Google Fonts support link.
*/
function jetpack_google_fonts_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-google-fonts' ) );
}
add_action( 'jetpack_learn_more_button_google-fonts', 'jetpack_google_fonts_more_link' );
/**
* Google Fonts description.
*/
function jetpack_more_info_google_fonts() {
esc_html_e( 'A selection of Google fonts for block enabled themes. This feature is still being developed.', 'jetpack' );
}
add_action( 'jetpack_module_more_info_google-fonts', 'jetpack_more_info_google_fonts' );
/**
* WAF support link.
*/
function jetpack_waf_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-waf' ) );
}
add_action( 'jetpack_learn_more_button_waf', 'jetpack_waf_more_link' );
/**
* WAF description.
*/
function jetpack_more_info_waf() {
esc_html_e( 'The Jetpack Firewall is a web application firewall designed to protect your WordPress site from malicious requests.', 'jetpack' );
}
add_action( 'jetpack_module_more_info_waf', 'jetpack_more_info_waf' );
/**
* Blaze support link.
*/
function jetpack_blaze_more_link() {
echo esc_url( Redirect::get_url( 'jetpack-support-blaze' ) );
}
add_action( 'jetpack_learn_more_button_blaze', 'jetpack_blaze_more_link' );
/**
* Blaze description.
*/
function jetpack_more_info_blaze() {
esc_html_e( 'Grow your audience by promoting your content across Tumblr and WordPress.com.', 'jetpack' );
}
add_action( 'jetpack_module_more_info_blaze', 'jetpack_more_info_blaze' );
@@ -0,0 +1,134 @@
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Module Name: Monitor
* Module Description: Jetpacks downtime monitoring will continuously watch your site and alert you the moment that downtime is detected.
* Sort Order: 28
* Recommendation Order: 10
* First Introduced: 2.6
* Requires Connection: Yes
* Requires User Connection: Yes
* Auto Activate: No
* Module Tags: Recommended
* Feature: Security
* Additional Search Queries: monitor, uptime, downtime, monitoring, maintenance, maintenance mode, offline, site is down, site down, down, repair, error
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
/**
* Class Jetpack_Monitor
*/
class Jetpack_Monitor {
/**
* Name of the module.
*
* @var string Name of module.
*/
public $module = 'monitor';
/**
* Constructor.
*/
public function __construct() {
add_action( 'jetpack_modules_loaded', array( $this, 'jetpack_modules_loaded' ) );
add_action( 'jetpack_activate_module_monitor', array( $this, 'activate_module' ) );
}
/**
* Runs upon module activation.
*
* @return void
*/
public function activate_module() {
if ( ( new Connection_Manager( 'jetpack' ) )->is_user_connected() ) {
self::update_option_receive_jetpack_monitor_notification( true );
}
}
/**
* Runs on the jetpack_modules_loaded hook to enable configuation.
*
* @return void
*/
public function jetpack_modules_loaded() {
Jetpack::enable_module_configurable( $this->module );
}
/**
* Whether to receive the notifications.
*
* @param bool $value `true` to enable notifications, `false` to disable them.
*
* @return bool
*/
public function update_option_receive_jetpack_monitor_notification( $value ) {
$xml = new Jetpack_IXR_Client(
array(
'user_id' => get_current_user_id(),
)
);
$xml->query( 'jetpack.monitor.setNotifications', (bool) $value );
if ( $xml->isError() ) {
wp_die( sprintf( '%s: %s', esc_html( $xml->getErrorCode() ), esc_html( $xml->getErrorMessage() ) ) );
}
// To be used only in Jetpack_Core_Json_Api_Endpoints::get_remote_value.
update_option( 'monitor_receive_notifications', (bool) $value );
return true;
}
/**
* Checks the status of notifications for current Jetpack site user.
*
* @since 2.8
* @since 4.1.0 New parameter $die_on_error.
*
* @param bool $die_on_error Whether to issue a wp_die when an error occurs or return a WP_Error object.
*
* @return boolean|WP_Error
*/
public static function user_receives_notifications( $die_on_error = true ) {
$xml = new Jetpack_IXR_Client(
array(
'user_id' => get_current_user_id(),
)
);
$xml->query( 'jetpack.monitor.isUserInNotifications' );
if ( $xml->isError() ) {
if ( $die_on_error ) {
wp_die( sprintf( '%s: %s', esc_html( $xml->getErrorCode() ), esc_html( $xml->getErrorMessage() ) ), 400 );
} else {
return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage(), array( 'status' => 400 ) );
}
}
return $xml->getResponse();
}
/**
* Returns date of the last downtime.
*
* @since 4.0.0
* @return string date in YYYY-MM-DD HH:mm:ss format
*/
public function monitor_get_last_downtime() {
$xml = new Jetpack_IXR_Client();
$xml->query( 'jetpack.monitor.getLastDowntime' );
if ( $xml->isError() ) {
return new WP_Error( 'monitor-downtime', $xml->getErrorMessage() );
}
set_transient( 'monitor_last_downtime', $xml->getResponse(), 10 * MINUTE_IN_SECONDS );
return $xml->getResponse();
}
}
new Jetpack_Monitor();
@@ -0,0 +1,258 @@
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Module Name: Notifications
* Module Description: Receive instant notifications of site comments and likes.
* Sort Order: 13
* First Introduced: 1.9
* Requires Connection: Yes
* Requires User Connection: Yes
* Auto Activate: Yes
* Module Tags: Other
* Feature: General
* Additional Search Queries: notification, notifications, toolbar, adminbar, push, comments
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Status\Host;
if ( ! defined( 'JETPACK_NOTES__CACHE_BUSTER' ) ) {
define( 'JETPACK_NOTES__CACHE_BUSTER', JETPACK__VERSION . '-' . gmdate( 'oW' ) . '-lite' );
}
/**
* Notifications class.
*/
class Jetpack_Notifications {
/**
* Jetpack object.
*
* @var bool|Jetpack Jetpack object.
*/
public $jetpack = false;
/**
* Singleton
*
* @static
*/
public static function init() {
static $instance = array();
if ( ! $instance ) {
$instance[0] = new Jetpack_Notifications();
}
return $instance[0];
}
/**
* Constructor.
*/
private function __construct() {
$this->jetpack = Jetpack::init();
add_action( 'init', array( $this, 'action_init' ) );
}
/**
* Adds s0.wp.com to a file path.
*
* @param string $file File path.
*
* @return string
*/
public function wpcom_static_url( $file ) {
return 'https://s0.wp.com' . $file;
}
/**
* Init the notifications admin bar.
*
* @return void
*/
public function action_init() {
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
return;
}
if ( ! has_filter( 'show_admin_bar', '__return_true' ) && ! is_user_logged_in() ) {
return;
}
// Do not show notifications in the Site Editor, which is always in fullscreen mode.
global $pagenow;
// Pre 13.7 pages that still need to be supported if < 13.7 is
// still installed.
$allowed_old_pages = array( 'admin.php', 'themes.php' );
$is_old_site_editor_page = in_array( $pagenow, $allowed_old_pages, true ) && isset( $_GET['page'] ) && 'gutenberg-edit-site' === $_GET['page']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
// For Gutenberg > 13.7, the core `site-editor.php` route is used instead
$is_site_editor_page = 'site-editor.php' === $pagenow;
if ( $is_site_editor_page || $is_old_site_editor_page ) {
return;
}
add_action( 'admin_bar_menu', array( $this, 'admin_bar_menu' ), 120 );
add_action( 'wp_head', array( $this, 'styles_and_scripts' ), 120 );
add_action( 'admin_head', array( $this, 'styles_and_scripts' ) );
}
/**
* Enqueues and registers styles/scripts for notifications.
*
* @return void
*/
public function styles_and_scripts() {
if ( self::is_block_editor() ) {
return;
}
$is_rtl = is_rtl();
if ( ( new Host() )->is_woa_site() ) {
/**
* Can be used to force Notifications to display in RTL style.
*
* @module notes
*
* @since 4.8.0
*
* @param bool true Should notifications be displayed in RTL style. Defaults to false.
*/
$is_rtl = apply_filters( 'a8c_wpcom_masterbar_enqueue_rtl_notification_styles', false );
}
if ( ! $is_rtl ) {
wp_enqueue_style( 'wpcom-notes-admin-bar', $this->wpcom_static_url( '/wp-content/mu-plugins/notes/admin-bar-v2.css' ), array( 'admin-bar' ), JETPACK_NOTES__CACHE_BUSTER );
} else {
wp_enqueue_style( 'wpcom-notes-admin-bar', $this->wpcom_static_url( '/wp-content/mu-plugins/notes/rtl/admin-bar-v2-rtl.css' ), array( 'admin-bar' ), JETPACK_NOTES__CACHE_BUSTER );
}
wp_enqueue_style( 'noticons', $this->wpcom_static_url( '/i/noticons/noticons.css' ), array( 'wpcom-notes-admin-bar' ), JETPACK_NOTES__CACHE_BUSTER );
$this->print_js();
$script_handles = array();
wp_register_script( 'wpcom-notes-common', $this->wpcom_static_url( '/wp-content/mu-plugins/notes/notes-common-lite.min.js' ), array(), JETPACK_NOTES__CACHE_BUSTER, true );
$script_handles[] = 'wpcom-notes-common';
wp_enqueue_script( 'wpcom-notes-admin-bar', $this->wpcom_static_url( '/wp-content/mu-plugins/notes/admin-bar-v2.js' ), array( 'wpcom-notes-common' ), JETPACK_NOTES__CACHE_BUSTER, true );
$script_handles[] = 'wpcom-notes-admin-bar';
$wp_notes_args = 'var wpNotesArgs = ' . wp_json_encode( array( 'cacheBuster' => JETPACK_NOTES__CACHE_BUSTER ) ) . ';';
wp_add_inline_script( 'wpcom-notes-admin-bar', $wp_notes_args, 'before' );
if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
add_filter(
'script_loader_tag',
function ( $tag, $handle ) use ( $script_handles ) {
if ( in_array( $handle, $script_handles, true ) ) {
$tag = preg_replace( '/(?<=<script)(?=\s|>)/i', ' data-ampdevmode', $tag );
}
return $tag;
},
10,
2
);
}
}
/**
* Adds notifications bubble to the admin bar.
*
* @return void
*/
public function admin_bar_menu() {
global $wp_admin_bar;
if ( ! is_object( $wp_admin_bar ) ) {
return;
}
if ( self::is_block_editor() ) {
return;
}
$user_locale = get_user_locale();
if ( ! class_exists( 'GP_Locales' ) ) {
if ( defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' ) && file_exists( JETPACK__GLOTPRESS_LOCALES_PATH ) ) {
require JETPACK__GLOTPRESS_LOCALES_PATH;
}
}
if ( class_exists( 'GP_Locales' ) ) {
$jetpack_locale_object = GP_Locales::by_field( 'slug', $user_locale );
if ( $jetpack_locale_object instanceof GP_Locale ) {
$user_locale = $jetpack_locale_object->slug;
}
}
$third_party_cookie_check_iframe = '<span style="display:none;"><iframe class="jetpack-notes-cookie-check" src="https://widgets.wp.com/3rd-party-cookie-check/index.html"></iframe></span>';
$title = self::get_notes_markup();
// The default fallback is `en_US`. Remove underscore if present, noting that lang codes can be more than three chars.
$user_locale = strtolower( explode( '_', $user_locale, 2 )[0] );
$wp_admin_bar->add_menu(
array(
'id' => 'notes',
'title' => $title,
'meta' => array(
'html' => '<div id="wpnt-notes-panel2" class="intrinsic-ignore" style="display:none" lang="' . esc_attr( $user_locale ) . '" dir="' . ( is_rtl() ? 'rtl' : 'ltr' ) . '"><div class="wpnt-notes-panel-header"><span class="wpnt-notes-header">' . __( 'Notifications', 'jetpack' ) . '</span><span class="wpnt-notes-panel-link"></span></div></div>' . $third_party_cookie_check_iframe,
'class' => 'menupop',
),
'parent' => 'top-secondary',
'href' => 'https://wordpress.com/notifications',
)
);
}
/**
* Returns the HTML markup for used by notification in top bar
*
* @return string
*/
private static function get_notes_markup() {
return '<span id="wpnt-notes-unread-count" class="wpnt-loading wpn-read"></span>
<span class="noticon noticon-bell ab-icon"></span>
<span class="screen-reader-text">' . esc_html__( 'Notifications', 'jetpack' ) . '</span>';
}
/**
* Echos the Notes JS.
*
* @return void
*/
public function print_js() {
$link_accounts_url = is_user_logged_in() && ! ( new Connection_Manager( 'jetpack' ) )->is_user_connected() ? Jetpack::admin_url() : false;
?>
<script data-ampdevmode type="text/javascript">
/* <![CDATA[ */
var wpNotesIsJetpackClient = true;
var wpNotesIsJetpackClientV2 = true;
<?php if ( $link_accounts_url ) : ?>
var wpNotesLinkAccountsURL = '<?php echo esc_url( $link_accounts_url ); ?>';
<?php endif; ?>
/* ]]> */
</script>
<?php
}
/**
* Checks to see if we're in the block editor.
*/
public static function is_block_editor() {
if ( function_exists( 'get_current_screen' ) ) {
$current_screen = get_current_screen();
if ( ! empty( $current_screen ) && $current_screen->is_block_editor() ) {
return true;
}
}
return false;
}
}
Jetpack_Notifications::init();
@@ -0,0 +1,336 @@
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Module Name: Asset CDN
* Module Description: Jetpacks Site Accelerator loads your site faster by optimizing your images and serving your images and static files from our global network of servers.
* Sort Order: 26
* Recommendation Order: 1
* First Introduced: 6.6
* Requires Connection: No
* Auto Activate: No
* Module Tags: Photos and Videos, Appearance, Recommended
* Feature: Recommended, Appearance
* Additional Search Queries: site accelerator, accelerate, static, assets, javascript, css, files, performance, cdn, bandwidth, content delivery network, pagespeed, combine js, optimize css
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Assets;
$GLOBALS['concatenate_scripts'] = false; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
Assets::add_resource_hint( '//c0.wp.com', 'preconnect' );
/**
* Asset CDN module main class file.
*/
class Jetpack_Photon_Static_Assets_CDN {
const CDN = 'https://c0.wp.com/';
/**
* Sets up action handlers needed for Jetpack CDN.
*/
public static function go() {
add_action( 'wp_print_scripts', array( __CLASS__, 'cdnize_assets' ) );
add_action( 'wp_print_styles', array( __CLASS__, 'cdnize_assets' ) );
add_action( 'admin_print_scripts', array( __CLASS__, 'cdnize_assets' ) );
add_action( 'admin_print_styles', array( __CLASS__, 'cdnize_assets' ) );
add_action( 'wp_footer', array( __CLASS__, 'cdnize_assets' ) );
add_filter( 'load_script_textdomain_relative_path', array( __CLASS__, 'fix_script_relative_path' ), 10, 2 );
add_filter( 'load_script_translation_file', array( __CLASS__, 'fix_local_script_translation_path' ), 10, 3 );
}
/**
* Sets up CDN URLs for assets that are enqueued by the WordPress Core.
*/
public static function cdnize_assets() {
global $wp_scripts, $wp_styles, $wp_version;
/*
* Short-circuit if AMP since not relevant as custom JS is not allowed and CSS is inlined.
* Note that it is not suitable to use the jetpack_force_disable_site_accelerator filter for this
* because it will be applied before the wp action, which is the point at which the queried object
* is available and we know whether the response will be AMP or not. This is particularly important
* for AMP-first (native AMP) pages where there are no AMP-specific URLs.
*/
if ( class_exists( Jetpack_AMP_Support::class ) && Jetpack_AMP_Support::is_amp_request() ) {
return;
}
/**
* Filters Jetpack CDN's Core version number and locale. Can be used to override the values
* that Jetpack uses to retrieve assets. Expects the values to be returned in an array.
*
* @module photon-cdn
*
* @since 6.6.0
*
* @param array $values array( $version = core assets version, i.e. 4.9.8, $locale = desired locale )
*/
list( $version, $locale ) = apply_filters( // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
'jetpack_cdn_core_version_and_locale',
array( $wp_version, get_locale() )
);
if ( self::is_public_version( $version ) ) {
$site_url = trailingslashit( site_url() );
foreach ( $wp_scripts->registered as $handle => $thing ) {
if ( wp_startswith( $thing->src, self::CDN ) ) {
continue;
}
if ( ! is_string( $thing->src ) ) {
continue;
}
$src = ltrim( str_replace( $site_url, '', $thing->src ), '/' );
if ( self::is_js_or_css_file( $src ) && in_array( substr( $src, 0, 9 ), array( 'wp-admin/', 'wp-includ' ), true ) ) {
$wp_scripts->registered[ $handle ]->src = sprintf( self::CDN . 'c/%1$s/%2$s', $version, $src );
$wp_scripts->registered[ $handle ]->ver = null;
}
}
foreach ( $wp_styles->registered as $handle => $thing ) {
if ( wp_startswith( $thing->src, self::CDN ) ) {
continue;
}
if ( ! is_string( $thing->src ) ) {
continue;
}
$src = ltrim( str_replace( $site_url, '', $thing->src ), '/' );
if ( self::is_js_or_css_file( $src ) && in_array( substr( $src, 0, 9 ), array( 'wp-admin/', 'wp-includ' ), true ) ) {
$wp_styles->registered[ $handle ]->src = sprintf( self::CDN . 'c/%1$s/%2$s', $version, $src );
$wp_styles->registered[ $handle ]->ver = null;
}
}
}
self::cdnize_plugin_assets( 'jetpack', JETPACK__VERSION );
if ( class_exists( 'WooCommerce' ) ) {
self::cdnize_plugin_assets( 'woocommerce', WC_VERSION );
}
}
/**
* Ensure use of the correct relative path when determining the JavaScript file names.
*
* @param string $relative The relative path of the script. False if it could not be determined.
* @param string $src The full source url of the script.
* @return string The expected relative path for the CDN-ed URL.
*/
public static function fix_script_relative_path( $relative, $src ) {
// Note relevant in AMP responses. See note above.
if ( class_exists( Jetpack_AMP_Support::class ) && Jetpack_AMP_Support::is_amp_request() ) {
return $relative;
}
$strpos = strpos( $src, '/wp-includes/' );
// We only treat URLs that have wp-includes in them. Cases like language textdomains
// can also use this filter, they don't need to be touched because they are local paths.
if ( false !== $strpos ) {
return substr( $src, 1 + $strpos );
}
// Get the local path from a URL which was CDN'ed by cdnize_plugin_assets().
if ( preg_match( '#^' . preg_quote( self::CDN, '#' ) . 'p/[^/]+/[^/]+/(.*)$#', $src, $m ) ) {
return $m[1];
}
return $relative;
}
/**
* Ensure use of the correct local path when loading the JavaScript translation file for a CDN'ed asset.
*
* @param string|false $file Path to the translation file to load. False if there isn't one.
* @param string $handle The script handle.
* @param string $domain The text domain.
*
* @return string The transformed local languages path.
*/
public static function fix_local_script_translation_path( $file, $handle, $domain ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
global $wp_scripts;
// This is a rewritten plugin URL, so load the language file from the plugins path.
if ( $file && isset( $wp_scripts->registered[ $handle ] ) && wp_startswith( $wp_scripts->registered[ $handle ]->src, self::CDN . 'p' ) ) {
return WP_LANG_DIR . '/plugins/' . basename( $file );
}
return $file;
}
/**
* Sets up CDN URLs for supported plugin assets.
*
* @param String $plugin_slug plugin slug string.
* @param String $current_version plugin version string.
* @return null|bool
*/
public static function cdnize_plugin_assets( $plugin_slug, $current_version ) {
global $wp_scripts, $wp_styles;
/**
* Filters Jetpack CDN's plugin slug and version number. Can be used to override the values
* that Jetpack uses to retrieve assets. For example, when testing a development version of Jetpack
* the assets are not yet published, so you may need to override the version value to either
* trunk, or the latest available version. Expects the values to be returned in an array.
*
* @module photon-cdn
*
* @since 6.6.0
*
* @param array $values array( $slug = the plugin repository slug, i.e. jetpack, $version = the plugin version, i.e. 6.6 )
*/
list( $plugin_slug, $current_version ) = apply_filters(
'jetpack_cdn_plugin_slug_and_version',
array( $plugin_slug, $current_version )
);
$assets = self::get_plugin_assets( $plugin_slug, $current_version );
$plugin_directory_url = plugins_url() . '/' . $plugin_slug . '/';
if ( is_wp_error( $assets ) || ! is_array( $assets ) ) {
return false;
}
foreach ( $wp_scripts->registered as $handle => $thing ) {
if ( wp_startswith( $thing->src, self::CDN ) ) {
continue;
}
if ( wp_startswith( $thing->src, $plugin_directory_url ) ) {
$local_path = substr( $thing->src, strlen( $plugin_directory_url ) );
if ( in_array( $local_path, $assets, true ) ) {
$wp_scripts->registered[ $handle ]->src = sprintf( self::CDN . 'p/%1$s/%2$s/%3$s', $plugin_slug, $current_version, $local_path );
$wp_scripts->registered[ $handle ]->ver = null;
}
}
}
foreach ( $wp_styles->registered as $handle => $thing ) {
if ( wp_startswith( $thing->src, self::CDN ) ) {
continue;
}
if ( wp_startswith( $thing->src, $plugin_directory_url ) ) {
$local_path = substr( $thing->src, strlen( $plugin_directory_url ) );
if ( in_array( $local_path, $assets, true ) ) {
$wp_styles->registered[ $handle ]->src = sprintf( self::CDN . 'p/%1$s/%2$s/%3$s', $plugin_slug, $current_version, $local_path );
$wp_styles->registered[ $handle ]->ver = null;
}
}
}
}
/**
* Returns cdn-able assets for a given plugin.
*
* @param string $plugin plugin slug string.
* @param string $version plugin version number string.
* @return array|bool Will return false if not a public version.
*/
public static function get_plugin_assets( $plugin, $version ) {
if ( 'jetpack' === $plugin && JETPACK__VERSION === $version ) {
if ( ! self::is_public_version( $version ) ) {
return false;
}
$assets = array(); // The variable will be redefined in the included file.
include JETPACK__PLUGIN_DIR . 'modules/photon-cdn/jetpack-manifest.php';
return $assets;
}
/**
* Used for other plugins to provide their bundled assets via filter to
* prevent the need of storing them in an option or an external api request
* to w.org.
*
* @module photon-cdn
*
* @since 6.6.0
*
* @param array $assets The assets array for the plugin.
* @param string $version The version of the plugin being requested.
*/
$assets = apply_filters( "jetpack_cdn_plugin_assets-{$plugin}", null, $version ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
if ( is_array( $assets ) ) {
return $assets;
}
if ( ! self::is_public_version( $version ) ) {
return false;
}
$cache = Jetpack_Options::get_option( 'static_asset_cdn_files', array() );
if ( isset( $cache[ $plugin ][ $version ] ) ) {
if ( is_array( $cache[ $plugin ][ $version ] ) ) {
return $cache[ $plugin ][ $version ];
}
if ( is_numeric( $cache[ $plugin ][ $version ] ) ) {
// Cache an empty result for up to 24h.
if ( (int) $cache[ $plugin ][ $version ] + DAY_IN_SECONDS > time() ) {
return array();
}
}
}
$url = sprintf( 'http://downloads.wordpress.org/plugin-checksums/%s/%s.json', $plugin, $version );
if ( wp_http_supports( array( 'ssl' ) ) ) {
$url = set_url_scheme( $url, 'https' );
}
$response = wp_remote_get( $url );
$body = trim( wp_remote_retrieve_body( $response ) );
$body = json_decode( $body, true );
$return = time();
if ( is_array( $body ) ) {
$return = array_filter( array_keys( $body['files'] ), array( __CLASS__, 'is_js_or_css_file' ) );
}
$cache[ $plugin ] = array();
$cache[ $plugin ][ $version ] = $return;
Jetpack_Options::update_option( 'static_asset_cdn_files', $cache, true );
return $return;
}
/**
* Checks a path whether it is a JS or CSS file.
*
* @param String $path file path.
* @return Boolean whether the file is a JS or CSS.
*/
public static function is_js_or_css_file( $path ) {
return ( ! str_contains( $path, '?' ) ) && in_array( substr( $path, -3 ), array( 'css', '.js' ), true );
}
/**
* Checks whether the version string indicates a production version.
*
* @param String $version the version string.
* @param Boolean $include_beta_and_rc whether to count beta and RC versions as production.
* @return Boolean
*/
public static function is_public_version( $version, $include_beta_and_rc = false ) {
if ( preg_match( '/^\d+(\.\d+)+$/', $version ) ) {
/** Example matches: `1`, `1.2`, `1.2.3`. */
return true;
} elseif ( $include_beta_and_rc && preg_match( '/^\d+(\.\d+)+(-(beta|rc|pressable)\d?)$/i', $version ) ) {
/** Example matches: `1.2.3`, `1.2.3-beta`, `1.2.3-pressable`, `1.2.3-beta1`, `1.2.3-rc`, `1.2.3-rc2`. */
return true;
}
// Unrecognized version.
return false;
}
}
/**
* Allow plugins to short-circuit the Asset CDN, even when the module is on.
*
* @module photon-cdn
*
* @since 6.7.0
*
* @param false bool Should the Asset CDN be blocked? False by default.
*/
if ( true !== apply_filters( 'jetpack_force_disable_site_accelerator', false ) ) {
Jetpack_Photon_Static_Assets_CDN::go();
}
@@ -0,0 +1,420 @@
<?php
// This file is autogenerated by bin/build-asset-cdn-json.php
$assets = array (
0 => '3rd-party/debug-bar/debug-bar.css',
1 => '3rd-party/debug-bar/debug-bar.js',
2 => '_inc/accessible-focus.js',
3 => '_inc/blocks/346.js',
4 => '_inc/blocks/ai-chat/view.css',
5 => '_inc/blocks/ai-chat/view.js',
6 => '_inc/blocks/ai-chat/view.rtl.css',
7 => '_inc/blocks/blogging-prompt/view.css',
8 => '_inc/blocks/blogging-prompt/view.js',
9 => '_inc/blocks/blogging-prompt/view.rtl.css',
10 => '_inc/blocks/blogroll/view.css',
11 => '_inc/blocks/blogroll/view.js',
12 => '_inc/blocks/blogroll/view.rtl.css',
13 => '_inc/blocks/business-hours/view.css',
14 => '_inc/blocks/business-hours/view.js',
15 => '_inc/blocks/business-hours/view.rtl.css',
16 => '_inc/blocks/button/view.css',
17 => '_inc/blocks/button/view.js',
18 => '_inc/blocks/button/view.rtl.css',
19 => '_inc/blocks/calendly/view.css',
20 => '_inc/blocks/calendly/view.js',
21 => '_inc/blocks/calendly/view.rtl.css',
22 => '_inc/blocks/components.css',
23 => '_inc/blocks/components.rtl.css',
24 => '_inc/blocks/contact-info/view.css',
25 => '_inc/blocks/contact-info/view.js',
26 => '_inc/blocks/contact-info/view.rtl.css',
27 => '_inc/blocks/cookie-consent/view.css',
28 => '_inc/blocks/cookie-consent/view.js',
29 => '_inc/blocks/cookie-consent/view.rtl.css',
30 => '_inc/blocks/donations/view.css',
31 => '_inc/blocks/donations/view.js',
32 => '_inc/blocks/donations/view.rtl.css',
33 => '_inc/blocks/editor-assets/mapbox-gl-1.13.0.css',
34 => '_inc/blocks/editor-assets/mapbox-gl-1.13.0.js',
35 => '_inc/blocks/editor-beta.css',
36 => '_inc/blocks/editor-beta.js',
37 => '_inc/blocks/editor-beta.rtl.css',
38 => '_inc/blocks/editor-experimental.css',
39 => '_inc/blocks/editor-experimental.js',
40 => '_inc/blocks/editor-experimental.rtl.css',
41 => '_inc/blocks/editor-no-post-editor.css',
42 => '_inc/blocks/editor-no-post-editor.js',
43 => '_inc/blocks/editor-no-post-editor.rtl.css',
44 => '_inc/blocks/editor.css',
45 => '_inc/blocks/editor.js',
46 => '_inc/blocks/editor.rtl.css',
47 => '_inc/blocks/eventbrite/view.css',
48 => '_inc/blocks/eventbrite/view.js',
49 => '_inc/blocks/eventbrite/view.rtl.css',
50 => '_inc/blocks/gif/view.css',
51 => '_inc/blocks/gif/view.js',
52 => '_inc/blocks/gif/view.rtl.css',
53 => '_inc/blocks/goodreads/view.css',
54 => '_inc/blocks/goodreads/view.js',
55 => '_inc/blocks/goodreads/view.rtl.css',
56 => '_inc/blocks/google-calendar/view.css',
57 => '_inc/blocks/google-calendar/view.js',
58 => '_inc/blocks/google-calendar/view.rtl.css',
59 => '_inc/blocks/google-docs-embed/view.css',
60 => '_inc/blocks/google-docs-embed/view.js',
61 => '_inc/blocks/google-docs-embed/view.rtl.css',
62 => '_inc/blocks/image-compare/view.css',
63 => '_inc/blocks/image-compare/view.js',
64 => '_inc/blocks/image-compare/view.rtl.css',
65 => '_inc/blocks/instagram-gallery/view.css',
66 => '_inc/blocks/instagram-gallery/view.js',
67 => '_inc/blocks/instagram-gallery/view.rtl.css',
68 => '_inc/blocks/mailchimp/view.css',
69 => '_inc/blocks/mailchimp/view.js',
70 => '_inc/blocks/mailchimp/view.rtl.css',
71 => '_inc/blocks/map/view.css',
72 => '_inc/blocks/map/view.js',
73 => '_inc/blocks/map/view.rtl.css',
74 => '_inc/blocks/nextdoor/view.js',
75 => '_inc/blocks/opentable/view.css',
76 => '_inc/blocks/opentable/view.js',
77 => '_inc/blocks/opentable/view.rtl.css',
78 => '_inc/blocks/payment-buttons/view.css',
79 => '_inc/blocks/payment-buttons/view.js',
80 => '_inc/blocks/payment-buttons/view.rtl.css',
81 => '_inc/blocks/podcast-player/view.css',
82 => '_inc/blocks/podcast-player/view.js',
83 => '_inc/blocks/podcast-player/view.rtl.css',
84 => '_inc/blocks/premium-content/view.css',
85 => '_inc/blocks/premium-content/view.js',
86 => '_inc/blocks/premium-content/view.rtl.css',
87 => '_inc/blocks/rating-star/view.css',
88 => '_inc/blocks/rating-star/view.js',
89 => '_inc/blocks/rating-star/view.rtl.css',
90 => '_inc/blocks/recipe/view.css',
91 => '_inc/blocks/recipe/view.js',
92 => '_inc/blocks/recipe/view.rtl.css',
93 => '_inc/blocks/recurring-payments/view.css',
94 => '_inc/blocks/recurring-payments/view.js',
95 => '_inc/blocks/recurring-payments/view.rtl.css',
96 => '_inc/blocks/repeat-visitor/view.js',
97 => '_inc/blocks/send-a-message/view.css',
98 => '_inc/blocks/send-a-message/view.js',
99 => '_inc/blocks/send-a-message/view.rtl.css',
100 => '_inc/blocks/sharing-button/view.css',
101 => '_inc/blocks/sharing-button/view.js',
102 => '_inc/blocks/sharing-button/view.rtl.css',
103 => '_inc/blocks/sharing-buttons/view.css',
104 => '_inc/blocks/sharing-buttons/view.js',
105 => '_inc/blocks/sharing-buttons/view.rtl.css',
106 => '_inc/blocks/slideshow/view.css',
107 => '_inc/blocks/slideshow/view.js',
108 => '_inc/blocks/slideshow/view.rtl.css',
109 => '_inc/blocks/story/view.css',
110 => '_inc/blocks/story/view.js',
111 => '_inc/blocks/story/view.rtl.css',
112 => '_inc/blocks/subscriptions/view.css',
113 => '_inc/blocks/subscriptions/view.js',
114 => '_inc/blocks/subscriptions/view.rtl.css',
115 => '_inc/blocks/swiper.css',
116 => '_inc/blocks/swiper.rtl.css',
117 => '_inc/blocks/tiled-gallery/view.css',
118 => '_inc/blocks/tiled-gallery/view.js',
119 => '_inc/blocks/tiled-gallery/view.rtl.css',
120 => '_inc/blocks/tock/view.css',
121 => '_inc/blocks/tock/view.js',
122 => '_inc/blocks/tock/view.rtl.css',
123 => '_inc/blocks/top-posts/view.css',
124 => '_inc/blocks/top-posts/view.js',
125 => '_inc/blocks/top-posts/view.rtl.css',
126 => '_inc/build/accessible-focus.min.js',
127 => '_inc/build/admin.css',
128 => '_inc/build/admin.js',
129 => '_inc/build/admin.rtl.css',
130 => '_inc/build/carousel/jetpack-carousel.min.js',
131 => '_inc/build/carousel/swiper-bundle.min.js',
132 => '_inc/build/comment-likes/comment-like-count.min.js',
133 => '_inc/build/crowdsignal-shortcode.min.js',
134 => '_inc/build/crowdsignal-survey.min.js',
135 => '_inc/build/custom-post-types/js/many-items.min.js',
136 => '_inc/build/custom-post-types/js/menu-checkboxes.min.js',
137 => '_inc/build/custom-post-types/js/nova-drag-drop.min.js',
138 => '_inc/build/deprecate.min.js',
139 => '_inc/build/facebook-embed.min.js',
140 => '_inc/build/gallery-settings.min.js',
141 => '_inc/build/infinite-scroll/infinity-customizer.min.js',
142 => '_inc/build/infinite-scroll/infinity.min.js',
143 => '_inc/build/jetpack-admin.min.js',
144 => '_inc/build/jetpack-deactivate-dialog.min.js',
145 => '_inc/build/jetpack-modules.min.js',
146 => '_inc/build/jetpack-modules.models.min.js',
147 => '_inc/build/jetpack-modules.views.min.js',
148 => '_inc/build/likes/post-count-jetpack.min.js',
149 => '_inc/build/likes/post-count.min.js',
150 => '_inc/build/likes/queuehandler.min.js',
151 => '_inc/build/plugins-page.css',
152 => '_inc/build/plugins-page.js',
153 => '_inc/build/plugins-page.rtl.css',
154 => '_inc/build/polldaddy-shortcode.min.js',
155 => '_inc/build/related-posts/related-posts-customizer.min.js',
156 => '_inc/build/related-posts/related-posts.min.js',
157 => '_inc/build/scan/admin-bar-notice.min.js',
158 => '_inc/build/sharedaddy/admin-sharing.min.js',
159 => '_inc/build/sharedaddy/sharing.min.js',
160 => '_inc/build/shortcodes/js/brightcove.min.js',
161 => '_inc/build/shortcodes/js/jmpress.min.js',
162 => '_inc/build/shortcodes/js/main.min.js',
163 => '_inc/build/shortcodes/js/quiz.min.js',
164 => '_inc/build/shortcodes/js/recipes-printthis.min.js',
165 => '_inc/build/shortcodes/js/recipes.min.js',
166 => '_inc/build/shortcodes/js/slideshow-shortcode.min.js',
167 => '_inc/build/style.min.css',
168 => '_inc/build/style.min.rtl.css',
169 => '_inc/build/theme-tools/responsive-videos/responsive-videos.css',
170 => '_inc/build/theme-tools/responsive-videos/responsive-videos.min.js',
171 => '_inc/build/theme-tools/responsive-videos/responsive-videos.rtl.css',
172 => '_inc/build/tiled-gallery/tiled-gallery/tiled-gallery.min.js',
173 => '_inc/build/twitter-timeline.min.js',
174 => '_inc/build/videopress/js/editor-view.min.js',
175 => '_inc/build/videopress/js/gutenberg-video-upload.min.js',
176 => '_inc/build/videopress/js/media-video-widget-extensions.min.js',
177 => '_inc/build/videopress/js/videopress-add-resumable-upload-support.min.js',
178 => '_inc/build/videopress/js/videopress-plupload.min.js',
179 => '_inc/build/videopress/js/videopress-uploader.min.js',
180 => '_inc/build/widget-visibility/editor/index.css',
181 => '_inc/build/widget-visibility/editor/index.js',
182 => '_inc/build/widget-visibility/editor/index.rtl.css',
183 => '_inc/build/widget-visibility/widget-conditions/widget-conditions.min.js',
184 => '_inc/build/widgets/contact-info/contact-info-admin.min.js',
185 => '_inc/build/widgets/customizer-utils.min.js',
186 => '_inc/build/widgets/eu-cookie-law/eu-cookie-law-admin.min.js',
187 => '_inc/build/widgets/eu-cookie-law/eu-cookie-law.min.js',
188 => '_inc/build/widgets/gallery/js/admin.min.js',
189 => '_inc/build/widgets/gallery/js/gallery.min.js',
190 => '_inc/build/widgets/google-translate/google-translate.min.js',
191 => '_inc/build/widgets/milestone/admin.min.js',
192 => '_inc/build/widgets/milestone/milestone.min.js',
193 => '_inc/build/widgets/simple-payments/customizer.min.js',
194 => '_inc/build/widgets/social-icons/social-icons-admin.min.js',
195 => '_inc/build/widgets/twitter-timeline-admin.min.js',
196 => '_inc/build/wordads/js/adflow-loader.min.js',
197 => '_inc/build/wordads/js/cmp-loader.min.js',
198 => '_inc/build/wordads/js/wordads-ccpa.min.js',
199 => '_inc/crowdsignal-shortcode.js',
200 => '_inc/crowdsignal-survey.js',
201 => '_inc/deprecate.js',
202 => '_inc/facebook-embed.js',
203 => '_inc/gallery-settings.js',
204 => '_inc/genericons/genericons.css',
205 => '_inc/genericons/genericons/genericons.css',
206 => '_inc/genericons/genericons/rtl/genericons-rtl.css',
207 => '_inc/jetpack-admin.js',
208 => '_inc/jetpack-deactivate-dialog.js',
209 => '_inc/jetpack-modules.js',
210 => '_inc/jetpack-modules.models.js',
211 => '_inc/jetpack-modules.views.js',
212 => '_inc/polldaddy-shortcode.js',
213 => '_inc/social-logos/social-logos.css',
214 => '_inc/social-logos/social-logos.min.css',
215 => '_inc/twitter-timeline.js',
216 => 'css/cleanslate-rtl.css',
217 => 'css/cleanslate-rtl.min.css',
218 => 'css/cleanslate.css',
219 => 'css/cleanslate.min.css',
220 => 'css/dashboard-widget-rtl.css',
221 => 'css/dashboard-widget-rtl.min.css',
222 => 'css/dashboard-widget.css',
223 => 'css/dashboard-widget.min.css',
224 => 'css/jetpack-admin-rtl.css',
225 => 'css/jetpack-admin-rtl.min.css',
226 => 'css/jetpack-admin.css',
227 => 'css/jetpack-admin.min.css',
228 => 'css/jetpack-deactivate-dialog-rtl.css',
229 => 'css/jetpack-deactivate-dialog-rtl.min.css',
230 => 'css/jetpack-deactivate-dialog.css',
231 => 'css/jetpack-deactivate-dialog.min.css',
232 => 'css/wordads-ccpa-rtl.css',
233 => 'css/wordads-ccpa-rtl.min.css',
234 => 'css/wordads-ccpa.css',
235 => 'css/wordads-ccpa.min.css',
236 => 'modules/carousel/jetpack-carousel-rtl.css',
237 => 'modules/carousel/jetpack-carousel.css',
238 => 'modules/carousel/jetpack-carousel.js',
239 => 'modules/carousel/swiper-bundle.css',
240 => 'modules/carousel/swiper-bundle.js',
241 => 'modules/comment-likes/admin-style.css',
242 => 'modules/comment-likes/comment-like-count.js',
243 => 'modules/comments/subscription-modal-on-comment/subscription-modal.css',
244 => 'modules/comments/subscription-modal-on-comment/subscription-modal.js',
245 => 'modules/custom-post-types/css/edit-items.css',
246 => 'modules/custom-post-types/css/many-items.css',
247 => 'modules/custom-post-types/css/nova-font.css',
248 => 'modules/custom-post-types/css/nova.css',
249 => 'modules/custom-post-types/css/portfolio-shortcode.css',
250 => 'modules/custom-post-types/css/testimonial-shortcode.css',
251 => 'modules/custom-post-types/js/many-items.js',
252 => 'modules/custom-post-types/js/menu-checkboxes.js',
253 => 'modules/custom-post-types/js/nova-drag-drop.js',
254 => 'modules/gravatar/gravatar-hovercards-amp.css',
255 => 'modules/infinite-scroll/infinity-customizer.js',
256 => 'modules/infinite-scroll/infinity.css',
257 => 'modules/infinite-scroll/infinity.js',
258 => 'modules/infinite-scroll/themes/twentyeleven.css',
259 => 'modules/infinite-scroll/themes/twentyfifteen-rtl.css',
260 => 'modules/infinite-scroll/themes/twentyfifteen.css',
261 => 'modules/infinite-scroll/themes/twentyfourteen.css',
262 => 'modules/infinite-scroll/themes/twentyseventeen-rtl.css',
263 => 'modules/infinite-scroll/themes/twentyseventeen.css',
264 => 'modules/infinite-scroll/themes/twentysixteen-rtl.css',
265 => 'modules/infinite-scroll/themes/twentysixteen.css',
266 => 'modules/infinite-scroll/themes/twentyten.css',
267 => 'modules/infinite-scroll/themes/twentythirteen.css',
268 => 'modules/infinite-scroll/themes/twentytwelve.css',
269 => 'modules/likes/post-count-jetpack.js',
270 => 'modules/likes/post-count.js',
271 => 'modules/likes/queuehandler.js',
272 => 'modules/likes/style.css',
273 => 'modules/plugin-search/plugin-search.css',
274 => 'modules/plugin-search/plugin-search.js',
275 => 'modules/post-by-email/post-by-email-rtl.css',
276 => 'modules/post-by-email/post-by-email-rtl.min.css',
277 => 'modules/post-by-email/post-by-email.css',
278 => 'modules/post-by-email/post-by-email.js',
279 => 'modules/post-by-email/post-by-email.min.css',
280 => 'modules/related-posts/related-posts-customizer.js',
281 => 'modules/related-posts/related-posts-rtl.css',
282 => 'modules/related-posts/related-posts.css',
283 => 'modules/related-posts/related-posts.js',
284 => 'modules/related-posts/rtl/related-posts-rtl.css',
285 => 'modules/scan/admin-bar-notice.js',
286 => 'modules/sharedaddy/admin-sharing-rtl.css',
287 => 'modules/sharedaddy/admin-sharing-rtl.min.css',
288 => 'modules/sharedaddy/admin-sharing.css',
289 => 'modules/sharedaddy/admin-sharing.js',
290 => 'modules/sharedaddy/admin-sharing.min.css',
291 => 'modules/sharedaddy/amp-sharing.css',
292 => 'modules/sharedaddy/sharing.css',
293 => 'modules/sharedaddy/sharing.js',
294 => 'modules/shortcodes/css/gravatar-amp.css',
295 => 'modules/shortcodes/css/quiz.css',
296 => 'modules/shortcodes/css/recipes-print-rtl.css',
297 => 'modules/shortcodes/css/recipes-print-rtl.min.css',
298 => 'modules/shortcodes/css/recipes-print.css',
299 => 'modules/shortcodes/css/recipes-print.min.css',
300 => 'modules/shortcodes/css/recipes-rtl.css',
301 => 'modules/shortcodes/css/recipes-rtl.min.css',
302 => 'modules/shortcodes/css/recipes.css',
303 => 'modules/shortcodes/css/recipes.min.css',
304 => 'modules/shortcodes/css/slideshow-shortcode-rtl.css',
305 => 'modules/shortcodes/css/slideshow-shortcode-rtl.min.css',
306 => 'modules/shortcodes/css/slideshow-shortcode.css',
307 => 'modules/shortcodes/css/slideshow-shortcode.min.css',
308 => 'modules/shortcodes/css/style.css',
309 => 'modules/shortcodes/js/brightcove.js',
310 => 'modules/shortcodes/js/jmpress.js',
311 => 'modules/shortcodes/js/jquery.cycle.min.js',
312 => 'modules/shortcodes/js/main.js',
313 => 'modules/shortcodes/js/quiz.js',
314 => 'modules/shortcodes/js/recipes-printthis.js',
315 => 'modules/shortcodes/js/recipes.js',
316 => 'modules/shortcodes/js/slideshow-shortcode.js',
317 => 'modules/simple-payments/paypal-express-checkout.js',
318 => 'modules/simple-payments/simple-payments.css',
319 => 'modules/subscriptions/subscribe-floating-button/subscribe-floating-button.css',
320 => 'modules/subscriptions/subscribe-modal/subscribe-modal.css',
321 => 'modules/subscriptions/subscribe-modal/subscribe-modal.js',
322 => 'modules/subscriptions/subscribe-overlay/subscribe-overlay.css',
323 => 'modules/subscriptions/subscribe-overlay/subscribe-overlay.js',
324 => 'modules/subscriptions/subscriptions.css',
325 => 'modules/theme-tools/compat/twentyfifteen-rtl.css',
326 => 'modules/theme-tools/compat/twentyfifteen.css',
327 => 'modules/theme-tools/compat/twentyfourteen-rtl.css',
328 => 'modules/theme-tools/compat/twentyfourteen.css',
329 => 'modules/theme-tools/compat/twentynineteen-rtl.css',
330 => 'modules/theme-tools/compat/twentynineteen.css',
331 => 'modules/theme-tools/compat/twentysixteen-rtl.css',
332 => 'modules/theme-tools/compat/twentysixteen.css',
333 => 'modules/theme-tools/compat/twentytwenty-rtl.css',
334 => 'modules/theme-tools/compat/twentytwenty.css',
335 => 'modules/theme-tools/compat/twentytwentyone-rtl.css',
336 => 'modules/theme-tools/compat/twentytwentyone.css',
337 => 'modules/theme-tools/content-options/customizer.js',
338 => 'modules/theme-tools/js/suggest.js',
339 => 'modules/theme-tools/responsive-videos/responsive-videos.css',
340 => 'modules/theme-tools/responsive-videos/responsive-videos.js',
341 => 'modules/theme-tools/site-logo/js/site-logo-header-text.js',
342 => 'modules/theme-tools/site-logo/js/site-logo-header-text.min.js',
343 => 'modules/theme-tools/social-menu/social-menu.css',
344 => 'modules/tiled-gallery/tiled-gallery/rtl/tiled-gallery-rtl.css',
345 => 'modules/tiled-gallery/tiled-gallery/tiled-gallery-rtl.css',
346 => 'modules/tiled-gallery/tiled-gallery/tiled-gallery.css',
347 => 'modules/tiled-gallery/tiled-gallery/tiled-gallery.js',
348 => 'modules/videopress/css/editor-rtl.css',
349 => 'modules/videopress/css/editor-rtl.min.css',
350 => 'modules/videopress/css/editor.css',
351 => 'modules/videopress/css/editor.min.css',
352 => 'modules/videopress/css/videopress-editor-style-rtl.css',
353 => 'modules/videopress/css/videopress-editor-style-rtl.min.css',
354 => 'modules/videopress/css/videopress-editor-style.css',
355 => 'modules/videopress/css/videopress-editor-style.min.css',
356 => 'modules/videopress/js/editor-view.js',
357 => 'modules/videopress/js/gutenberg-video-upload.js',
358 => 'modules/videopress/js/media-video-widget-extensions.js',
359 => 'modules/videopress/js/videopress-add-resumable-upload-support.js',
360 => 'modules/videopress/js/videopress-plupload.js',
361 => 'modules/videopress/js/videopress-uploader.js',
362 => 'modules/videopress/videopress-admin-rtl.css',
363 => 'modules/videopress/videopress-admin-rtl.min.css',
364 => 'modules/videopress/videopress-admin.css',
365 => 'modules/videopress/videopress-admin.min.css',
366 => 'modules/widget-visibility/widget-conditions/rtl/widget-conditions-rtl.css',
367 => 'modules/widget-visibility/widget-conditions/widget-conditions-rtl.css',
368 => 'modules/widget-visibility/widget-conditions/widget-conditions-rtl.min.css',
369 => 'modules/widget-visibility/widget-conditions/widget-conditions.css',
370 => 'modules/widget-visibility/widget-conditions/widget-conditions.js',
371 => 'modules/widget-visibility/widget-conditions/widget-conditions.min.css',
372 => 'modules/widgets/authors/style.css',
373 => 'modules/widgets/contact-info/contact-info-admin.js',
374 => 'modules/widgets/contact-info/contact-info-map.css',
375 => 'modules/widgets/customizer-controls.css',
376 => 'modules/widgets/customizer-utils.js',
377 => 'modules/widgets/eu-cookie-law/eu-cookie-law-admin.js',
378 => 'modules/widgets/eu-cookie-law/eu-cookie-law.js',
379 => 'modules/widgets/eu-cookie-law/style.css',
380 => 'modules/widgets/facebook-likebox/style.css',
381 => 'modules/widgets/flickr/style.css',
382 => 'modules/widgets/gallery/css/admin-rtl.css',
383 => 'modules/widgets/gallery/css/admin-rtl.min.css',
384 => 'modules/widgets/gallery/css/admin.css',
385 => 'modules/widgets/gallery/css/admin.min.css',
386 => 'modules/widgets/gallery/css/rtl/admin-rtl.css',
387 => 'modules/widgets/gallery/js/admin.js',
388 => 'modules/widgets/gallery/js/gallery.js',
389 => 'modules/widgets/goodreads/css/goodreads.css',
390 => 'modules/widgets/goodreads/css/rtl/goodreads-rtl.css',
391 => 'modules/widgets/google-translate/google-translate.js',
392 => 'modules/widgets/gravatar-profile.css',
393 => 'modules/widgets/image-widget/style.css',
394 => 'modules/widgets/instagram/instagram.css',
395 => 'modules/widgets/milestone/admin.js',
396 => 'modules/widgets/milestone/milestone-widget.css',
397 => 'modules/widgets/milestone/milestone.js',
398 => 'modules/widgets/milestone/style-admin.css',
399 => 'modules/widgets/my-community/style.css',
400 => 'modules/widgets/simple-payments/customizer.css',
401 => 'modules/widgets/simple-payments/customizer.js',
402 => 'modules/widgets/simple-payments/style.css',
403 => 'modules/widgets/social-icons/social-icons-admin.css',
404 => 'modules/widgets/social-icons/social-icons-admin.js',
405 => 'modules/widgets/social-icons/social-icons.css',
406 => 'modules/widgets/social-media-icons/style.css',
407 => 'modules/widgets/top-posts/style.css',
408 => 'modules/widgets/twitter-timeline-admin.js',
409 => 'modules/widgets/wordpress-post-widget/style.css',
410 => 'modules/wordads/css/style.css',
411 => 'modules/wordads/js/adflow-loader.js',
412 => 'modules/wordads/js/cmp-loader.js',
413 => 'modules/wordads/js/wordads-ccpa.js',
414 => 'modules/wpgroho.js',
);
@@ -0,0 +1,17 @@
<?php
/**
* Module Name: Image CDN
* Module Description: Mirrors and serves your images from our free and fast image CDN, improving your sites performance with no additional load on your servers.
* Sort Order: 25
* Recommendation Order: 1
* First Introduced: 2.0
* Requires Connection: Yes
* Auto Activate: No
* Module Tags: Photos and Videos, Appearance, Recommended
* Feature: Recommended, Appearance
* Additional Search Queries: photon, photo cdn, image cdn, speed, compression, resize, responsive images, responsive, content distribution network, optimize, page speed, image optimize, photon jetpack
*
* @package automattic/jetpack
*/
Automattic\Jetpack\Image_CDN\Image_CDN_Setup::load();
@@ -0,0 +1,648 @@
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Adds the PSH functionality to Jetpack.
*
* @package automattic/jetpack
*/
// phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files.
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
use Automattic\Jetpack\Redirect;
use Automattic\Jetpack\Tracking;
// Disable direct access and execution.
if ( ! defined( 'ABSPATH' ) ) {
exit( 0 );
}
if (
is_admin() &&
Jetpack::is_connection_ready() &&
/** This filter is documented in _inc/lib/admin-pages/class.jetpack-react-page.php */
apply_filters( 'jetpack_show_promotions', true ) &&
// Disable feature hints when plugins cannot be installed.
! Constants::is_true( 'DISALLOW_FILE_MODS' ) &&
jetpack_is_psh_active()
) {
Jetpack_Plugin_Search::init();
}
// Register endpoints when WP REST API is initialized.
add_action( 'rest_api_init', array( 'Jetpack_Plugin_Search', 'register_endpoints' ) );
/**
* Class that includes cards in the plugin search results when users enter terms that match some Jetpack feature.
* Card can be dismissed and includes a title, description, button to enable the feature and a link for more information.
*
* @since 7.1.0
*/
class Jetpack_Plugin_Search {
/**
* PSH slug name.
*
* @var string
*/
public static $slug = 'jetpack-plugin-search';
/**
* Singleton constructor.
*
* @return Jetpack_Plugin_Search
*/
public static function init() {
static $instance = null;
if ( ! $instance ) {
$instance = new Jetpack_Plugin_Search();
}
return $instance;
}
/**
* Jetpack_Plugin_Search constructor.
*/
public function __construct() {
add_action( 'current_screen', array( $this, 'start' ) );
}
/**
* Add actions and filters only if this is the plugin installation screen and it's the first page.
*
* @param object $screen WP SCreen object.
*
* @since 7.1.0
*/
public function start( $screen ) {
if ( 'plugin-install' === $screen->base && ( ! isset( $_GET['paged'] ) || 1 === intval( $_GET['paged'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
add_action( 'admin_enqueue_scripts', array( $this, 'load_plugins_search_script' ) );
add_filter( 'plugins_api_result', array( $this, 'inject_jetpack_module_suggestion' ), 10, 3 );
add_filter( 'self_admin_url', array( $this, 'plugin_details' ) );
add_filter( 'plugin_install_action_links', array( $this, 'insert_module_related_links' ), 10, 2 );
}
}
/**
* Modify URL used to fetch to plugin information so it pulls Jetpack plugin page.
*
* @param string $url URL to load in dialog pulling the plugin page from wporg.
*
* @since 7.1.0
*
* @return string The URL with 'jetpack' instead of 'jetpack-plugin-search'.
*/
public function plugin_details( $url ) {
return false !== stripos( $url, 'tab=plugin-information&amp;plugin=' . self::$slug )
? 'plugin-install.php?tab=plugin-information&amp;plugin=jetpack&amp;TB_iframe=true&amp;width=600&amp;height=550'
: $url;
}
/**
* Register REST API endpoints.
*
* @since 7.1.0
*/
public static function register_endpoints() {
register_rest_route(
'jetpack/v4',
'/hints',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => __CLASS__ . '::dismiss',
'permission_callback' => __CLASS__ . '::can_request',
'args' => array(
'hint' => array(
'default' => '',
'type' => 'string',
'required' => true,
'validate_callback' => __CLASS__ . '::is_hint_id',
),
),
)
);
}
/**
* A WordPress REST API permission callback method that accepts a request object and
* decides if the current user has enough privileges to act.
*
* @since 7.1.0
*
* @return bool does a current user have enough privileges.
*/
public static function can_request() {
return current_user_can( 'jetpack_admin_page' );
}
/**
* Validates that the ID of the hint to dismiss is a string.
*
* @since 7.1.0
*
* @param string|bool $value Value to check.
* @param WP_REST_Request $request The request sent to the WP REST API.
* @param string $param Name of the parameter passed to endpoint holding $value.
*
* @return bool|WP_Error
*/
public static function is_hint_id( $value, $request, $param ) {
return in_array( $value, Jetpack::get_available_modules(), true )
? true
/* translators: %s is the name of a parameter passed to an endpoint. */
: new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an alphanumeric string.', 'jetpack' ), $param ) );
}
/**
* A WordPress REST API callback method that accepts a request object and decides what to do with it.
*
* @param WP_REST_Request $request {
* Array of parameters received by request.
*
* @type string $hint Slug of card to dismiss.
* }
*
* @since 7.1.0
*
* @return bool|array|WP_Error a resulting value or object, or an error.
*/
public static function dismiss( WP_REST_Request $request ) {
return self::add_to_dismissed_hints( $request['hint'] )
? rest_ensure_response( array( 'code' => 'success' ) )
: new WP_Error( 'not_dismissed', esc_html__( 'The card could not be dismissed', 'jetpack' ), array( 'status' => 400 ) );
}
/**
* Returns a list of previously dismissed hints.
*
* @since 7.1.0
*
* @return array List of dismissed hints.
*/
protected static function get_dismissed_hints() {
$dismissed_hints = Jetpack_Options::get_option( 'dismissed_hints' );
return isset( $dismissed_hints ) && is_array( $dismissed_hints )
? $dismissed_hints
: array();
}
/**
* Save the hint in the list of dismissed hints.
*
* @since 7.1.0
*
* @param string $hint The hint id, which is a Jetpack module slug.
*
* @return bool Whether the card was added to the list and hence dismissed.
*/
protected static function add_to_dismissed_hints( $hint ) {
return Jetpack_Options::update_option( 'dismissed_hints', array_merge( self::get_dismissed_hints(), array( $hint ) ) );
}
/**
* Checks that the module slug passed should be displayed.
*
* A feature hint will be displayed if it has not been dismissed before or if 2 or fewer other hints have been dismissed.
*
* @since 7.2.1
*
* @param string $hint The hint id, which is a Jetpack module slug.
*
* @return bool True if $hint should be displayed.
*/
protected function should_display_hint( $hint ) {
$dismissed_hints = static::get_dismissed_hints();
// If more than 2 hints have been dismissed, then show no more.
if ( 2 < count( $dismissed_hints ) ) {
return false;
}
$plan = Jetpack_Plan::get();
if ( isset( $plan['class'] ) && ( 'free' === $plan['class'] || 'personal' === $plan['class'] ) && 'vaultpress' === $hint ) {
return false;
}
return ! in_array( $hint, $dismissed_hints, true );
}
/**
* Load the search scripts and CSS for PSH.
*/
public function load_plugins_search_script() {
wp_enqueue_script( self::$slug, plugins_url( 'modules/plugin-search/plugin-search.js', JETPACK__PLUGIN_FILE ), array( 'jquery' ), JETPACK__VERSION, true );
wp_localize_script(
self::$slug,
'jetpackPluginSearch',
array(
'nonce' => wp_create_nonce( 'wp_rest' ),
'base_rest_url' => rest_url( '/jetpack/v4' ),
'poweredBy' => esc_html__( 'by Jetpack (installed)', 'jetpack' ),
'manageSettings' => esc_html__( 'Configure', 'jetpack' ),
'activateModule' => esc_html__( 'Activate Module', 'jetpack' ),
'getStarted' => esc_html__( 'Get started', 'jetpack' ),
'activated' => esc_html__( 'Activated', 'jetpack' ),
'activating' => esc_html__( 'Activating', 'jetpack' ),
'logo' => 'https://ps.w.org/jetpack/assets/icon.svg?rev=1791404',
'legend' => esc_html__(
'This suggestion was made by Jetpack, the security and performance plugin already installed on your site.',
'jetpack'
),
'supportText' => esc_html__(
'Learn more about these suggestions.',
'jetpack'
),
'supportLink' => Redirect::get_url( 'plugin-hint-learn-support' ),
'hideText' => esc_html__( 'Hide this suggestion', 'jetpack' ),
)
);
wp_enqueue_style( self::$slug, plugins_url( 'modules/plugin-search/plugin-search.css', JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION );
}
/**
* Get the plugin repo's data for Jetpack to populate the fields with.
*
* @return array|mixed|object|WP_Error
*/
public static function get_jetpack_plugin_data() {
$data = get_transient( 'jetpack_plugin_data' );
if ( false === $data || is_wp_error( $data ) ) {
include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
$data = plugins_api(
'plugin_information',
array(
'slug' => 'jetpack',
'is_ssl' => is_ssl(),
'fields' => array(
'banners' => true,
'reviews' => true,
'active_installs' => true,
'versions' => false,
'sections' => false,
),
)
);
set_transient( 'jetpack_plugin_data', $data, DAY_IN_SECONDS );
}
return $data;
}
/**
* Create a list with additional features for those we don't have a module, like Akismet.
*
* @since 7.1.0
*
* @return array List of features.
*/
public function get_extra_features() {
return array(
'akismet' => array(
'name' => 'Akismet',
'search_terms' => 'akismet, anti-spam, antispam, comments, spam, spam protection, form spam, captcha, no captcha, nocaptcha, recaptcha, phising, google',
'short_description' => esc_html__( 'Keep your visitors and search engines happy by stopping comment and contact form spam with Akismet.', 'jetpack' ),
'requires_connection' => true,
'module' => 'akismet',
'sort' => '16',
'learn_more_button' => Redirect::get_url( 'plugin-hint-upgrade-akismet' ),
'configure_url' => admin_url( 'admin.php?page=akismet-key-config' ),
),
'sharing-block' => array(
'name' => esc_html__( 'Sharing buttons block', 'jetpack' ),
'search_terms' => 'share, sharing, sharing block, sharing button, social buttons, buttons, share facebook, share twitter, social share, icons, email, facebook, twitter, x, linkedin, pinterest, pocket, social media',
'short_description' => esc_html__( 'Add sharing buttons blocks anywhere on your website to help your visitors share your content.', 'jetpack' ),
'requires_connection' => false,
'module' => 'sharing-block',
'sort' => '13',
'learn_more_button' => Redirect::get_url( 'jetpack-support-sharing-block' ),
'configure_url' => admin_url( 'site-editor.php?path=%2Fwp_template' ),
),
);
}
/**
* Intercept the plugins API response and add in an appropriate card for Jetpack
*
* @param object $result Plugin search results.
* @param string $action unused.
* @param object $args Search args.
*/
public function inject_jetpack_module_suggestion( $result, $action, $args ) {
/*
* Bail if something else hooks into the Plugins' API response
* and does not return results.
*/
if ( empty( $result->plugins ) || is_wp_error( $result ) ) {
return $result;
}
// Looks like a search query; it's matching time.
if ( ! empty( $args->search ) ) {
$searchable_modules = array(
'contact-form',
'monitor',
'photon',
'photon-cdn',
'protect',
'publicize',
'related-posts',
'akismet',
'vaultpress',
'videopress',
'search',
);
/*
* Let's handle the Sharing feature differently.
* If we're using a block-based theme, we should suggest the sharing block.
* If using a classic theme, we should suggest the old sharing module.
*/
if ( wp_is_block_theme() ) {
$searchable_modules[] = 'sharing-block';
} else {
$searchable_modules[] = 'sharedaddy';
}
require_once JETPACK__PLUGIN_DIR . 'class.jetpack-admin.php';
$tracking = new Tracking();
$jetpack_modules_list = array_intersect_key(
array_merge( $this->get_extra_features(), Jetpack_Admin::init()->get_modules() ),
array_flip( $searchable_modules )
);
uasort( $jetpack_modules_list, array( $this, 'by_sorting_option' ) );
// Record event when user searches for a term over 3 chars (less than 3 is not very useful).
if ( strlen( $args->search ) >= 3 ) {
$tracking->record_user_event( 'wpa_plugin_search_term', array( 'search_term' => $args->search ) );
}
// Lowercase, trim, remove punctuation/special chars, decode url, remove 'jetpack'.
$normalized_term = $this->sanitize_search_term( $args->search );
$matching_module = null;
// Try to match a passed search term with module's search terms.
foreach ( $jetpack_modules_list as $module_slug => $module_opts ) {
/*
* Does the site's current plan support the feature?
* We don't use Jetpack_Plan::supports() here because
* that check always returns Akismet as supported,
* since Akismet has a free version.
*/
$current_plan = Jetpack_Plan::get();
$is_supported_by_plan = in_array( $module_slug, $current_plan['supports'], true );
if (
false !== stripos( $module_opts['search_terms'] . ', ' . $module_opts['name'], $normalized_term )
&& $is_supported_by_plan
) {
$matching_module = $module_slug;
break;
}
}
if ( isset( $matching_module ) && $this->should_display_hint( $matching_module ) ) {
// Record event when a matching feature is found.
$tracking->record_user_event( 'wpa_plugin_search_match_found', array( 'feature' => $matching_module ) );
$inject = (array) self::get_jetpack_plugin_data();
$image_url = plugins_url( 'modules/plugin-search/psh', JETPACK__PLUGIN_FILE );
$overrides = array(
'plugin-search' => true, // Helps to determine if that an injected card.
'name' => sprintf( // Supplement name/description so that they clearly indicate this was added.
/* translators: Jetpack module name */
esc_html_x( 'Jetpack: %s', 'Jetpack: Module Name', 'jetpack' ),
$jetpack_modules_list[ $matching_module ]['name']
),
'short_description' => $jetpack_modules_list[ $matching_module ]['short_description'],
'requires_connection' => (bool) $jetpack_modules_list[ $matching_module ]['requires_connection'],
'slug' => self::$slug,
'version' => JETPACK__VERSION,
'icons' => array(
'1x' => "$image_url-128.png",
'2x' => "$image_url-256.png",
'svg' => "$image_url.svg",
),
);
// Splice in the base module data.
$inject = array_merge( $inject, $jetpack_modules_list[ $matching_module ], $overrides );
// Add it to the top of the list.
$result->plugins = array_filter( $result->plugins, array( $this, 'filter_cards' ) );
array_unshift( $result->plugins, $inject );
}
}
return $result;
}
/**
* Remove cards for Jetpack plugins since we don't want duplicates.
*
* @since 7.1.0
* @since 7.2.0 Only remove Jetpack.
* @since 7.4.0 Simplify for WordPress 5.1+.
*
* @param array|object $plugin WordPress search result card.
*
* @return bool
*/
public function filter_cards( $plugin ) {
/*
* $plugin is normally an array.
* However, since the response data can be filtered,
* we cannot fully trust its format.
* Let's handle both arrays and objects, and bail if it's neither.
*/
if ( is_array( $plugin ) && ! empty( $plugin['slug'] ) ) {
$slug = $plugin['slug'];
} elseif ( is_object( $plugin ) && ! empty( $plugin->slug ) ) {
$slug = $plugin->slug;
} else {
return false;
}
return ! in_array( $slug, array( 'jetpack' ), true );
}
/**
* Take a raw search query and return something a bit more standardized and
* easy to work with.
*
* @param string $term The raw search term.
* @return string A simplified/sanitized version.
*/
private function sanitize_search_term( $term ) {
$term = strtolower( urldecode( $term ) );
// remove non-alpha/space chars.
$term = preg_replace( '/[^a-z ]/', '', $term );
// remove strings that don't help matches.
$term = trim( str_replace( array( 'jetpack', 'jp', 'free', 'wordpress' ), '', $term ) );
return $term;
}
/**
* Callback function to sort the array of modules by the sort option.
*
* @param array $m1 Array 1 to sort.
* @param array $m2 Array 2 to sort.
*/
private function by_sorting_option( $m1, $m2 ) {
return $m1['sort'] <=> $m2['sort'];
}
/**
* Modify the URL to the feature settings, for example Publicize.
* Sharing is included here because while we still have a page in WP Admin,
* we prefer to send users to Calypso.
*
* @param string $feature Feature.
* @param string $configure_url URL to configure feature.
*
* @return string
* @since 7.1.0
*/
private function get_configure_url( $feature, $configure_url ) {
switch ( $feature ) {
case 'sharing':
case 'publicize':
$configure_url = Redirect::get_url( 'calypso-marketing-connections' );
break;
case 'seo-tools':
$configure_url = Redirect::get_url(
'calypso-marketing-traffic',
array(
'anchor' => 'seo',
)
);
break;
case 'google-analytics':
$configure_url = Redirect::get_url(
'calypso-marketing-traffic',
array(
'anchor' => 'analytics',
)
);
break;
case 'wordads':
$configure_url = Redirect::get_url( 'wpcom-ads-settings' );
break;
}
return $configure_url;
}
/**
* Put some more appropriate links on our custom result cards.
*
* @param array $links Related links.
* @param array $plugin Plugin result information.
*/
public function insert_module_related_links( $links, $plugin ) {
if ( self::$slug !== $plugin['slug'] ) {
return $links;
}
// By the time this filter is applied, self_admin_url was already applied and we don't need it anymore.
remove_filter( 'self_admin_url', array( $this, 'plugin_details' ) );
$links = array();
if ( 'sharing-block' === $plugin['module'] ) {
$links['jp_get_started'] = '<a
id="plugin-select-settings"
class="jetpack-plugin-search__primary jetpack-plugin-search__get-started button"
href="' . esc_url( admin_url( 'site-editor.php?path=%2Fwp_template' ) ) . '"
data-module="' . esc_attr( $plugin['module'] ) . '"
data-track="get_started"
>' . esc_html__( 'Add block', 'jetpack' ) . '</a>';
} elseif ( 'akismet' === $plugin['module'] || 'vaultpress' === $plugin['module'] ) {
$links['jp_get_started'] = '<a
id="plugin-select-settings"
class="jetpack-plugin-search__primary jetpack-plugin-search__get-started button"
href="' . esc_url( Redirect::get_url( 'plugin-hint-learn-' . $plugin['module'] ) ) . '"
data-module="' . esc_attr( $plugin['module'] ) . '"
data-track="get_started"
>' . esc_html__( 'Get started', 'jetpack' ) . '</a>';
// Jetpack installed, active, feature not enabled; prompt to enable.
} elseif (
current_user_can( 'jetpack_activate_modules' ) &&
! Jetpack::is_module_active( $plugin['module'] ) &&
Jetpack_Plan::supports( $plugin['module'] )
) {
$links[] = '<button
id="plugin-select-activate"
class="jetpack-plugin-search__primary button"
data-module="' . esc_attr( $plugin['module'] ) . '"
data-configure-url="' . esc_url( $this->get_configure_url( $plugin['module'], $plugin['configure_url'] ) ) . '"
> ' . esc_html__( 'Enable', 'jetpack' ) . '</button>';
// Jetpack installed, active, feature enabled; link to settings.
} elseif (
! empty( $plugin['configure_url'] ) &&
current_user_can( 'jetpack_configure_modules' ) &&
Jetpack::is_module_active( $plugin['module'] ) &&
/** This filter is documented in class.jetpack-admin.php */
apply_filters( 'jetpack_module_configurable_' . $plugin['module'], false )
) {
$links[] = '<a
id="plugin-select-settings"
class="jetpack-plugin-search__primary button jetpack-plugin-search__configure"
href="' . esc_url( $this->get_configure_url( $plugin['module'], $plugin['configure_url'] ) ) . '"
data-module="' . esc_attr( $plugin['module'] ) . '"
data-track="configure"
>' . esc_html__( 'Configure', 'jetpack' ) . '</a>';
// Module is active, doesn't have options to configure.
} elseif ( Jetpack::is_module_active( $plugin['module'] ) ) {
$links['jp_get_started'] = '<a
id="plugin-select-settings"
class="jetpack-plugin-search__primary jetpack-plugin-search__get-started button"
href="' . esc_url( Redirect::get_url( 'plugin-hint-learn-' . $plugin['module'] ) ) . '"
data-module="' . esc_attr( $plugin['module'] ) . '"
data-track="get_started"
>' . esc_html__( 'Get started', 'jetpack' ) . '</a>';
}
// Add link pointing to a relevant doc page in jetpack.com only if the Get started button isn't displayed.
if ( ! empty( $plugin['learn_more_button'] ) && ! isset( $links['jp_get_started'] ) ) {
$links[] = '<a
class="jetpack-plugin-search__learn-more"
href="' . esc_url( $plugin['learn_more_button'] ) . '"
target="_blank"
data-module="' . esc_attr( $plugin['module'] ) . '"
data-track="learn_more"
>' . esc_html__( 'Learn more', 'jetpack' ) . '</a>';
}
// Dismiss link.
$links[] = '<a
class="jetpack-plugin-search__dismiss"
data-module="' . esc_attr( $plugin['module'] ) . '"
>' . esc_html__( 'Hide this suggestion', 'jetpack' ) . '</a>';
return $links;
}
}
/**
* Master control that checks if Plugin search hints is active.
*
* @since 7.1.1
*
* @return bool True if PSH is active.
*/
function jetpack_is_psh_active() {
/**
* Disables the Plugin Search Hints feature found when searching the plugins page.
*
* @since 8.7.0
*
* @param bool Set false to disable the feature.
*/
return apply_filters( 'jetpack_psh_active', true );
}
@@ -0,0 +1,84 @@
.plugin-card-jetpack-plugin-search h3 {
margin: 0 0 4px 0;
}
.plugin-card-jetpack-plugin-search .column-name,
.plugin-card-jetpack-plugin-search .column-description {
margin-right: 20px;
}
.plugin-card-jetpack-plugin-search .action-links {
overflow: auto;
position: static;
}
@media screen and (max-width: 1100px) and (min-width: 782px), (max-width: 480px) {
.plugin-card-jetpack-plugin-search .action-links {
margin-left: 0;
}
}
.plugin-card-jetpack-plugin-search .plugin-action-buttons {
margin: 0;
width: 100%;
text-align: left;
white-space: nowrap;
}
.plugin-card-jetpack-plugin-search .plugin-action-buttons .jetpack-plugin-search__primary {
background: #069e08;
border-color: #00a523;
color: #fff;
box-shadow: 0 1px 0 #c5e2c3;
}
.plugin-card-jetpack-plugin-search .plugin-action-buttons .jetpack-plugin-search__primary:hover {
background: #00a523;
border-color: #008b1d;
color: #fff;
}
.plugin-card-jetpack-plugin-search .plugin-card-bottom {
display: none;
}
.jetpack-plugin-search__bottom {
display: flex;
align-items: center;
align-content: space-between;
clear: both;
padding: 12px 20px;
background-color: #f6f7f7;
border-top: 1px solid #dcdcde;
overflow: hidden;
}
.jetpack-plugin-search__text {
flex: 1;
margin: 0 24px 0 16px;
}
@media screen and (max-width: 1100px) and (min-width: 782px), (max-width: 480px) {
.plugin-card-jetpack-plugin-search .plugin-action-buttons li {
display: block;
}
.plugin-card-jetpack-plugin-search .plugin-action-buttons li button {
margin-right: 0;
}
}
/* Hides the link to dismiss cards when it's in action links are before being moved to bottom row*/
.action-links .jetpack-plugin-search__dismiss {
display: none;
}
.jetpack-plugin-search__bottom .jetpack-plugin-search__dismiss {
color: #484848;
font-style: italic;
text-decoration: underline;
cursor: pointer;
}
.jetpack-plugin-search__dismiss:hover {
color: #646970;
}
@@ -0,0 +1,273 @@
/**
* Handles the activation of a Jetpack feature, dismissing the card, and replacing the bottom row
* of the card with customized content.
*/
/* global jetpackPluginSearch, jpTracksAJAX */
var JetpackPSH = {};
( function ( $, jpsh ) {
JetpackPSH = {
$pluginFilter: $( '#plugin-filter' ),
/**
* Get parent search hint element.
* @return {Element | null}
*/
getCard: function () {
return document.querySelector( '.plugin-card-jetpack-plugin-search' );
},
/**
* Track user event such as a click on a button or a link.
*
* @param {string} eventName Event identifier.
* @param {object} feature Identifier of feature involved in the event.
* @param {object} target Object where action was performed.
*/
trackEvent: function ( eventName, feature, target ) {
jpTracksAJAX
.record_ajax_event( eventName, 'click', { feature: feature } )
.always( function () {
if ( 'undefined' !== typeof target && !! target.getAttribute( 'href' ) ) {
// If it has an href, follow it.
window.location = target.getAttribute( 'href' );
}
} );
},
/**
* Update title of the card to add a mention that the result is from the Jetpack plugin.
*/
updateCardTitle: function () {
var hint = JetpackPSH.getCard();
if ( 'object' === typeof hint && null !== hint ) {
var title = hint.querySelector( '.column-name h3' );
title.outerHTML =
title.outerHTML + '<strong>' + jetpackPluginSearch.poweredBy + '</strong>';
}
},
/**
* Move action links below description.
*/
moveActionLinks: function () {
var hint = JetpackPSH.getCard();
if ( 'object' === typeof hint && null !== hint ) {
var descriptionContainer = hint.querySelector( '.column-description' );
// Keep only the first paragraph. The second is the plugin author.
var descriptionText = descriptionContainer.querySelector( 'p:first-child' );
var actionLinks = hint.querySelector( '.action-links' );
// Change the contents of the description, to keep the description text and the action links.
descriptionContainer.innerHTML = descriptionText.outerHTML + actionLinks.outerHTML;
// Remove the action links from their default location.
actionLinks.parentNode.removeChild( actionLinks );
}
},
/**
* Replace bottom row of the card to insert logo, text and link to dismiss the card.
*/
replaceCardBottom: function () {
var hint = JetpackPSH.getCard();
if ( 'object' === typeof hint && null !== hint ) {
hint.querySelector( '.plugin-card-bottom' ).outerHTML =
'<div class="jetpack-plugin-search__bottom"><img src="' +
jetpackPluginSearch.logo +
'" width="32" />' +
'<p class="jetpack-plugin-search__text">' +
jetpackPluginSearch.legend +
' <a class="jetpack-plugin-search__support_link" href="' +
jetpackPluginSearch.supportLink +
'" target="_blank" rel="noopener noreferrer" data-track="support_link" >' +
jetpackPluginSearch.supportText +
'</a>' +
'</p>' +
'</div>';
// Remove link and parent li from action links and move it to bottom row
var dismissLink = document.querySelector( '.jetpack-plugin-search__dismiss' );
dismissLink.parentNode.parentNode.removeChild( dismissLink.parentNode );
document.querySelector( '.jetpack-plugin-search__bottom' ).appendChild( dismissLink );
}
},
/**
* Check if plugin card list nodes changed. If there's a Jetpack PSH card, replace the title and the bottom row.
* @param {array} mutationsList
*/
replaceOnNewResults: function ( mutationsList ) {
mutationsList.forEach( function ( mutation ) {
if (
'childList' === mutation.type &&
1 === document.querySelectorAll( '.plugin-card-jetpack-plugin-search' ).length
) {
JetpackPSH.updateCardTitle();
JetpackPSH.moveActionLinks();
JetpackPSH.replaceCardBottom();
}
} );
},
dismiss: function ( moduleName ) {
document.getElementById( 'the-list' ).removeChild( JetpackPSH.getCard() );
$.ajax( {
url: jpsh.base_rest_url + '/hints',
method: 'post',
beforeSend: function ( xhr ) {
xhr.setRequestHeader( 'X-WP-Nonce', jpsh.nonce );
},
data: JSON.stringify( {
hint: moduleName,
} ),
contentType: 'application/json',
dataType: 'json',
} ).done( function () {
JetpackPSH.trackEvent( 'wpa_plugin_search_dismiss', moduleName );
} );
},
ajaxActivateModule: function ( moduleName ) {
var $moduleBtn = JetpackPSH.$pluginFilter.find( '#plugin-select-activate' );
$moduleBtn.toggleClass( 'install-now updating-message' );
$moduleBtn.prop( 'disabled', true );
$moduleBtn.text( jpsh.activating );
var data = {};
data[ moduleName ] = true;
$.ajax( {
url: jpsh.base_rest_url + '/settings',
method: 'post',
beforeSend: function ( xhr ) {
xhr.setRequestHeader( 'X-WP-Nonce', jpsh.nonce );
},
data: JSON.stringify( data ),
contentType: 'application/json',
dataType: 'json',
} )
.done( function () {
JetpackPSH.updateButton( moduleName );
JetpackPSH.trackEvent( 'wpa_plugin_search_activate', moduleName );
} )
.error( function () {
$moduleBtn.toggleClass( 'install-now updating-message' );
} );
},
// Remove onclick handler, disable loading spinner, update button to redirect to module settings.
updateButton: function ( moduleName ) {
$.ajax( {
url: jpsh.base_rest_url + '/module/' + moduleName,
method: 'get',
beforeSend: function ( xhr ) {
xhr.setRequestHeader( 'X-WP-Nonce', jpsh.nonce );
},
dataType: 'json',
} ).done( function ( response ) {
var $moduleBtn = JetpackPSH.$pluginFilter.find( '#plugin-select-activate' );
$moduleBtn.prop( 'onclick', null ).off( 'click' );
$moduleBtn.toggleClass( 'install-now updating-message' );
$moduleBtn.text( jpsh.activated );
setTimeout( function () {
var url = 'https://jetpack.com/redirect/?source=plugin-hint-learn-' + moduleName,
label = jpsh.getStarted,
classes = 'jetpack-plugin-search__primary button',
track = 'configure';
// If the feature has options in Jetpack admin UI, link to them.
if ( response.options && 0 < Object.keys( response.options ).length ) {
url = $moduleBtn.data( 'configure-url' );
label = jpsh.manageSettings;
classes += ' jetpack-plugin-search__configure';
} else {
// If it has no options, the Get started button will be displayed so remove the Learn more link if it's there.
var learnMore = document.querySelector( '.jetpack-plugin-search__learn-more' );
learnMore.parentNode.removeChild( learnMore );
classes += ' jetpack-plugin-search__get-started';
track = 'get_started';
}
$moduleBtn.replaceWith(
'<a id="plugin-select-settings" class="' +
classes +
'" href="' +
url +
'" data-module="' +
moduleName +
'" data-track="' +
track +
'">' +
label +
'</a>'
);
}, 1000 );
} );
},
/**
* Start suggesting.
*/
init: function () {
if ( JetpackPSH.$pluginFilter.length < 1 ) {
return;
}
// Update title to show that the suggestion is from Jetpack.
JetpackPSH.updateCardTitle();
// Update the description and action links.
JetpackPSH.moveActionLinks();
// Replace PSH bottom row on page load
JetpackPSH.replaceCardBottom();
// Listen for changes in plugin search results
var resultsObserver = new MutationObserver( JetpackPSH.replaceOnNewResults );
resultsObserver.observe( document.getElementById( 'plugin-filter' ), { childList: true } );
JetpackPSH.$pluginFilter
.on( 'click', '.jetpack-plugin-search__dismiss', function ( event ) {
event.preventDefault();
JetpackPSH.dismiss( $( this ).data( 'module' ) );
} )
.on( 'click', 'button#plugin-select-activate', function ( event ) {
event.preventDefault();
JetpackPSH.ajaxActivateModule( $( this ).data( 'module' ) );
} )
.on( 'click', '.jetpack-plugin-search__primary', function ( event ) {
event.preventDefault();
var $this = $( this );
if ( $this.data( 'track' ) ) {
// This catches Purchase, Configure, and Get started. Feature activation is tracked when it ends successfully, in its callback.
JetpackPSH.trackEvent(
'wpa_plugin_search_' + $this.data( 'track' ),
$this.data( 'module' ),
$this.get( 0 )
);
}
} )
.on( 'click', '.jetpack-plugin-search__learn-more', function ( event ) {
event.preventDefault();
var $this = $( this );
JetpackPSH.trackEvent(
'wpa_plugin_search_learn_more',
$this.data( 'module' ),
$this.get( 0 )
);
} )
.on( 'click', '.jetpack-plugin-search__support_link', function ( event ) {
event.preventDefault();
var $this = $( this );
JetpackPSH.trackEvent(
'wpa_plugin_search_support_link',
$this.data( 'module' ),
$this.get( 0 )
);
} );
},
};
JetpackPSH.init();
} )( jQuery, jetpackPluginSearch );
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

@@ -0,0 +1,24 @@
<?php
/**
* Module Name: Post by email
* Module Description: Publish posts by sending an email
* First Introduced: 2.0
* Sort Order: 14
* Requires Connection: Yes
* Requires User Connection: Yes
* Auto Activate: No
* Module Tags: Writing
* Feature: Writing
* Additional Search Queries: post by email, email
*
* @package automattic/jetpack
*/
/**
* Require the PBE Class.
*/
require_once __DIR__ . '/post-by-email/class-jetpack-post-by-email.php';
add_action( 'jetpack_modules_loaded', array( 'Jetpack_Post_By_Email', 'init' ) );
Jetpack::enable_module_configurable( __FILE__ );
@@ -0,0 +1,256 @@
<?php
/**
* Class Jetpack_Post_By_Email
*
* @package automattic/jetpack
*/
use Automattic\Jetpack\Connection\Tokens;
use Automattic\Jetpack\Redirect;
/**
* Class Jetpack_Post_By_Email
*/
class Jetpack_Post_By_Email {
/**
* Initialize PBE.
*
* @return Jetpack_Post_By_Email
*/
public static function init() {
static $instance = null;
if ( ! $instance ) {
$instance = new Jetpack_Post_By_Email();
}
return $instance;
}
/**
* Singleton
*/
private function __construct() {
add_action( 'init', array( $this, 'action_init' ) );
}
/**
* Adds hooks for PBE.
*/
public function action_init() {
if ( ! current_user_can( 'edit_posts' ) ) {
return;
}
add_action( 'profile_personal_options', array( $this, 'user_profile' ) );
add_action( 'admin_print_scripts-profile.php', array( $this, 'profile_scripts' ) );
add_action( 'wp_ajax_jetpack_post_by_email_enable', array( $this, 'create_post_by_email_address' ) );
add_action( 'wp_ajax_jetpack_post_by_email_regenerate', array( $this, 'regenerate_post_by_email_address' ) );
add_action( 'wp_ajax_jetpack_post_by_email_disable', array( $this, 'delete_post_by_email_address' ) );
}
/**
* Enqueues scripts for user profile page.
*/
public function profile_scripts() {
wp_enqueue_script( 'post-by-email', plugins_url( 'post-by-email.js', __FILE__ ), array( 'jquery' ), JETPACK__VERSION, true );
wp_localize_script(
'post-by-email',
'pbeVars',
array(
'rest_nonce' => wp_create_nonce( 'wp_rest' ),
)
);
wp_enqueue_style( 'post-by-email', plugins_url( 'post-by-email.css', __FILE__ ), array(), JETPACK__VERSION );
// Inline styles. @see wp_maybe_inline_styles()
if ( is_rtl() ) {
wp_style_add_data( 'post-by-email', 'path', plugin_dir_path( __FILE__ ) . 'post-by-email-rtl.min.css' );
} else {
wp_style_add_data( 'post-by-email', 'path', plugin_dir_path( __FILE__ ) . 'post-by-email.min.css' );
}
}
/**
* Check if the user is connected.
*
* @return bool True if connected. False if not.
*/
public function check_user_connection() {
$user_token = ( new Tokens() )->get_access_token( get_current_user_id() );
$is_user_connected = $user_token && ! is_wp_error( $user_token );
// If the user is already connected via Jetpack, then we're good.
if ( $is_user_connected ) {
return true;
}
return false;
}
/**
* Adds field to user profile page.
*/
public function user_profile() {
$blog_name = get_bloginfo( 'blogname' );
if ( empty( $blog_name ) ) {
$blog_name = home_url( '/' );
}
?>
<div id="post-by-email" class="jetpack-targetable">
<h3><?php esc_html_e( 'Post by Email', 'jetpack' ); ?></h3>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Email Address', 'jetpack' ); ?><span id="jp-pbe-spinner" class="spinner"></span></th>
<td>
<div id="jp-pbe-error" class="jetpack-inline-error"></div>
<?php
if ( $this->check_user_connection() ) {
$email = $this->get_post_by_email_address();
$enable_button_style = empty( $email ) ? '' : 'display: none;';
$info_style = empty( $email ) ? 'display: none;' : '';
?>
<input type="button" name="jp-pbe-enable" id="jp-pbe-enable" class="button" value="<?php esc_attr_e( 'Enable Post By Email', 'jetpack' ); ?>" style="<?php echo esc_attr( $enable_button_style ); ?>" />
<div id="jp-pbe-info" style="<?php echo esc_attr( $info_style ); ?>">
<p id="jp-pbe-email-wrapper">
<input type="text" id="jp-pbe-email" value="<?php echo esc_attr( $email ); ?>" readonly="readonly" class="regular-text" />
<span class="description"><a target="_blank" rel="noopener noreferrer" href="<?php echo esc_url( Redirect::get_url( 'jetpack-support-post-by-email' ) ); ?>"><?php esc_html_e( 'More information', 'jetpack' ); ?></a></span>
</p>
<p>
<input type="button" name="jp-pbe-regenerate" id="jp-pbe-regenerate" class="button" value="<?php esc_attr_e( 'Regenerate Address', 'jetpack' ); ?> " />
<input type="button" name="jp-pbe-disable" id="jp-pbe-disable" class="button" value="<?php esc_attr_e( 'Disable Post By Email', 'jetpack' ); ?> " />
</p>
</div>
<?php
} else {
$jetpack = Jetpack::init();
?>
<p class="jetpack-inline-message">
<?php
printf(
/* translators: Placeholder is the site's name from WordPress settings. */
esc_html( wptexturize( __( 'To use Post By Email, you need to link your %s account to your WordPress.com account.', 'jetpack' ) ) ),
'<strong>' . esc_html( $blog_name ) . '</strong>'
);
?>
<br />
<?php echo esc_html( wptexturize( __( "If you don't have a WordPress.com account yet, you can sign up for free in just a few seconds.", 'jetpack' ) ) ); ?>
</p>
<p>
<a href="<?php echo esc_url( $jetpack->build_connect_url( false, get_edit_profile_url( get_current_user_id() ) . '#post-by-email', 'unlinked-user-pbe' ) ); ?>" class="button button-connector" id="wpcom-connect"><?php esc_html_e( 'Link account with WordPress.com', 'jetpack' ); ?></a>
</p>
<?php
}
?>
</td>
</tr>
</table>
</div>
<?php
}
/**
* XMLRPC Query to WP.com for PBE e-mail address for user.
*
* @return string|null PBE E-mail Address or null on error.
*/
public function get_post_by_email_address() {
$xml = $this->init_rest_connection();
$xml->query( 'jetpack.getPostByEmailAddress' );
if ( $xml->isError() ) {
return null;
}
$response = $xml->getResponse();
if ( empty( $response ) ) {
return null;
}
return $response;
}
/**
* Process the REST API request to modify the "Post by Email" settings.
*
* @param string $action Allowed values: 'create', 'regenerate', 'delete'.
*
* @return array|false
*/
public function process_api_request( $action ) {
$endpoint = null;
$error_message = esc_html__( 'Please try again later.', 'jetpack' );
$result = false;
switch ( $action ) {
case 'create':
$endpoint = 'jetpack.createPostByEmailAddress';
$error_message = esc_html__( 'Unable to create the Post by Email address. Please try again later.', 'jetpack' );
break;
case 'regenerate':
$endpoint = 'jetpack.regeneratePostByEmailAddress';
$error_message = esc_html__( 'Unable to regenerate the Post by Email address. Please try again later.', 'jetpack' );
break;
case 'delete':
$endpoint = 'jetpack.deletePostByEmailAddress';
$error_message = esc_html__( 'Unable to delete the Post by Email address. Please try again later.', 'jetpack' );
break;
}
if ( $endpoint ) {
$result = $this->process_rest_proxy_request( $endpoint, $error_message );
}
return $result;
}
/**
* Calls WPCOM through authenticated request to create, regenerate or delete the Post by Email address.
*
* @since 4.3.0
*
* @param string $endpoint Process to call on WPCOM to create, regenerate or delete the Post by Email address.
* @param string $error Error message to return.
*
* @return array
*/
private function process_rest_proxy_request( $endpoint, $error ) {
if ( ! current_user_can( 'edit_posts' ) ) {
return array( 'message' => $error );
}
$xml = $this->init_rest_connection();
$xml->query( $endpoint );
if ( $xml->isError() ) {
return array( 'message' => $error );
}
$response = $xml->getResponse();
if ( empty( $response ) ) {
return array( 'message' => $error );
}
// Used only in Jetpack_Core_Json_Api_Endpoints::get_remote_value.
update_option( 'post_by_email_address' . get_current_user_id(), $response );
return $response;
}
/**
* Initialize the IXR client
*
* @return Jetpack_IXR_Client
*/
private function init_rest_connection() {
return new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) );
}
}
@@ -0,0 +1,7 @@
#jp-pbe-error {
display: none;
}
#post-by-email:target .jetpack-inline-message {
background-color: #fff;
}
@@ -0,0 +1 @@
#jp-pbe-error{display:none}#post-by-email:target .jetpack-inline-message{background-color:#fff}
@@ -0,0 +1,6 @@
#jp-pbe-error {
display: none;
}
#post-by-email:target .jetpack-inline-message {
background-color: #fff;
}

Some files were not shown because too many files have changed in this diff Show More