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