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