PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.1.5
Jetpack – WP Security, Backup, Speed, & Growth v13.1.5
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / custom-post-types / testimonial.php
jetpack / modules / custom-post-types Last commit date
comics 4 years ago css 5 years ago js 5 years ago comics.php 2 years ago nova.php 2 years ago portfolios.php 3 years ago testimonial.php 3 years ago
testimonial.php
924 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Register a Testimonial post type and handle displaying it anywhere on the site.
4 *
5 * @package automattic/jetpack
6 * @phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
7 * @phpcs:disable MediaWiki.Usage.NestedFunctions.NestedFunction
8 */
9
10 // phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files.
11
12 /**
13 * Add a Testimonial CPT, and display it with a shortcode
14 */
15 class Jetpack_Testimonial {
16 const CUSTOM_POST_TYPE = 'jetpack-testimonial';
17 const OPTION_NAME = 'jetpack_testimonial';
18 const OPTION_READING_SETTING = 'jetpack_testimonial_posts_per_page';
19
20 /**
21 * Initialize class.
22 *
23 * @return self
24 */
25 public static function init() {
26 static $instance = false;
27
28 if ( ! $instance ) {
29 $instance = new Jetpack_Testimonial();
30 }
31
32 return $instance;
33 }
34
35 /**
36 * Conditionally hook into WordPress.
37 *
38 * Setup user option for enabling CPT.
39 * If user has CPT enabled, show in admin.
40 */
41 public function __construct() {
42 // Make sure the post types are loaded for imports
43 add_action( 'import_start', array( $this, 'register_post_types' ) );
44
45 // If called via REST API, we need to register later in lifecycle
46 add_action( 'restapi_theme_init', array( $this, 'maybe_register_cpt' ) );
47
48 // Add to REST API post type allowed list.
49 add_filter( 'rest_api_allowed_post_types', array( $this, 'allow_cpt_rest_api_type' ) );
50
51 $this->maybe_register_cpt();
52 }
53
54 /**
55 * Registers the custom post types and adds action/filter handlers, but
56 * only if the site supports it
57 */
58 public function maybe_register_cpt() {
59 // Add an option to enable the CPT
60 add_action( 'admin_init', array( $this, 'settings_api_init' ) );
61
62 // Check on theme switch if theme supports CPT and setting is disabled
63 add_action( 'after_switch_theme', array( $this, 'activation_post_type_support' ) );
64
65 $setting = Jetpack_Options::get_option_and_ensure_autoload( self::OPTION_NAME, '0' );
66
67 // Bail early if Testimonial option is not set and the theme doesn't declare support
68 if ( empty( $setting ) && ! $this->site_supports_custom_post_type() ) {
69 return;
70 }
71
72 if ( ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) && ! Jetpack::is_module_active( 'custom-content-types' ) ) {
73 return;
74 }
75
76 // CPT magic
77 $this->register_post_types();
78 add_action( sprintf( 'add_option_%s', self::OPTION_NAME ), array( $this, 'flush_rules_on_enable' ), 10 );
79 add_action( sprintf( 'update_option_%s', self::OPTION_NAME ), array( $this, 'flush_rules_on_enable' ), 10 );
80 add_action( sprintf( 'publish_%s', self::CUSTOM_POST_TYPE ), array( $this, 'flush_rules_on_first_testimonial' ) );
81 add_action( 'after_switch_theme', array( $this, 'flush_rules_on_switch' ) );
82
83 // Admin Customization
84 add_filter( 'enter_title_here', array( $this, 'change_default_title' ) );
85 add_filter( sprintf( 'manage_%s_posts_columns', self::CUSTOM_POST_TYPE ), array( $this, 'edit_title_column_label' ) );
86 add_filter( 'post_updated_messages', array( $this, 'updated_messages' ) );
87 if ( ! wp_is_block_theme() ) {
88 add_action( 'customize_register', array( $this, 'customize_register' ) );
89 }
90
91 // Only add the 'Customize' sub-menu if the theme supports it.
92 if ( is_admin() && current_theme_supports( self::CUSTOM_POST_TYPE ) && ! empty( self::count_testimonials() ) ) {
93 add_action( 'admin_menu', array( $this, 'add_customize_page' ) );
94 }
95
96 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
97 // Track all the things
98 add_action( sprintf( 'add_option_%s', self::OPTION_NAME ), array( $this, 'new_activation_stat_bump' ) );
99 add_action( sprintf( 'update_option_%s', self::OPTION_NAME ), array( $this, 'update_option_stat_bump' ), 11, 2 );
100 add_action( sprintf( 'publish_%s', self::CUSTOM_POST_TYPE ), array( $this, 'new_testimonial_stat_bump' ) );
101
102 // Add to Dotcom XML sitemaps
103 add_filter( 'wpcom_sitemap_post_types', array( $this, 'add_to_sitemap' ) );
104 } else {
105 // Add to Jetpack XML sitemap
106 add_filter( 'jetpack_sitemap_post_types', array( $this, 'add_to_sitemap' ) );
107 }
108
109 // Adjust CPT archive and custom taxonomies to obey CPT reading setting
110 add_filter( 'pre_get_posts', array( $this, 'query_reading_setting' ), 20 );
111 add_filter( 'infinite_scroll_settings', array( $this, 'infinite_scroll_click_posts_per_page' ) );
112
113 // Register [jetpack_testimonials] always and
114 // register [testimonials] if [testimonials] isn't already set
115 add_shortcode( 'jetpack_testimonials', array( $this, 'jetpack_testimonial_shortcode' ) );
116
117 if ( ! shortcode_exists( 'testimonials' ) ) {
118 add_shortcode( 'testimonials', array( $this, 'jetpack_testimonial_shortcode' ) );
119 }
120
121 // If CPT was enabled programatically and no CPT items exist when user switches away, disable
122 if ( $setting && $this->site_supports_custom_post_type() ) {
123 add_action( 'switch_theme', array( $this, 'deactivation_post_type_support' ) );
124 }
125 }
126
127 /**
128 * Add a checkbox field in 'Settings' > 'Writing'
129 * for enabling CPT functionality.
130 *
131 * @return void
132 */
133 public function settings_api_init() {
134 add_settings_field(
135 self::OPTION_NAME,
136 '<span class="cpt-options">' . __( 'Testimonials', 'jetpack' ) . '</span>',
137 array( $this, 'setting_html' ),
138 'writing',
139 'jetpack_cpt_section'
140 );
141
142 register_setting(
143 'writing',
144 self::OPTION_NAME,
145 'intval'
146 );
147
148 // Check if CPT is enabled first so that intval doesn't get set to NULL on re-registering.
149 if ( $this->site_supports_custom_post_type() ) {
150 register_setting(
151 'writing',
152 self::OPTION_READING_SETTING,
153 'intval'
154 );
155 }
156 }
157
158 /**
159 * HTML code to display a checkbox true/false option
160 * for the CPT setting.
161 *
162 * @return void
163 */
164 public function setting_html() {
165 if ( current_theme_supports( self::CUSTOM_POST_TYPE ) ) : ?>
166 <p><?php esc_html_e( 'Your theme supports Testimonials', 'jetpack' ); ?></p>
167 <?php else : ?>
168 <label for="<?php echo esc_attr( self::OPTION_NAME ); ?>">
169 <input name="<?php echo esc_attr( self::OPTION_NAME ); ?>" id="<?php echo esc_attr( self::OPTION_NAME ); ?>" <?php echo checked( get_option( self::OPTION_NAME, '0' ), true, false ); ?> type="checkbox" value="1" />
170 <?php esc_html_e( 'Enable Testimonials for this site.', 'jetpack' ); ?>
171 <a target="_blank" href="https://en.support.wordpress.com/testimonials/"><?php esc_html_e( 'Learn More', 'jetpack' ); ?></a>
172 </label>
173 <?php
174 endif;
175
176 if ( $this->site_supports_custom_post_type() ) :
177 printf(
178 '<p><label for="%1$s">%2$s</label></p>',
179 esc_attr( self::OPTION_READING_SETTING ),
180 sprintf(
181 /* translators: %1$s is replaced with an input field for numbers */
182 esc_html__( 'Testimonial pages display at most %1$s testimonials', 'jetpack' ),
183 sprintf(
184 '<input name="%1$s" id="%1$s" type="number" step="1" min="1" value="%2$s" class="small-text" />',
185 esc_attr( self::OPTION_READING_SETTING ),
186 esc_attr( get_option( self::OPTION_READING_SETTING, '10' ) )
187 )
188 )
189 );
190 endif;
191 }
192
193 /**
194 * Should this Custom Post Type be made available?
195 */
196 private function site_supports_custom_post_type() {
197 // If the current theme requests it.
198 if ( current_theme_supports( self::CUSTOM_POST_TYPE ) || get_option( self::OPTION_NAME, '0' ) ) {
199 return true;
200 }
201
202 // Otherwise, say no unless something wants to filter us to say yes.
203 /** This action is documented in modules/custom-post-types/nova.php */
204 return (bool) apply_filters( 'jetpack_enable_cpt', false, self::CUSTOM_POST_TYPE );
205 }
206
207 /**
208 * Add to REST API post type allowed list.
209 *
210 * @param array $post_types Array of allowed post types.
211 * @return array `$post_types` with our type added.
212 */
213 public function allow_cpt_rest_api_type( $post_types ) {
214 $post_types[] = self::CUSTOM_POST_TYPE;
215
216 return $post_types;
217 }
218
219 /**
220 * Bump Testimonial > New Activation stat
221 */
222 public function new_activation_stat_bump() {
223 /** This action is documented in modules/widgets/social-media-icons.php */
224 do_action( 'jetpack_bump_stats_extras', 'testimonials', 'new-activation' );
225 }
226
227 /**
228 * Bump Testimonial > Option On/Off stats to get total active
229 *
230 * @param mixed $old The old option value.
231 * @param mixed $new The new option value.
232 */
233 public function update_option_stat_bump( $old, $new ) {
234 if ( empty( $old ) && ! empty( $new ) ) {
235 /** This action is documented in modules/widgets/social-media-icons.php */
236 do_action( 'jetpack_bump_stats_extras', 'testimonials', 'option-on' );
237 }
238
239 if ( ! empty( $old ) && empty( $new ) ) {
240 /** This action is documented in modules/widgets/social-media-icons.php */
241 do_action( 'jetpack_bump_stats_extras', 'testimonials', 'option-off' );
242 }
243 }
244
245 /**
246 * Bump Testimonial > Published Testimonials stat when testimonials are published
247 */
248 public function new_testimonial_stat_bump() {
249 /** This action is documented in modules/widgets/social-media-icons.php */
250 do_action( 'jetpack_bump_stats_extras', 'testimonials', 'published-testimonials' );
251 }
252
253 /**
254 * Flush permalinks when CPT option is turned on/off
255 */
256 public function flush_rules_on_enable() {
257 flush_rewrite_rules();
258 }
259
260 /**
261 * Count published testimonials and flush permalinks when first testimonial is published
262 */
263 public function flush_rules_on_first_testimonial() {
264 $testimonials = get_transient( 'jetpack-testimonial-count-cache' );
265
266 if ( false === $testimonials ) {
267 flush_rewrite_rules();
268 $testimonials = (int) wp_count_posts( self::CUSTOM_POST_TYPE )->publish;
269
270 if ( ! empty( $testimonials ) ) {
271 set_transient( 'jetpack-testimonial-count-cache', $testimonials, HOUR_IN_SECONDS * 12 );
272 }
273 }
274 }
275
276 /**
277 * Flush permalinks when CPT supported theme is activated
278 */
279 public function flush_rules_on_switch() {
280 if ( current_theme_supports( self::CUSTOM_POST_TYPE ) ) {
281 flush_rewrite_rules();
282 }
283 }
284
285 /**
286 * On plugin/theme activation, check if current theme supports CPT
287 */
288 public static function activation_post_type_support() {
289 if ( current_theme_supports( self::CUSTOM_POST_TYPE ) ) {
290 update_option( self::OPTION_NAME, '1' );
291 }
292 }
293
294 /**
295 * On theme switch, check if CPT item exists and disable if not
296 */
297 public function deactivation_post_type_support() {
298 $portfolios = get_posts(
299 array(
300 'fields' => 'ids',
301 'posts_per_page' => 1,
302 'post_type' => self::CUSTOM_POST_TYPE,
303 'suppress_filters' => false,
304 )
305 );
306
307 if ( empty( $portfolios ) ) {
308 update_option( self::OPTION_NAME, '0' );
309 }
310 }
311
312 /**
313 * Register Post Type
314 */
315 public function register_post_types() {
316 if ( post_type_exists( self::CUSTOM_POST_TYPE ) ) {
317 return;
318 }
319
320 register_post_type(
321 self::CUSTOM_POST_TYPE,
322 array(
323 'description' => __( 'Customer Testimonials', 'jetpack' ),
324 'labels' => array(
325 'name' => esc_html__( 'Testimonials', 'jetpack' ),
326 'singular_name' => esc_html__( 'Testimonial', 'jetpack' ),
327 'menu_name' => esc_html__( 'Testimonials', 'jetpack' ),
328 'all_items' => esc_html__( 'All Testimonials', 'jetpack' ),
329 'add_new' => esc_html__( 'Add New', 'jetpack' ),
330 'add_new_item' => esc_html__( 'Add New Testimonial', 'jetpack' ),
331 'edit_item' => esc_html__( 'Edit Testimonial', 'jetpack' ),
332 'new_item' => esc_html__( 'New Testimonial', 'jetpack' ),
333 'view_item' => esc_html__( 'View Testimonial', 'jetpack' ),
334 'search_items' => esc_html__( 'Search Testimonials', 'jetpack' ),
335 'not_found' => esc_html__( 'No Testimonials found', 'jetpack' ),
336 'not_found_in_trash' => esc_html__( 'No Testimonials found in Trash', 'jetpack' ),
337 'filter_items_list' => esc_html__( 'Filter Testimonials list', 'jetpack' ),
338 'items_list_navigation' => esc_html__( 'Testimonial list navigation', 'jetpack' ),
339 'items_list' => esc_html__( 'Testimonials list', 'jetpack' ),
340 ),
341 'supports' => array(
342 'title',
343 'editor',
344 'thumbnail',
345 'page-attributes',
346 'revisions',
347 'excerpt',
348 'newspack_blocks',
349 ),
350 'rewrite' => array(
351 'slug' => 'testimonial',
352 'with_front' => false,
353 'feeds' => false,
354 'pages' => true,
355 ),
356 'public' => true,
357 'show_ui' => true,
358 'menu_position' => 20, // below Pages
359 'menu_icon' => 'dashicons-testimonial',
360 'capability_type' => 'page',
361 'map_meta_cap' => true,
362 'has_archive' => true,
363 'query_var' => 'testimonial',
364 'show_in_rest' => true,
365 )
366 );
367 }
368
369 /**
370 * Update messages for the Testimonial admin.
371 *
372 * @param array $messages Existing post update messages.
373 * @return array Updated `$messages`.
374 */
375 public function updated_messages( $messages ) {
376 global $post;
377
378 $messages[ self::CUSTOM_POST_TYPE ] = array(
379 0 => '', // Unused. Messages start at index 1.
380 1 => sprintf(
381 /* Translators: link to Testimonial item's page. */
382 __( 'Testimonial updated. <a href="%s">View testimonial</a>', 'jetpack' ),
383 esc_url( get_permalink( $post->ID ) )
384 ),
385 2 => esc_html__( 'Custom field updated.', 'jetpack' ),
386 3 => esc_html__( 'Custom field deleted.', 'jetpack' ),
387 4 => esc_html__( 'Testimonial updated.', 'jetpack' ),
388 5 => isset( $_GET['revision'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Copying core message handling.
389 ? sprintf(
390 /* translators: %s: date and time of the revision */
391 esc_html__( 'Testimonial restored to revision from %s', 'jetpack' ),
392 wp_post_revision_title( (int) $_GET['revision'], false ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Copying core message handling.
393 )
394 : false,
395 6 => sprintf(
396 /* Translators: link to Testimonial item's page. */
397 __( 'Testimonial published. <a href="%s">View testimonial</a>', 'jetpack' ),
398 esc_url( get_permalink( $post->ID ) )
399 ),
400 7 => esc_html__( 'Testimonial saved.', 'jetpack' ),
401 8 => sprintf(
402 /* Translators: link to Testimonial item's page. */
403 __( 'Testimonial submitted. <a target="_blank" href="%s">Preview testimonial</a>', 'jetpack' ),
404 esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) )
405 ),
406 9 => sprintf(
407 /* Translators: 1: Publishing date and time. 2. Link to testimonial's item page. */
408 __( 'Testimonial scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview testimonial</a>', 'jetpack' ),
409 // translators: Publish box date format, see https://php.net/date
410 date_i18n( __( 'M j, Y @ G:i', 'jetpack' ), strtotime( $post->post_date ) ),
411 esc_url( get_permalink( $post->ID ) )
412 ),
413 10 => sprintf(
414 /* Translators: link to Testimonial item's page. */
415 __( 'Testimonial draft updated. <a target="_blank" href="%s">Preview testimonial</a>', 'jetpack' ),
416 esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) )
417 ),
418 );
419
420 return $messages;
421 }
422
423 /**
424 * Change ‘Enter Title Here’ text for the Testimonial.
425 *
426 * @param string $title Placeholder text. Default 'Add title'.
427 * @return string Replacement title.
428 */
429 public function change_default_title( $title ) {
430 if ( self::CUSTOM_POST_TYPE === get_post_type() ) {
431 $title = esc_html__( "Enter the customer's name here", 'jetpack' );
432 }
433
434 return $title;
435 }
436
437 /**
438 * Change ‘Title’ column label on all Testimonials page.
439 *
440 * @param array $columns An array of column names.
441 * @return array Updated array.
442 */
443 public function edit_title_column_label( $columns ) {
444 $columns['title'] = esc_html__( 'Customer Name', 'jetpack' );
445
446 return $columns;
447 }
448
449 /**
450 * Follow CPT reading setting on CPT archive page
451 *
452 * @param WP_Query $query A WP_Query instance.
453 */
454 public function query_reading_setting( $query ) {
455 if ( ! is_admin()
456 && $query->is_main_query()
457 && $query->is_post_type_archive( self::CUSTOM_POST_TYPE )
458 ) {
459 $query->set( 'posts_per_page', get_option( self::OPTION_READING_SETTING, '10' ) );
460 }
461 }
462
463 /**
464 * If Infinite Scroll is set to 'click', use our custom reading setting instead of core's `posts_per_page`.
465 *
466 * @param array $settings Array of Infinite Scroll settings.
467 * @return array Updated `$settings`.
468 */
469 public function infinite_scroll_click_posts_per_page( $settings ) {
470 global $wp_query;
471
472 if ( ! is_admin() && true === $settings['click_handle'] && $wp_query->is_post_type_archive( self::CUSTOM_POST_TYPE ) ) {
473 $settings['posts_per_page'] = get_option( self::OPTION_READING_SETTING, $settings['posts_per_page'] ); // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page
474 }
475
476 return $settings;
477 }
478
479 /**
480 * Add CPT to Dotcom sitemap
481 *
482 * @param array $post_types Array of post types included in sitemap.
483 * @return array Updated `$post_types`.
484 */
485 public function add_to_sitemap( $post_types ) {
486 $post_types[] = self::CUSTOM_POST_TYPE;
487
488 return $post_types;
489 }
490
491 /**
492 * Count the number of published testimonials.
493 *
494 * @return int
495 */
496 private function count_testimonials() {
497 $testimonials = get_transient( 'jetpack-testimonial-count-cache' );
498
499 if ( false === $testimonials ) {
500 $testimonials = (int) wp_count_posts( self::CUSTOM_POST_TYPE )->publish;
501
502 if ( ! empty( $testimonials ) ) {
503 set_transient( 'jetpack-testimonial-count-cache', $testimonials, 60 * 60 * 12 );
504 }
505 }
506
507 return $testimonials;
508 }
509
510 /**
511 * Adds a submenu link to the Customizer.
512 */
513 public function add_customize_page() {
514 add_submenu_page(
515 'edit.php?post_type=' . self::CUSTOM_POST_TYPE,
516 esc_html__( 'Customize Testimonials Archive', 'jetpack' ),
517 esc_html__( 'Customize', 'jetpack' ),
518 'edit_theme_options',
519 add_query_arg(
520 array(
521 'url' => rawurlencode( home_url( '/testimonial/' ) ),
522 'autofocus[section]' => 'jetpack_testimonials',
523 ),
524 'customize.php'
525 )
526 );
527 }
528
529 /**
530 * Adds testimonial section to the Customizer.
531 *
532 * @param WP_Customize_Manager $wp_customize Customizer instance.
533 */
534 public function customize_register( $wp_customize ) {
535 jetpack_testimonial_custom_control_classes();
536
537 $wp_customize->add_section(
538 'jetpack_testimonials',
539 array(
540 'title' => esc_html__( 'Testimonials', 'jetpack' ),
541 'theme_supports' => self::CUSTOM_POST_TYPE,
542 'priority' => 130,
543 )
544 );
545
546 $wp_customize->add_setting(
547 'jetpack_testimonials[page-title]',
548 array(
549 'default' => esc_html__( 'Testimonials', 'jetpack' ),
550 'sanitize_callback' => array( 'Jetpack_Testimonial_Title_Control', 'sanitize_content' ),
551 'sanitize_js_callback' => array( 'Jetpack_Testimonial_Title_Control', 'sanitize_content' ),
552 )
553 );
554 $wp_customize->add_control(
555 'jetpack_testimonials[page-title]',
556 array(
557 'section' => 'jetpack_testimonials',
558 'label' => esc_html__( 'Testimonial Archive Title', 'jetpack' ),
559 'type' => 'text',
560 )
561 );
562
563 $wp_customize->add_setting(
564 'jetpack_testimonials[page-content]',
565 array(
566 'default' => '',
567 'sanitize_callback' => array( 'Jetpack_Testimonial_Textarea_Control', 'sanitize_content' ),
568 'sanitize_js_callback' => array( 'Jetpack_Testimonial_Textarea_Control', 'sanitize_content' ),
569 )
570 );
571 $wp_customize->add_control(
572 new Jetpack_Testimonial_Textarea_Control(
573 $wp_customize,
574 'jetpack_testimonials[page-content]',
575 array(
576 'section' => 'jetpack_testimonials',
577 'settings' => 'jetpack_testimonials[page-content]',
578 'label' => esc_html__( 'Testimonial Archive Content', 'jetpack' ),
579 )
580 )
581 );
582
583 $wp_customize->add_setting(
584 'jetpack_testimonials[featured-image]',
585 array(
586 'default' => '',
587 'sanitize_callback' => 'attachment_url_to_postid',
588 'sanitize_js_callback' => 'attachment_url_to_postid',
589 'theme_supports' => 'post-thumbnails',
590 )
591 );
592 $wp_customize->add_control(
593 new WP_Customize_Image_Control(
594 $wp_customize,
595 'jetpack_testimonials[featured-image]',
596 array(
597 'section' => 'jetpack_testimonials',
598 'label' => esc_html__( 'Testimonial Archive Featured Image', 'jetpack' ),
599 )
600 )
601 );
602
603 // The featured image control doesn't display properly in the Customizer unless we coerce
604 // it back into a URL sooner, since that's what WP_Customize_Upload_Control::to_json() expects
605 if ( is_admin() ) {
606 add_filter( 'theme_mod_jetpack_testimonials', array( $this, 'coerce_testimonial_image_to_url' ) );
607 }
608 }
609
610 /**
611 * Add Featured image to theme mod if necessary.
612 *
613 * @param array $opt The value of the current theme modification.
614 * @return array Updated `$opt`.
615 */
616 public function coerce_testimonial_image_to_url( $opt ) {
617 if ( ! $opt || ! is_array( $opt ) ) {
618 return $opt;
619 }
620 if ( ! isset( $opt['featured-image'] ) || ! is_scalar( $opt['featured-image'] ) ) {
621 return $opt;
622 }
623 $url = wp_get_attachment_url( $opt['featured-image'] );
624 if ( $url ) {
625 $opt['featured-image'] = $url;
626 }
627 return $opt;
628 }
629
630 /**
631 * Our [testimonial] shortcode.
632 * Prints Testimonial data styled to look good on *any* theme.
633 *
634 * @param array $atts Shortcode attributes.
635 *
636 * @return string HTML from `self::jetpack_testimonial_shortcode_html()`.
637 */
638 public static function jetpack_testimonial_shortcode( $atts ) {
639 // Default attributes.
640 $atts = shortcode_atts(
641 array(
642 'display_content' => true, // Can be false, true, or full.
643 'image' => true,
644 'columns' => 1,
645 'showposts' => -1,
646 'order' => 'asc',
647 'orderby' => 'menu_order,date',
648 ),
649 $atts,
650 'testimonial'
651 );
652
653 // A little sanitization.
654 if (
655 $atts['display_content']
656 && 'true' != $atts['display_content'] // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
657 && 'full' !== $atts['display_content']
658 ) {
659 $atts['display_content'] = false;
660 }
661
662 if ( $atts['image'] && 'true' != $atts['image'] ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
663 $atts['image'] = false;
664 }
665
666 $atts['columns'] = absint( $atts['columns'] );
667
668 $atts['showposts'] = (int) $atts['showposts'];
669
670 if ( $atts['order'] ) {
671 $atts['order'] = urldecode( $atts['order'] );
672 $atts['order'] = strtoupper( $atts['order'] );
673 if ( 'DESC' !== $atts['order'] ) {
674 $atts['order'] = 'ASC';
675 }
676 }
677
678 if ( $atts['orderby'] ) {
679 $atts['orderby'] = urldecode( $atts['orderby'] );
680 $atts['orderby'] = strtolower( $atts['orderby'] );
681 $allowed_keys = array( 'author', 'date', 'title', 'menu_order', 'rand' );
682
683 $parsed = array();
684 foreach ( explode( ',', $atts['orderby'] ) as $orderby ) {
685 if ( ! in_array( $orderby, $allowed_keys, true ) ) {
686 continue;
687 }
688 $parsed[] = $orderby;
689 }
690
691 if ( empty( $parsed ) ) {
692 unset( $atts['orderby'] );
693 } else {
694 $atts['orderby'] = implode( ' ', $parsed );
695 }
696 }
697
698 // enqueue shortcode styles when shortcode is used
699 if ( ! wp_style_is( 'jetpack-testimonial-style', 'enqueued' ) ) {
700 wp_enqueue_style( 'jetpack-testimonial-style', plugins_url( 'css/testimonial-shortcode.css', __FILE__ ), array(), '20140326' );
701 }
702
703 return self::jetpack_testimonial_shortcode_html( $atts );
704 }
705
706 /**
707 * The Testimonial shortcode loop.
708 *
709 * @param array $atts Shortcode attributes.
710 *
711 * @return string html
712 */
713 private static function jetpack_testimonial_shortcode_html( $atts ) {
714 // Default query arguments
715 $defaults = array(
716 'order' => $atts['order'],
717 'orderby' => $atts['orderby'],
718 'posts_per_page' => $atts['showposts'],
719 );
720
721 $args = wp_parse_args( $atts, $defaults );
722 $args['post_type'] = self::CUSTOM_POST_TYPE; // Force this post type
723 $query = new WP_Query( $args );
724
725 $testimonial_index_number = 0;
726
727 ob_start();
728
729 // If we have testimonials, create the html
730 if ( $query->have_posts() ) {
731
732 ?>
733 <div class="jetpack-testimonial-shortcode column-<?php echo esc_attr( $atts['columns'] ); ?>">
734 <?php
735 // Construct the loop...
736 while ( $query->have_posts() ) {
737 $query->the_post();
738 $post_id = get_the_ID();
739 ?>
740 <div class="testimonial-entry <?php echo esc_attr( self::get_testimonial_class( $testimonial_index_number, $atts['columns'], has_post_thumbnail( $post_id ) ) ); ?>">
741 <?php
742 // The content
743 if ( false !== $atts['display_content'] ) {
744 if ( 'full' === $atts['display_content'] ) {
745 ?>
746 <div class="testimonial-entry-content"><?php the_content(); ?></div>
747 <?php
748 } else {
749 ?>
750 <div class="testimonial-entry-content"><?php the_excerpt(); ?></div>
751 <?php
752 }
753 }
754 ?>
755 <span class="testimonial-entry-title">&#8213; <a href="<?php echo esc_url( get_permalink() ); ?>" title="<?php echo esc_attr( the_title_attribute() ); ?>"><?php the_title(); ?></a></span>
756 <?php
757 // Featured image
758 if ( false !== $atts['image'] ) :
759 echo self::get_testimonial_thumbnail_link( $post_id ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in method.
760 endif;
761 ?>
762 </div><!-- close .testimonial-entry -->
763 <?php
764 ++$testimonial_index_number;
765 } // end of while loop
766
767 wp_reset_postdata();
768 ?>
769 </div><!-- close .jetpack-testimonial-shortcode -->
770 <?php
771 } else {
772 ?>
773 <p><em><?php esc_html_e( 'Your Testimonial Archive currently has no entries. You can start creating them on your dashboard.', 'jetpack' ); ?></p></em>
774 <?php
775 }
776 $html = ob_get_clean();
777
778 // Return the HTML block
779 return $html;
780 }
781
782 /**
783 * Individual testimonial class
784 *
785 * @param int $testimonial_index_number iterator count the number of columns up starting from 0.
786 * @param int $columns number of columns to display the content in.
787 * @param bool $image has a thumbnail or not.
788 *
789 * @return string
790 */
791 private static function get_testimonial_class( $testimonial_index_number, $columns, $image ) {
792 $class = array();
793
794 $class[] = 'testimonial-entry-column-' . $columns;
795
796 if ( $columns > 1 ) {
797 if ( ( $testimonial_index_number % 2 ) === 0 ) {
798 $class[] = 'testimonial-entry-mobile-first-item-row';
799 } else {
800 $class[] = 'testimonial-entry-mobile-last-item-row';
801 }
802 }
803
804 // add first and last classes to first and last items in a row
805 if ( ( $testimonial_index_number % $columns ) === 0 ) {
806 $class[] = 'testimonial-entry-first-item-row';
807 } elseif ( ( $testimonial_index_number % $columns ) === ( $columns - 1 ) ) {
808 $class[] = 'testimonial-entry-last-item-row';
809 }
810
811 // add class if testimonial has a featured image
812 if ( false !== $image ) {
813 $class[] = 'has-testimonial-thumbnail';
814 }
815
816 /**
817 * Filter the class applied to testimonial div in the testimonial
818 *
819 * @module custom-content-types
820 *
821 * @since 3.4.0
822 *
823 * @param string $class class name of the div.
824 * @param int $testimonial_index_number iterator count the number of columns up starting from 0.
825 * @param int $columns number of columns to display the content in.
826 * @param boolean $image has a thumbnail or not.
827 */
828 return apply_filters( 'testimonial-entry-post-class', implode( ' ', $class ), $testimonial_index_number, $columns, $image ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
829 }
830
831 /**
832 * Display the featured image if it's available
833 *
834 * @param int $post_id Post ID.
835 *
836 * @return string html
837 */
838 private static function get_testimonial_thumbnail_link( $post_id ) {
839 if ( has_post_thumbnail( $post_id ) ) {
840 /**
841 * Change the thumbnail size for the Testimonial CPT.
842 *
843 * @module custom-content-types
844 *
845 * @since 3.4.0
846 *
847 * @param string|array $var Either a registered size keyword or size array.
848 */
849 return '<a class="testimonial-featured-image" href="' . esc_url( get_permalink( $post_id ) ) . '">' . get_the_post_thumbnail( $post_id, apply_filters( 'jetpack_testimonial_thumbnail_size', 'thumbnail' ) ) . '</a>';
850 }
851 }
852 }
853
854 /**
855 * Additional Testimonial customizer options.
856 */
857 function jetpack_testimonial_custom_control_classes() {
858 /**
859 * Clean the title parameter.
860 */
861 class Jetpack_Testimonial_Title_Control extends WP_Customize_Control {
862 /**
863 * Sanitize content passed to control.
864 *
865 * @param string $value Control value.
866 * @return string Sanitized value.
867 */
868 public static function sanitize_content( $value ) {
869 if ( '' != $value ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
870 $value = trim( convert_chars( wptexturize( $value ) ) );
871 }
872
873 return $value;
874 }
875 }
876
877 /**
878 * Clean textarea content.
879 */
880 class Jetpack_Testimonial_Textarea_Control extends WP_Customize_Control {
881 /**
882 * Control type.
883 *
884 * @var string
885 */
886 public $type = 'textarea';
887
888 /**
889 * Render the control's content.
890 */
891 public function render_content() {
892 ?>
893 <label>
894 <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
895 <textarea rows="5" style="width:100%;" <?php $this->link(); ?>><?php echo esc_textarea( $this->value() ); ?></textarea>
896 </label>
897 <?php
898 }
899
900 /**
901 * Sanitize content passed to control.
902 *
903 * @param string $value Control value.
904 * @return string Sanitized value.
905 */
906 public static function sanitize_content( $value ) {
907 if ( ! empty( $value ) ) {
908 /** This filter is already documented in core. wp-includes/post-template.php */
909 $value = apply_filters( 'the_content', $value );
910 }
911
912 $value = preg_replace( '@<div id="jp-post-flair"([^>]+)?>(.+)?</div>@is', '', $value ); // Strip WPCOM and Jetpack post flair if included in content
913
914 return $value;
915 }
916 }
917 }
918
919 add_action( 'init', array( 'Jetpack_Testimonial', 'init' ) );
920
921 // Check on plugin activation if theme supports CPT
922 register_activation_hook( __FILE__, array( 'Jetpack_Testimonial', 'activation_post_type_support' ) );
923 add_action( 'jetpack_activate_module_custom-content-types', array( 'Jetpack_Testimonial', 'activation_post_type_support' ) );
924