PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 14.1.1
Jetpack – WP Security, Backup, Speed, & Growth v14.1.1
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
css 5 years ago js 5 years ago nova.php 2 years ago portfolios.php 1 year ago testimonial.php 1 year ago
testimonial.php
928 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 *
7 * @phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
8 * @phpcs:disable MediaWiki.Usage.NestedFunctions.NestedFunction
9 */
10
11 // phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files.
12
13 if ( ! class_exists( 'Jetpack_Testimonial' ) ) {
14 /**
15 * Add a Testimonial CPT, and display it with a shortcode
16 */
17 class Jetpack_Testimonial {
18 const CUSTOM_POST_TYPE = 'jetpack-testimonial';
19 const OPTION_NAME = 'jetpack_testimonial';
20 const OPTION_READING_SETTING = 'jetpack_testimonial_posts_per_page';
21
22 /**
23 * Initialize class.
24 *
25 * @return self
26 */
27 public static function init() {
28 static $instance = false;
29
30 if ( ! $instance ) {
31 $instance = new Jetpack_Testimonial();
32 }
33
34 return $instance;
35 }
36
37 /**
38 * Conditionally hook into WordPress.
39 *
40 * Setup user option for enabling CPT.
41 * If user has CPT enabled, show in admin.
42 */
43 public function __construct() {
44 // Make sure the post types are loaded for imports
45 add_action( 'import_start', array( $this, 'register_post_types' ) );
46
47 // If called via REST API, we need to register later in lifecycle
48 add_action( 'restapi_theme_init', array( $this, 'maybe_register_cpt' ) );
49
50 // Add to REST API post type allowed list.
51 add_filter( 'rest_api_allowed_post_types', array( $this, 'allow_cpt_rest_api_type' ) );
52
53 $this->maybe_register_cpt();
54 }
55
56 /**
57 * Registers the custom post types and adds action/filter handlers, but
58 * only if the site supports it
59 */
60 public function maybe_register_cpt() {
61 // Add an option to enable the CPT. Set the priority to 11 to ensure "Portfolio Projects" appears above "Testimonials" in the UI.
62 add_action( 'admin_init', array( $this, 'settings_api_init' ), 11 );
63
64 // Check on theme switch if theme supports CPT and setting is disabled
65 add_action( 'after_switch_theme', array( $this, 'activation_post_type_support' ) );
66
67 $setting = Jetpack_Options::get_option_and_ensure_autoload( self::OPTION_NAME, '0' );
68
69 // Bail early if Testimonial option is not set and the theme doesn't declare support
70 if ( empty( $setting ) && ! $this->site_supports_custom_post_type() ) {
71 return;
72 }
73
74 if ( ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) && ! Jetpack::is_module_active( 'custom-content-types' ) ) {
75 return;
76 }
77
78 // CPT magic
79 $this->register_post_types();
80 add_action( sprintf( 'add_option_%s', self::OPTION_NAME ), array( $this, 'flush_rules_on_enable' ), 10 );
81 add_action( sprintf( 'update_option_%s', self::OPTION_NAME ), array( $this, 'flush_rules_on_enable' ), 10 );
82 add_action( sprintf( 'publish_%s', self::CUSTOM_POST_TYPE ), array( $this, 'flush_rules_on_first_testimonial' ) );
83 add_action( 'after_switch_theme', array( $this, 'flush_rules_on_switch' ) );
84
85 // Admin Customization
86 add_filter( 'enter_title_here', array( $this, 'change_default_title' ) );
87 add_filter( sprintf( 'manage_%s_posts_columns', self::CUSTOM_POST_TYPE ), array( $this, 'edit_title_column_label' ) );
88 add_filter( 'post_updated_messages', array( $this, 'updated_messages' ) );
89 if ( ! wp_is_block_theme() ) {
90 add_action( 'customize_register', array( $this, 'customize_register' ) );
91 }
92
93 // Only add the 'Customize' sub-menu if the theme supports it.
94 if ( is_admin() && current_theme_supports( self::CUSTOM_POST_TYPE ) && ! empty( self::count_testimonials() ) ) {
95 add_action( 'admin_menu', array( $this, 'add_customize_page' ) );
96 }
97
98 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
99 // Track all the things
100 add_action( sprintf( 'add_option_%s', self::OPTION_NAME ), array( $this, 'new_activation_stat_bump' ) );
101 add_action( sprintf( 'update_option_%s', self::OPTION_NAME ), array( $this, 'update_option_stat_bump' ), 11, 2 );
102 add_action( sprintf( 'publish_%s', self::CUSTOM_POST_TYPE ), array( $this, 'new_testimonial_stat_bump' ) );
103
104 // Add to Dotcom XML sitemaps
105 add_filter( 'wpcom_sitemap_post_types', array( $this, 'add_to_sitemap' ) );
106 } else {
107 // Add to Jetpack XML sitemap
108 add_filter( 'jetpack_sitemap_post_types', array( $this, 'add_to_sitemap' ) );
109 }
110
111 // Adjust CPT archive and custom taxonomies to obey CPT reading setting
112 add_filter( 'pre_get_posts', array( $this, 'query_reading_setting' ), 20 );
113 add_filter( 'infinite_scroll_settings', array( $this, 'infinite_scroll_click_posts_per_page' ) );
114
115 // Register [jetpack_testimonials] always and
116 // register [testimonials] if [testimonials] isn't already set
117 add_shortcode( 'jetpack_testimonials', array( $this, 'jetpack_testimonial_shortcode' ) );
118
119 if ( ! shortcode_exists( 'testimonials' ) ) {
120 add_shortcode( 'testimonials', array( $this, 'jetpack_testimonial_shortcode' ) );
121 }
122
123 // If CPT was enabled programatically and no CPT items exist when user switches away, disable
124 if ( $setting && $this->site_supports_custom_post_type() ) {
125 add_action( 'switch_theme', array( $this, 'deactivation_post_type_support' ) );
126 }
127 }
128
129 /**
130 * Add a checkbox field in 'Settings' > 'Writing'
131 * for enabling CPT functionality.
132 *
133 * @return void
134 */
135 public function settings_api_init() {
136 add_settings_field(
137 self::OPTION_NAME,
138 '<span class="cpt-options">' . __( 'Testimonials', 'jetpack' ) . '</span>',
139 array( $this, 'setting_html' ),
140 'writing',
141 'jetpack_cpt_section'
142 );
143
144 register_setting(
145 'writing',
146 self::OPTION_NAME,
147 'intval'
148 );
149
150 // Check if CPT is enabled first so that intval doesn't get set to NULL on re-registering.
151 if ( $this->site_supports_custom_post_type() ) {
152 register_setting(
153 'writing',
154 self::OPTION_READING_SETTING,
155 'intval'
156 );
157 }
158 }
159
160 /**
161 * HTML code to display a checkbox true/false option
162 * for the CPT setting.
163 *
164 * @return void
165 */
166 public function setting_html() {
167 if ( current_theme_supports( self::CUSTOM_POST_TYPE ) ) : ?>
168 <p><?php esc_html_e( 'Your theme supports Testimonials', 'jetpack' ); ?></p>
169 <?php else : ?>
170 <label for="<?php echo esc_attr( self::OPTION_NAME ); ?>">
171 <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" />
172 <?php esc_html_e( 'Enable Testimonials for this site.', 'jetpack' ); ?>
173 <a target="_blank" href="https://en.support.wordpress.com/testimonials/"><?php esc_html_e( 'Learn More', 'jetpack' ); ?></a>
174 </label>
175 <?php
176 endif;
177
178 if ( $this->site_supports_custom_post_type() ) :
179 printf(
180 '<p><label for="%1$s">%2$s</label></p>',
181 esc_attr( self::OPTION_READING_SETTING ),
182 sprintf(
183 /* translators: %1$s is replaced with an input field for numbers */
184 esc_html__( 'Testimonial pages display at most %1$s testimonials', 'jetpack' ),
185 sprintf(
186 '<input name="%1$s" id="%1$s" type="number" step="1" min="1" value="%2$s" class="small-text" />',
187 esc_attr( self::OPTION_READING_SETTING ),
188 esc_attr( get_option( self::OPTION_READING_SETTING, '10' ) )
189 )
190 )
191 );
192 endif;
193 }
194
195 /**
196 * Should this Custom Post Type be made available?
197 */
198 private function site_supports_custom_post_type() {
199 // If the current theme requests it.
200 if ( current_theme_supports( self::CUSTOM_POST_TYPE ) || get_option( self::OPTION_NAME, '0' ) ) {
201 return true;
202 }
203
204 // Otherwise, say no unless something wants to filter us to say yes.
205 /** This action is documented in modules/custom-post-types/nova.php */
206 return (bool) apply_filters( 'jetpack_enable_cpt', false, self::CUSTOM_POST_TYPE );
207 }
208
209 /**
210 * Add to REST API post type allowed list.
211 *
212 * @param array $post_types Array of allowed post types.
213 * @return array `$post_types` with our type added.
214 */
215 public function allow_cpt_rest_api_type( $post_types ) {
216 $post_types[] = self::CUSTOM_POST_TYPE;
217
218 return $post_types;
219 }
220
221 /**
222 * Bump Testimonial > New Activation stat
223 */
224 public function new_activation_stat_bump() {
225 /** This action is documented in modules/widgets/social-media-icons.php */
226 do_action( 'jetpack_bump_stats_extras', 'testimonials', 'new-activation' );
227 }
228
229 /**
230 * Bump Testimonial > Option On/Off stats to get total active
231 *
232 * @param mixed $old The old option value.
233 * @param mixed $new The new option value.
234 */
235 public function update_option_stat_bump( $old, $new ) {
236 if ( empty( $old ) && ! empty( $new ) ) {
237 /** This action is documented in modules/widgets/social-media-icons.php */
238 do_action( 'jetpack_bump_stats_extras', 'testimonials', 'option-on' );
239 }
240
241 if ( ! empty( $old ) && empty( $new ) ) {
242 /** This action is documented in modules/widgets/social-media-icons.php */
243 do_action( 'jetpack_bump_stats_extras', 'testimonials', 'option-off' );
244 }
245 }
246
247 /**
248 * Bump Testimonial > Published Testimonials stat when testimonials are published
249 */
250 public function new_testimonial_stat_bump() {
251 /** This action is documented in modules/widgets/social-media-icons.php */
252 do_action( 'jetpack_bump_stats_extras', 'testimonials', 'published-testimonials' );
253 }
254
255 /**
256 * Flush permalinks when CPT option is turned on/off
257 */
258 public function flush_rules_on_enable() {
259 flush_rewrite_rules();
260 }
261
262 /**
263 * Count published testimonials and flush permalinks when first testimonial is published
264 */
265 public function flush_rules_on_first_testimonial() {
266 $testimonials = get_transient( 'jetpack-testimonial-count-cache' );
267
268 if ( false === $testimonials ) {
269 flush_rewrite_rules();
270 $testimonials = (int) wp_count_posts( self::CUSTOM_POST_TYPE )->publish;
271
272 if ( ! empty( $testimonials ) ) {
273 set_transient( 'jetpack-testimonial-count-cache', $testimonials, HOUR_IN_SECONDS * 12 );
274 }
275 }
276 }
277
278 /**
279 * Flush permalinks when CPT supported theme is activated
280 */
281 public function flush_rules_on_switch() {
282 if ( current_theme_supports( self::CUSTOM_POST_TYPE ) ) {
283 flush_rewrite_rules();
284 }
285 }
286
287 /**
288 * On plugin/theme activation, check if current theme supports CPT
289 */
290 public static function activation_post_type_support() {
291 if ( current_theme_supports( self::CUSTOM_POST_TYPE ) ) {
292 update_option( self::OPTION_NAME, '1' );
293 }
294 }
295
296 /**
297 * On theme switch, check if CPT item exists and disable if not
298 */
299 public function deactivation_post_type_support() {
300 $portfolios = get_posts(
301 array(
302 'fields' => 'ids',
303 'posts_per_page' => 1,
304 'post_type' => self::CUSTOM_POST_TYPE,
305 'suppress_filters' => false,
306 )
307 );
308
309 if ( empty( $portfolios ) ) {
310 update_option( self::OPTION_NAME, '0' );
311 }
312 }
313
314 /**
315 * Register Post Type
316 */
317 public function register_post_types() {
318 if ( post_type_exists( self::CUSTOM_POST_TYPE ) ) {
319 return;
320 }
321
322 register_post_type(
323 self::CUSTOM_POST_TYPE,
324 array(
325 'description' => __( 'Customer Testimonials', 'jetpack' ),
326 'labels' => array(
327 'name' => esc_html__( 'Testimonials', 'jetpack' ),
328 'singular_name' => esc_html__( 'Testimonial', 'jetpack' ),
329 'menu_name' => esc_html__( 'Testimonials', 'jetpack' ),
330 'all_items' => esc_html__( 'All Testimonials', 'jetpack' ),
331 'add_new' => esc_html__( 'Add New', 'jetpack' ),
332 'add_new_item' => esc_html__( 'Add New Testimonial', 'jetpack' ),
333 'edit_item' => esc_html__( 'Edit Testimonial', 'jetpack' ),
334 'new_item' => esc_html__( 'New Testimonial', 'jetpack' ),
335 'view_item' => esc_html__( 'View Testimonial', 'jetpack' ),
336 'search_items' => esc_html__( 'Search Testimonials', 'jetpack' ),
337 'not_found' => esc_html__( 'No Testimonials found', 'jetpack' ),
338 'not_found_in_trash' => esc_html__( 'No Testimonials found in Trash', 'jetpack' ),
339 'filter_items_list' => esc_html__( 'Filter Testimonials list', 'jetpack' ),
340 'items_list_navigation' => esc_html__( 'Testimonial list navigation', 'jetpack' ),
341 'items_list' => esc_html__( 'Testimonials list', 'jetpack' ),
342 ),
343 'supports' => array(
344 'title',
345 'editor',
346 'thumbnail',
347 'page-attributes',
348 'revisions',
349 'excerpt',
350 'newspack_blocks',
351 ),
352 'rewrite' => array(
353 'slug' => 'testimonial',
354 'with_front' => false,
355 'feeds' => false,
356 'pages' => true,
357 ),
358 'public' => true,
359 'show_ui' => true,
360 'menu_position' => 20, // below Pages
361 'menu_icon' => 'dashicons-testimonial',
362 'capability_type' => 'page',
363 'map_meta_cap' => true,
364 'has_archive' => true,
365 'query_var' => 'testimonial',
366 'show_in_rest' => true,
367 )
368 );
369 }
370
371 /**
372 * Update messages for the Testimonial admin.
373 *
374 * @param array $messages Existing post update messages.
375 * @return array Updated `$messages`.
376 */
377 public function updated_messages( $messages ) {
378 global $post;
379
380 $messages[ self::CUSTOM_POST_TYPE ] = array(
381 0 => '', // Unused. Messages start at index 1.
382 1 => sprintf(
383 /* Translators: link to Testimonial item's page. */
384 __( 'Testimonial updated. <a href="%s">View testimonial</a>', 'jetpack' ),
385 esc_url( get_permalink( $post->ID ) )
386 ),
387 2 => esc_html__( 'Custom field updated.', 'jetpack' ),
388 3 => esc_html__( 'Custom field deleted.', 'jetpack' ),
389 4 => esc_html__( 'Testimonial updated.', 'jetpack' ),
390 5 => isset( $_GET['revision'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Copying core message handling.
391 ? sprintf(
392 /* translators: %s: date and time of the revision */
393 esc_html__( 'Testimonial restored to revision from %s', 'jetpack' ),
394 wp_post_revision_title( (int) $_GET['revision'], false ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Copying core message handling.
395 )
396 : false,
397 6 => sprintf(
398 /* Translators: link to Testimonial item's page. */
399 __( 'Testimonial published. <a href="%s">View testimonial</a>', 'jetpack' ),
400 esc_url( get_permalink( $post->ID ) )
401 ),
402 7 => esc_html__( 'Testimonial saved.', 'jetpack' ),
403 8 => sprintf(
404 /* Translators: link to Testimonial item's page. */
405 __( 'Testimonial submitted. <a target="_blank" href="%s">Preview testimonial</a>', 'jetpack' ),
406 esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) )
407 ),
408 9 => sprintf(
409 /* Translators: 1: Publishing date and time. 2. Link to testimonial's item page. */
410 __( 'Testimonial scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview testimonial</a>', 'jetpack' ),
411 // translators: Publish box date format, see https://php.net/date
412 date_i18n( __( 'M j, Y @ G:i', 'jetpack' ), strtotime( $post->post_date ) ),
413 esc_url( get_permalink( $post->ID ) )
414 ),
415 10 => sprintf(
416 /* Translators: link to Testimonial item's page. */
417 __( 'Testimonial draft updated. <a target="_blank" href="%s">Preview testimonial</a>', 'jetpack' ),
418 esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) )
419 ),
420 );
421
422 return $messages;
423 }
424
425 /**
426 * Change ‘Enter Title Here’ text for the Testimonial.
427 *
428 * @param string $title Placeholder text. Default 'Add title'.
429 * @return string Replacement title.
430 */
431 public function change_default_title( $title ) {
432 if ( self::CUSTOM_POST_TYPE === get_post_type() ) {
433 $title = esc_html__( "Enter the customer's name here", 'jetpack' );
434 }
435
436 return $title;
437 }
438
439 /**
440 * Change ‘Title’ column label on all Testimonials page.
441 *
442 * @param array $columns An array of column names.
443 * @return array Updated array.
444 */
445 public function edit_title_column_label( $columns ) {
446 $columns['title'] = esc_html__( 'Customer Name', 'jetpack' );
447
448 return $columns;
449 }
450
451 /**
452 * Follow CPT reading setting on CPT archive page
453 *
454 * @param WP_Query $query A WP_Query instance.
455 */
456 public function query_reading_setting( $query ) {
457 if ( ! is_admin()
458 && $query->is_main_query()
459 && $query->is_post_type_archive( self::CUSTOM_POST_TYPE )
460 ) {
461 $query->set( 'posts_per_page', get_option( self::OPTION_READING_SETTING, '10' ) );
462 }
463 }
464
465 /**
466 * If Infinite Scroll is set to 'click', use our custom reading setting instead of core's `posts_per_page`.
467 *
468 * @param array $settings Array of Infinite Scroll settings.
469 * @return array Updated `$settings`.
470 */
471 public function infinite_scroll_click_posts_per_page( $settings ) {
472 global $wp_query;
473
474 if ( ! is_admin() && true === $settings['click_handle'] && $wp_query->is_post_type_archive( self::CUSTOM_POST_TYPE ) ) {
475 $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
476 }
477
478 return $settings;
479 }
480
481 /**
482 * Add CPT to Dotcom sitemap
483 *
484 * @param array $post_types Array of post types included in sitemap.
485 * @return array Updated `$post_types`.
486 */
487 public function add_to_sitemap( $post_types ) {
488 $post_types[] = self::CUSTOM_POST_TYPE;
489
490 return $post_types;
491 }
492
493 /**
494 * Count the number of published testimonials.
495 *
496 * @return int
497 */
498 private function count_testimonials() {
499 $testimonials = get_transient( 'jetpack-testimonial-count-cache' );
500
501 if ( false === $testimonials ) {
502 $testimonials = (int) wp_count_posts( self::CUSTOM_POST_TYPE )->publish;
503
504 if ( ! empty( $testimonials ) ) {
505 set_transient( 'jetpack-testimonial-count-cache', $testimonials, 60 * 60 * 12 );
506 }
507 }
508
509 return $testimonials;
510 }
511
512 /**
513 * Adds a submenu link to the Customizer.
514 */
515 public function add_customize_page() {
516 add_submenu_page(
517 'edit.php?post_type=' . self::CUSTOM_POST_TYPE,
518 esc_html__( 'Customize Testimonials Archive', 'jetpack' ),
519 esc_html__( 'Customize', 'jetpack' ),
520 'edit_theme_options',
521 add_query_arg(
522 array(
523 'url' => rawurlencode( home_url( '/testimonial/' ) ),
524 'autofocus[section]' => 'jetpack_testimonials',
525 ),
526 'customize.php'
527 )
528 );
529 }
530
531 /**
532 * Adds testimonial section to the Customizer.
533 *
534 * @param WP_Customize_Manager $wp_customize Customizer instance.
535 */
536 public function customize_register( $wp_customize ) {
537 jetpack_testimonial_custom_control_classes();
538
539 $wp_customize->add_section(
540 'jetpack_testimonials',
541 array(
542 'title' => esc_html__( 'Testimonials', 'jetpack' ),
543 'theme_supports' => self::CUSTOM_POST_TYPE,
544 'priority' => 130,
545 )
546 );
547
548 $wp_customize->add_setting(
549 'jetpack_testimonials[page-title]',
550 array(
551 'default' => esc_html__( 'Testimonials', 'jetpack' ),
552 'sanitize_callback' => array( 'Jetpack_Testimonial_Title_Control', 'sanitize_content' ),
553 'sanitize_js_callback' => array( 'Jetpack_Testimonial_Title_Control', 'sanitize_content' ),
554 )
555 );
556 $wp_customize->add_control(
557 'jetpack_testimonials[page-title]',
558 array(
559 'section' => 'jetpack_testimonials',
560 'label' => esc_html__( 'Testimonial Archive Title', 'jetpack' ),
561 'type' => 'text',
562 )
563 );
564
565 $wp_customize->add_setting(
566 'jetpack_testimonials[page-content]',
567 array(
568 'default' => '',
569 'sanitize_callback' => array( 'Jetpack_Testimonial_Textarea_Control', 'sanitize_content' ),
570 'sanitize_js_callback' => array( 'Jetpack_Testimonial_Textarea_Control', 'sanitize_content' ),
571 )
572 );
573 $wp_customize->add_control(
574 new Jetpack_Testimonial_Textarea_Control(
575 $wp_customize,
576 'jetpack_testimonials[page-content]',
577 array(
578 'section' => 'jetpack_testimonials',
579 'settings' => 'jetpack_testimonials[page-content]',
580 'label' => esc_html__( 'Testimonial Archive Content', 'jetpack' ),
581 )
582 )
583 );
584
585 $wp_customize->add_setting(
586 'jetpack_testimonials[featured-image]',
587 array(
588 'default' => '',
589 'sanitize_callback' => 'attachment_url_to_postid',
590 'sanitize_js_callback' => 'attachment_url_to_postid',
591 'theme_supports' => 'post-thumbnails',
592 )
593 );
594 $wp_customize->add_control(
595 new WP_Customize_Image_Control(
596 $wp_customize,
597 'jetpack_testimonials[featured-image]',
598 array(
599 'section' => 'jetpack_testimonials',
600 'label' => esc_html__( 'Testimonial Archive Featured Image', 'jetpack' ),
601 )
602 )
603 );
604
605 // The featured image control doesn't display properly in the Customizer unless we coerce
606 // it back into a URL sooner, since that's what WP_Customize_Upload_Control::to_json() expects
607 if ( is_admin() ) {
608 add_filter( 'theme_mod_jetpack_testimonials', array( $this, 'coerce_testimonial_image_to_url' ) );
609 }
610 }
611
612 /**
613 * Add Featured image to theme mod if necessary.
614 *
615 * @param array $opt The value of the current theme modification.
616 * @return array Updated `$opt`.
617 */
618 public function coerce_testimonial_image_to_url( $opt ) {
619 if ( ! $opt || ! is_array( $opt ) ) {
620 return $opt;
621 }
622 if ( ! isset( $opt['featured-image'] ) || ! is_scalar( $opt['featured-image'] ) ) {
623 return $opt;
624 }
625 $url = wp_get_attachment_url( $opt['featured-image'] );
626 if ( $url ) {
627 $opt['featured-image'] = $url;
628 }
629 return $opt;
630 }
631
632 /**
633 * Our [testimonial] shortcode.
634 * Prints Testimonial data styled to look good on *any* theme.
635 *
636 * @param array $atts Shortcode attributes.
637 *
638 * @return string HTML from `self::jetpack_testimonial_shortcode_html()`.
639 */
640 public static function jetpack_testimonial_shortcode( $atts ) {
641 // Default attributes.
642 $atts = shortcode_atts(
643 array(
644 'display_content' => true, // Can be false, true, or full.
645 'image' => true,
646 'columns' => 1,
647 'showposts' => -1,
648 'order' => 'asc',
649 'orderby' => 'menu_order,date',
650 ),
651 $atts,
652 'testimonial'
653 );
654
655 // A little sanitization.
656 if (
657 $atts['display_content']
658 && 'true' != $atts['display_content'] // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
659 && 'full' !== $atts['display_content']
660 ) {
661 $atts['display_content'] = false;
662 }
663
664 if ( $atts['image'] && 'true' != $atts['image'] ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
665 $atts['image'] = false;
666 }
667
668 $atts['columns'] = absint( $atts['columns'] );
669
670 $atts['showposts'] = (int) $atts['showposts'];
671
672 if ( $atts['order'] ) {
673 $atts['order'] = urldecode( $atts['order'] );
674 $atts['order'] = strtoupper( $atts['order'] );
675 if ( 'DESC' !== $atts['order'] ) {
676 $atts['order'] = 'ASC';
677 }
678 }
679
680 if ( $atts['orderby'] ) {
681 $atts['orderby'] = urldecode( $atts['orderby'] );
682 $atts['orderby'] = strtolower( $atts['orderby'] );
683 $allowed_keys = array( 'author', 'date', 'title', 'menu_order', 'rand' );
684
685 $parsed = array();
686 foreach ( explode( ',', $atts['orderby'] ) as $orderby ) {
687 if ( ! in_array( $orderby, $allowed_keys, true ) ) {
688 continue;
689 }
690 $parsed[] = $orderby;
691 }
692
693 if ( empty( $parsed ) ) {
694 unset( $atts['orderby'] );
695 } else {
696 $atts['orderby'] = implode( ' ', $parsed );
697 }
698 }
699
700 // enqueue shortcode styles when shortcode is used
701 if ( ! wp_style_is( 'jetpack-testimonial-style', 'enqueued' ) ) {
702 wp_enqueue_style( 'jetpack-testimonial-style', plugins_url( 'css/testimonial-shortcode.css', __FILE__ ), array(), '20140326' );
703 }
704
705 return self::jetpack_testimonial_shortcode_html( $atts );
706 }
707
708 /**
709 * The Testimonial shortcode loop.
710 *
711 * @param array $atts Shortcode attributes.
712 *
713 * @return string html
714 */
715 private static function jetpack_testimonial_shortcode_html( $atts ) {
716 // Default query arguments
717 $defaults = array(
718 'order' => $atts['order'],
719 'orderby' => $atts['orderby'],
720 'posts_per_page' => $atts['showposts'],
721 );
722
723 $args = wp_parse_args( $atts, $defaults );
724 $args['post_type'] = self::CUSTOM_POST_TYPE; // Force this post type
725 $query = new WP_Query( $args );
726
727 $testimonial_index_number = 0;
728
729 ob_start();
730
731 // If we have testimonials, create the html
732 if ( $query->have_posts() ) {
733
734 ?>
735 <div class="jetpack-testimonial-shortcode column-<?php echo esc_attr( $atts['columns'] ); ?>">
736 <?php
737 // Construct the loop...
738 while ( $query->have_posts() ) {
739 $query->the_post();
740 $post_id = get_the_ID();
741 ?>
742 <div class="testimonial-entry <?php echo esc_attr( self::get_testimonial_class( $testimonial_index_number, $atts['columns'], has_post_thumbnail( $post_id ) ) ); ?>">
743 <?php
744 // The content
745 if ( false !== $atts['display_content'] ) {
746 if ( 'full' === $atts['display_content'] ) {
747 ?>
748 <div class="testimonial-entry-content"><?php the_content(); ?></div>
749 <?php
750 } else {
751 ?>
752 <div class="testimonial-entry-content"><?php the_excerpt(); ?></div>
753 <?php
754 }
755 }
756 ?>
757 <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>
758 <?php
759 // Featured image
760 if ( false !== $atts['image'] ) :
761 echo self::get_testimonial_thumbnail_link( $post_id ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in method.
762 endif;
763 ?>
764 </div><!-- close .testimonial-entry -->
765 <?php
766 ++$testimonial_index_number;
767 } // end of while loop
768
769 wp_reset_postdata();
770 ?>
771 </div><!-- close .jetpack-testimonial-shortcode -->
772 <?php
773 } else {
774 ?>
775 <p><em><?php esc_html_e( 'Your Testimonial Archive currently has no entries. You can start creating them on your dashboard.', 'jetpack' ); ?></p></em>
776 <?php
777 }
778 $html = ob_get_clean();
779
780 // Return the HTML block
781 return $html;
782 }
783
784 /**
785 * Individual testimonial class
786 *
787 * @param int $testimonial_index_number iterator count the number of columns up starting from 0.
788 * @param int $columns number of columns to display the content in.
789 * @param bool $image has a thumbnail or not.
790 *
791 * @return string
792 */
793 private static function get_testimonial_class( $testimonial_index_number, $columns, $image ) {
794 $class = array();
795
796 $class[] = 'testimonial-entry-column-' . $columns;
797
798 if ( $columns > 1 ) {
799 if ( ( $testimonial_index_number % 2 ) === 0 ) {
800 $class[] = 'testimonial-entry-mobile-first-item-row';
801 } else {
802 $class[] = 'testimonial-entry-mobile-last-item-row';
803 }
804 }
805
806 // add first and last classes to first and last items in a row
807 if ( ( $testimonial_index_number % $columns ) === 0 ) {
808 $class[] = 'testimonial-entry-first-item-row';
809 } elseif ( ( $testimonial_index_number % $columns ) === ( $columns - 1 ) ) {
810 $class[] = 'testimonial-entry-last-item-row';
811 }
812
813 // add class if testimonial has a featured image
814 if ( false !== $image ) {
815 $class[] = 'has-testimonial-thumbnail';
816 }
817
818 /**
819 * Filter the class applied to testimonial div in the testimonial
820 *
821 * @module custom-content-types
822 *
823 * @since 3.4.0
824 *
825 * @param string $class class name of the div.
826 * @param int $testimonial_index_number iterator count the number of columns up starting from 0.
827 * @param int $columns number of columns to display the content in.
828 * @param boolean $image has a thumbnail or not.
829 */
830 return apply_filters( 'testimonial-entry-post-class', implode( ' ', $class ), $testimonial_index_number, $columns, $image ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
831 }
832
833 /**
834 * Display the featured image if it's available
835 *
836 * @param int $post_id Post ID.
837 *
838 * @return string html
839 */
840 private static function get_testimonial_thumbnail_link( $post_id ) {
841 if ( has_post_thumbnail( $post_id ) ) {
842 /**
843 * Change the thumbnail size for the Testimonial CPT.
844 *
845 * @module custom-content-types
846 *
847 * @since 3.4.0
848 *
849 * @param string|array $var Either a registered size keyword or size array.
850 */
851 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>';
852 }
853 }
854 }
855
856 /**
857 * Additional Testimonial customizer options.
858 */
859 function jetpack_testimonial_custom_control_classes() {
860 /**
861 * Clean the title parameter.
862 */
863 class Jetpack_Testimonial_Title_Control extends WP_Customize_Control {
864 /**
865 * Sanitize content passed to control.
866 *
867 * @param string $value Control value.
868 * @return string Sanitized value.
869 */
870 public static function sanitize_content( $value ) {
871 if ( '' != $value ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
872 $value = trim( convert_chars( wptexturize( $value ) ) );
873 }
874
875 return $value;
876 }
877 }
878
879 /**
880 * Clean textarea content.
881 */
882 class Jetpack_Testimonial_Textarea_Control extends WP_Customize_Control {
883 /**
884 * Control type.
885 *
886 * @var string
887 */
888 public $type = 'textarea';
889
890 /**
891 * Render the control's content.
892 */
893 public function render_content() {
894 ?>
895 <label>
896 <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
897 <textarea rows="5" style="width:100%;" <?php $this->link(); ?>><?php echo esc_textarea( $this->value() ); ?></textarea>
898 </label>
899 <?php
900 }
901
902 /**
903 * Sanitize content passed to control.
904 *
905 * @param string $value Control value.
906 * @return string Sanitized value.
907 */
908 public static function sanitize_content( $value ) {
909 if ( ! empty( $value ) ) {
910 /** This filter is already documented in core. wp-includes/post-template.php */
911 $value = apply_filters( 'the_content', $value );
912 }
913
914 $value = preg_replace( '@<div id="jp-post-flair"([^>]+)?>(.+)?</div>@is', '', $value ); // Strip WPCOM and Jetpack post flair if included in content
915
916 return $value;
917 }
918 }
919 }
920
921 add_action( 'init', array( 'Jetpack_Testimonial', 'init' ) );
922
923 // Check on plugin activation if theme supports CPT
924 register_activation_hook( __FILE__, array( 'Jetpack_Testimonial', 'activation_post_type_support' ) );
925 add_action( 'jetpack_activate_module_custom-content-types', array( 'Jetpack_Testimonial', 'activation_post_type_support' ) );
926
927 }
928