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