PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.5.2
Jetpack – WP Security, Backup, Speed, & Growth v13.5.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 / portfolios.php
jetpack / modules / custom-post-types Last commit date
comics 2 years ago css 5 years ago js 5 years ago comics.php 2 years ago nova.php 2 years ago portfolios.php 2 years ago testimonial.php 2 years ago
portfolios.php
1121 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Register a portfolio post type and handle displaying it anywhere on the site.
4 *
5 * @package automattic/jetpack
6 */
7
8 /**
9 * Jetpack Portfolio.
10 */
11 class Jetpack_Portfolio {
12 const CUSTOM_POST_TYPE = 'jetpack-portfolio';
13 const CUSTOM_TAXONOMY_TYPE = 'jetpack-portfolio-type';
14 const CUSTOM_TAXONOMY_TAG = 'jetpack-portfolio-tag';
15 const OPTION_NAME = 'jetpack_portfolio';
16 const OPTION_READING_SETTING = 'jetpack_portfolio_posts_per_page';
17
18 /**
19 * Initialize class.
20 */
21 public static function init() {
22 static $instance = false;
23
24 if ( ! $instance ) {
25 $instance = new Jetpack_Portfolio();
26 }
27
28 return $instance;
29 }
30
31 /**
32 * Conditionally hook into WordPress.
33 *
34 * Setup user option for enabling CPT
35 * If user has CPT enabled, show in admin
36 */
37 public function __construct() {
38 // Add an option to enable the CPT.
39 add_action( 'admin_init', array( $this, 'settings_api_init' ) );
40
41 // Check on theme switch if theme supports CPT and setting is disabled.
42 add_action( 'after_switch_theme', array( $this, 'activation_post_type_support' ) );
43
44 // Make sure the post types are loaded for imports.
45 add_action( 'import_start', array( $this, 'register_post_types' ) );
46
47 // Add to REST API post type allowed list.
48 add_filter( 'rest_api_allowed_post_types', array( $this, 'allow_portfolio_rest_api_type' ) );
49
50 // If called via REST API, we need to register later in lifecycle.
51 add_action( 'restapi_theme_init', array( $this, 'maybe_register_cpt' ) );
52
53 $this->maybe_register_cpt();
54 }
55
56 /**
57 * Registers the custom post types and adds action/filter handlers, but
58 * only if the site supports it
59 */
60 public function maybe_register_cpt() {
61 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
62 $setting = get_option( self::OPTION_NAME, '0' );
63 } else {
64 $setting = Jetpack_Options::get_option_and_ensure_autoload( self::OPTION_NAME, '0' );
65 }
66
67 // Bail early if Portfolio 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 // CPT magic.
73 $this->register_post_types();
74 add_action( sprintf( 'add_option_%s', self::OPTION_NAME ), array( $this, 'flush_rules_on_enable' ), 10 );
75 add_action( sprintf( 'update_option_%s', self::OPTION_NAME ), array( $this, 'flush_rules_on_enable' ), 10 );
76 add_action( sprintf( 'publish_%s', self::CUSTOM_POST_TYPE ), array( $this, 'flush_rules_on_first_project' ) );
77 add_action( 'after_switch_theme', array( $this, 'flush_rules_on_switch' ) );
78
79 // Admin Customization.
80 add_filter( 'post_updated_messages', array( $this, 'updated_messages' ) );
81 add_filter( sprintf( 'manage_%s_posts_columns', self::CUSTOM_POST_TYPE ), array( $this, 'edit_admin_columns' ) );
82 add_filter( sprintf( 'manage_%s_posts_custom_column', self::CUSTOM_POST_TYPE ), array( $this, 'image_column' ), 10, 2 );
83 if ( ! wp_is_block_theme() ) {
84 add_action( 'customize_register', array( $this, 'customize_register' ) );
85 }
86
87 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
88
89 // Track all the things.
90 add_action( sprintf( 'add_option_%s', self::OPTION_NAME ), array( $this, 'new_activation_stat_bump' ) );
91 add_action( sprintf( 'update_option_%s', self::OPTION_NAME ), array( $this, 'update_option_stat_bump' ), 11, 2 );
92 add_action( sprintf( 'publish_%s', self::CUSTOM_POST_TYPE ), array( $this, 'new_project_stat_bump' ) );
93 }
94
95 add_image_size( 'jetpack-portfolio-admin-thumb', 50, 50, true );
96 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
97
98 // register jetpack_portfolio shortcode and portfolio shortcode (legacy).
99 add_shortcode( 'portfolio', array( $this, 'portfolio_shortcode' ) );
100 add_shortcode( 'jetpack_portfolio', array( $this, 'portfolio_shortcode' ) );
101
102 // Adjust CPT archive and custom taxonomies to obey CPT reading setting.
103 add_filter( 'infinite_scroll_settings', array( $this, 'infinite_scroll_click_posts_per_page' ) );
104 add_filter( 'infinite_scroll_results', array( $this, 'infinite_scroll_results' ), 10, 3 );
105
106 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
107 // Add to Dotcom XML sitemaps.
108 add_filter( 'wpcom_sitemap_post_types', array( $this, 'add_to_sitemap' ) );
109 } else {
110 // Add to Jetpack XML sitemap.
111 add_filter( 'jetpack_sitemap_post_types', array( $this, 'add_to_sitemap' ) );
112 }
113
114 // Adjust CPT archive and custom taxonomies to obey CPT reading setting.
115 add_filter( 'pre_get_posts', array( $this, 'query_reading_setting' ) );
116
117 // If CPT was enabled programatically and no CPT items exist when user switches away, disable.
118 if ( $setting && $this->site_supports_custom_post_type() ) {
119 add_action( 'switch_theme', array( $this, 'deactivation_post_type_support' ) );
120 }
121 }
122
123 /**
124 * Add a checkbox field in 'Settings' > 'Writing'
125 * for enabling CPT functionality.
126 *
127 * @return void
128 */
129 public function settings_api_init() {
130 add_settings_field(
131 self::OPTION_NAME,
132 '<span class="cpt-options">' . __( 'Portfolio Projects', 'jetpack' ) . '</span>',
133 array( $this, 'setting_html' ),
134 'writing',
135 'jetpack_cpt_section'
136 );
137 register_setting(
138 'writing',
139 self::OPTION_NAME,
140 'intval'
141 );
142
143 // Check if CPT is enabled first so that intval doesn't get set to NULL on re-registering.
144 if ( get_option( self::OPTION_NAME, '0' ) || current_theme_supports( self::CUSTOM_POST_TYPE ) ) {
145 register_setting(
146 'writing',
147 self::OPTION_READING_SETTING,
148 'intval'
149 );
150 }
151 }
152
153 /**
154 * HTML code to display a checkbox true/false option
155 * for the Portfolio CPT setting.
156 *
157 * @return void
158 */
159 public function setting_html() {
160 if ( current_theme_supports( self::CUSTOM_POST_TYPE ) ) : ?>
161 <p>
162 <?php
163 echo wp_kses(
164 sprintf(
165 /* translators: %s is the name of a custom post type such as "jetpack-portfolio" */
166 __( 'Your theme supports <strong>%s</strong>', 'jetpack' ),
167 esc_attr( self::CUSTOM_POST_TYPE )
168 ),
169 array(
170 'strong' => array(),
171 )
172 );
173 ?>
174 </p>
175 <?php else : ?>
176 <label for="<?php echo esc_attr( self::OPTION_NAME ); ?>">
177 <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" />
178 <?php esc_html_e( 'Enable Portfolio Projects for this site.', 'jetpack' ); ?>
179 <a target="_blank" href="https://en.support.wordpress.com/portfolios/"><?php esc_html_e( 'Learn More', 'jetpack' ); ?></a>
180 </label>
181 <?php
182 endif;
183 if ( get_option( self::OPTION_NAME, '0' ) || current_theme_supports( self::CUSTOM_POST_TYPE ) ) :
184 printf(
185 '<p><label for="%1$s">%2$s</label></p>',
186 esc_attr( self::OPTION_READING_SETTING ),
187 sprintf(
188 /* translators: %1$s is replaced with an input field for numbers */
189 __( 'Portfolio pages display at most %1$s projects', 'jetpack' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- the placeholder contains HTML.
190 sprintf(
191 '<input name="%1$s" id="%1$s" type="number" step="1" min="1" value="%2$s" class="small-text" />',
192 esc_attr( self::OPTION_READING_SETTING ),
193 esc_attr( get_option( self::OPTION_READING_SETTING, '10' ) )
194 )
195 )
196 );
197 endif;
198 }
199
200 /**
201 * Bump Portfolio > New Activation stat.
202 */
203 public function new_activation_stat_bump() {
204 bump_stats_extras( 'portfolios', 'new-activation' );
205 }
206
207 /**
208 * Bump Portfolio > Option On/Off stats to get total active.
209 *
210 * @param mixed $old The old option value.
211 * @param mixed $new The new option value.
212 */
213 public function update_option_stat_bump( $old, $new ) {
214 if ( empty( $old ) && ! empty( $new ) ) {
215 bump_stats_extras( 'portfolios', 'option-on' );
216 }
217
218 if ( ! empty( $old ) && empty( $new ) ) {
219 bump_stats_extras( 'portfolios', 'option-off' );
220 }
221 }
222
223 /**
224 * Bump Portfolio > Published Projects stat when projects are published.
225 */
226 public function new_project_stat_bump() {
227 bump_stats_extras( 'portfolios', 'published-projects' );
228 }
229
230 /**
231 * Should this Custom Post Type be made available?
232 */
233 private function site_supports_custom_post_type() {
234 // If the current theme requests it.
235 if ( current_theme_supports( self::CUSTOM_POST_TYPE ) || get_option( self::OPTION_NAME, '0' ) ) {
236 return true;
237 }
238
239 // Otherwise, say no unless something wants to filter us to say yes.
240 /** This action is documented in modules/custom-post-types/nova.php */
241 return (bool) apply_filters( 'jetpack_enable_cpt', false, self::CUSTOM_POST_TYPE );
242 }
243
244 /**
245 * Flush permalinks when CPT option is turned on/off
246 */
247 public function flush_rules_on_enable() {
248 flush_rewrite_rules();
249 }
250
251 /**
252 * Count published projects and flush permalinks when first projects is published
253 */
254 public function flush_rules_on_first_project() {
255 $projects = get_transient( 'jetpack-portfolio-count-cache' );
256
257 if ( false === $projects ) {
258 flush_rewrite_rules();
259 $projects = (int) wp_count_posts( self::CUSTOM_POST_TYPE )->publish;
260
261 if ( ! empty( $projects ) ) {
262 set_transient( 'jetpack-portfolio-count-cache', $projects, HOUR_IN_SECONDS * 12 );
263 }
264 }
265 }
266
267 /**
268 * Flush permalinks when CPT supported theme is activated
269 */
270 public function flush_rules_on_switch() {
271 if ( current_theme_supports( self::CUSTOM_POST_TYPE ) ) {
272 flush_rewrite_rules();
273 }
274 }
275
276 /**
277 * On plugin/theme activation, check if current theme supports CPT
278 */
279 public static function activation_post_type_support() {
280 if ( current_theme_supports( self::CUSTOM_POST_TYPE ) ) {
281 update_option( self::OPTION_NAME, '1' );
282 }
283 }
284
285 /**
286 * On theme switch, check if CPT item exists and disable if not
287 */
288 public function deactivation_post_type_support() {
289 $portfolios = get_posts(
290 array(
291 'fields' => 'ids',
292 'posts_per_page' => 1,
293 'post_type' => self::CUSTOM_POST_TYPE,
294 'suppress_filters' => false,
295 )
296 );
297
298 if ( empty( $portfolios ) ) {
299 update_option( self::OPTION_NAME, '0' );
300 }
301 }
302
303 /**
304 * Register Post Type
305 */
306 public function register_post_types() {
307 if ( post_type_exists( self::CUSTOM_POST_TYPE ) ) {
308 return;
309 }
310
311 register_post_type(
312 self::CUSTOM_POST_TYPE,
313 array(
314 'labels' => array(
315 'name' => esc_html__( 'Projects', 'jetpack' ),
316 'singular_name' => esc_html__( 'Project', 'jetpack' ),
317 'menu_name' => esc_html__( 'Portfolio', 'jetpack' ),
318 'all_items' => esc_html__( 'All Projects', 'jetpack' ),
319 'add_new' => esc_html__( 'Add New', 'jetpack' ),
320 'add_new_item' => esc_html__( 'Add New Project', 'jetpack' ),
321 'edit_item' => esc_html__( 'Edit Project', 'jetpack' ),
322 'new_item' => esc_html__( 'New Project', 'jetpack' ),
323 'view_item' => esc_html__( 'View Project', 'jetpack' ),
324 'search_items' => esc_html__( 'Search Projects', 'jetpack' ),
325 'not_found' => esc_html__( 'No Projects found', 'jetpack' ),
326 'not_found_in_trash' => esc_html__( 'No Projects found in Trash', 'jetpack' ),
327 'filter_items_list' => esc_html__( 'Filter projects list', 'jetpack' ),
328 'items_list_navigation' => esc_html__( 'Project list navigation', 'jetpack' ),
329 'items_list' => esc_html__( 'Projects list', 'jetpack' ),
330 ),
331 'supports' => array(
332 'title',
333 'editor',
334 'thumbnail',
335 'author',
336 'post-formats',
337 'comments',
338 'publicize',
339 'wpcom-markdown',
340 'revisions',
341 'excerpt',
342 'custom-fields',
343 'newspack_blocks',
344 ),
345 'rewrite' => array(
346 'slug' => 'portfolio',
347 'with_front' => false,
348 'feeds' => true,
349 'pages' => true,
350 ),
351 'public' => true,
352 'show_ui' => true,
353 'menu_position' => 20, // below Pages.
354 'menu_icon' => 'dashicons-portfolio', // 3.8+ dashicon option.
355 'capability_type' => 'page',
356 'map_meta_cap' => true,
357 'taxonomies' => array( self::CUSTOM_TAXONOMY_TYPE, self::CUSTOM_TAXONOMY_TAG ),
358 'has_archive' => true,
359 'query_var' => 'portfolio',
360 'show_in_rest' => true,
361 )
362 );
363
364 register_taxonomy(
365 self::CUSTOM_TAXONOMY_TYPE,
366 self::CUSTOM_POST_TYPE,
367 array(
368 'hierarchical' => true,
369 'labels' => array(
370 'name' => esc_html__( 'Project Types', 'jetpack' ),
371 'singular_name' => esc_html__( 'Project Type', 'jetpack' ),
372 'menu_name' => esc_html__( 'Project Types', 'jetpack' ),
373 'all_items' => esc_html__( 'All Project Types', 'jetpack' ),
374 'edit_item' => esc_html__( 'Edit Project Type', 'jetpack' ),
375 'view_item' => esc_html__( 'View Project Type', 'jetpack' ),
376 'update_item' => esc_html__( 'Update Project Type', 'jetpack' ),
377 'add_new_item' => esc_html__( 'Add New Project Type', 'jetpack' ),
378 'new_item_name' => esc_html__( 'New Project Type Name', 'jetpack' ),
379 'parent_item' => esc_html__( 'Parent Project Type', 'jetpack' ),
380 'parent_item_colon' => esc_html__( 'Parent Project Type:', 'jetpack' ),
381 'search_items' => esc_html__( 'Search Project Types', 'jetpack' ),
382 'items_list_navigation' => esc_html__( 'Project type list navigation', 'jetpack' ),
383 'items_list' => esc_html__( 'Project type list', 'jetpack' ),
384 ),
385 'public' => true,
386 'show_ui' => true,
387 'show_in_nav_menus' => true,
388 'show_in_rest' => true,
389 'show_admin_column' => true,
390 'query_var' => true,
391 'rewrite' => array( 'slug' => 'project-type' ),
392 )
393 );
394
395 register_taxonomy(
396 self::CUSTOM_TAXONOMY_TAG,
397 self::CUSTOM_POST_TYPE,
398 array(
399 'hierarchical' => false,
400 'labels' => array(
401 'name' => esc_html__( 'Project Tags', 'jetpack' ),
402 'singular_name' => esc_html__( 'Project Tag', 'jetpack' ),
403 'menu_name' => esc_html__( 'Project Tags', 'jetpack' ),
404 'all_items' => esc_html__( 'All Project Tags', 'jetpack' ),
405 'edit_item' => esc_html__( 'Edit Project Tag', 'jetpack' ),
406 'view_item' => esc_html__( 'View Project Tag', 'jetpack' ),
407 'update_item' => esc_html__( 'Update Project Tag', 'jetpack' ),
408 'add_new_item' => esc_html__( 'Add New Project Tag', 'jetpack' ),
409 'new_item_name' => esc_html__( 'New Project Tag Name', 'jetpack' ),
410 'search_items' => esc_html__( 'Search Project Tags', 'jetpack' ),
411 'popular_items' => esc_html__( 'Popular Project Tags', 'jetpack' ),
412 'separate_items_with_commas' => esc_html__( 'Separate tags with commas', 'jetpack' ),
413 'add_or_remove_items' => esc_html__( 'Add or remove tags', 'jetpack' ),
414 'choose_from_most_used' => esc_html__( 'Choose from the most used tags', 'jetpack' ),
415 'not_found' => esc_html__( 'No tags found.', 'jetpack' ),
416 'items_list_navigation' => esc_html__( 'Project tag list navigation', 'jetpack' ),
417 'items_list' => esc_html__( 'Project tag list', 'jetpack' ),
418 ),
419 'public' => true,
420 'show_ui' => true,
421 'show_in_nav_menus' => true,
422 'show_in_rest' => true,
423 'show_admin_column' => true,
424 'query_var' => true,
425 'rewrite' => array( 'slug' => 'project-tag' ),
426 )
427 );
428
429 register_taxonomy_for_object_type( 'post_format', self::CUSTOM_POST_TYPE );
430 }
431
432 /**
433 * Update messages for the Portfolio admin.
434 *
435 * @param array $messages Existing post update 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 portfolio item's page. */
444 __( 'Project updated. <a href="%s">View item</a>', 'jetpack' ),
445 esc_url( get_permalink( $post->ID ) )
446 ),
447 2 => esc_html__( 'Custom field updated.', 'jetpack' ),
448 3 => esc_html__( 'Custom field deleted.', 'jetpack' ),
449 4 => esc_html__( 'Project updated.', 'jetpack' ),
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__( 'Project restored to revision from %s', 'jetpack' ),
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 portfolio item's page. */
459 __( 'Project published. <a href="%s">View project</a>', 'jetpack' ),
460 esc_url( get_permalink( $post->ID ) )
461 ),
462 7 => esc_html__( 'Project saved.', 'jetpack' ),
463 8 => sprintf(
464 /* Translators: link to portfolio item's page. */
465 __( 'Project submitted. <a target="_blank" href="%s">Preview project</a>', 'jetpack' ),
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 portfolio's item page. */
470 __( 'Project scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview project</a>', 'jetpack' ),
471 /* translators: Publish box date format, see https://php.net/date */
472 date_i18n( __( 'M j, Y @ G:i', 'jetpack' ), strtotime( $post->post_date ) ),
473 esc_url( get_permalink( $post->ID ) )
474 ),
475 10 => sprintf(
476 /* Translators: link to portfolio item's page. */
477 __( 'Project item draft updated. <a target="_blank" href="%s">Preview project</a>', 'jetpack' ),
478 esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) )
479 ),
480 );
481
482 return $messages;
483 }
484
485 /**
486 * Change ‘Title’ column label
487 * Add Featured Image column
488 *
489 * @param array $columns An array of column names.
490 */
491 public function edit_admin_columns( $columns ) {
492 // change 'Title' to 'Project'.
493 $columns['title'] = __( 'Project', 'jetpack' );
494 if ( current_theme_supports( 'post-thumbnails' ) ) {
495 // add featured image before 'Project'.
496 $columns = array_slice( $columns, 0, 1, true ) + array( 'thumbnail' => '' ) + array_slice( $columns, 1, null, true );
497 }
498
499 return $columns;
500 }
501
502 /**
503 * Add featured image to column
504 *
505 * @param string $column The name of the column to display.
506 * @param int $post_id The current post ID.
507 */
508 public function image_column( $column, $post_id ) {
509 if ( 'thumbnail' !== $column ) {
510 return;
511 }
512
513 echo get_the_post_thumbnail( $post_id, 'jetpack-portfolio-admin-thumb' );
514 }
515
516 /**
517 * Adjust image column width
518 *
519 * @param string $hook Page hook.
520 */
521 public function enqueue_admin_styles( $hook ) {
522 $screen = get_current_screen();
523
524 if (
525 'edit.php' === $hook
526 && self::CUSTOM_POST_TYPE === $screen->post_type
527 && current_theme_supports( 'post-thumbnails' )
528 ) {
529 wp_add_inline_style( 'wp-admin', '.manage-column.column-thumbnail { width: 50px; } @media screen and (max-width: 360px) { .column-thumbnail{ display:none; } }' );
530 }
531 }
532
533 /**
534 * Adds portfolio section to the Customizer.
535 *
536 * @param WP_Customize_Manager $wp_customize Customizer instance.
537 */
538 public function customize_register( $wp_customize ) {
539 $options = get_theme_support( self::CUSTOM_POST_TYPE );
540
541 if ( ( ! isset( $options[0]['title'] ) || true !== $options[0]['title'] ) && ( ! isset( $options[0]['content'] ) || true !== $options[0]['content'] ) && ( ! isset( $options[0]['featured-image'] ) || true !== $options[0]['featured-image'] ) ) {
542 return;
543 }
544
545 $wp_customize->add_section(
546 'jetpack_portfolio',
547 array(
548 'title' => esc_html__( 'Portfolio', 'jetpack' ),
549 'theme_supports' => self::CUSTOM_POST_TYPE,
550 'priority' => 130,
551 )
552 );
553
554 if ( isset( $options[0]['title'] ) && true === $options[0]['title'] ) {
555 $wp_customize->add_setting(
556 'jetpack_portfolio_title',
557 array(
558 'default' => esc_html__( 'Projects', 'jetpack' ),
559 'type' => 'option',
560 'sanitize_callback' => 'sanitize_text_field',
561 'sanitize_js_callback' => 'sanitize_text_field',
562 )
563 );
564
565 $wp_customize->add_control(
566 'jetpack_portfolio_title',
567 array(
568 'section' => 'jetpack_portfolio',
569 'label' => esc_html__( 'Portfolio Archive Title', 'jetpack' ),
570 'type' => 'text',
571 )
572 );
573 }
574
575 if ( isset( $options[0]['content'] ) && true === $options[0]['content'] ) {
576 $wp_customize->add_setting(
577 'jetpack_portfolio_content',
578 array(
579 'default' => '',
580 'type' => 'option',
581 'sanitize_callback' => 'wp_kses_post',
582 'sanitize_js_callback' => 'wp_kses_post',
583 )
584 );
585
586 $wp_customize->add_control(
587 'jetpack_portfolio_content',
588 array(
589 'section' => 'jetpack_portfolio',
590 'label' => esc_html__( 'Portfolio Archive Content', 'jetpack' ),
591 'type' => 'textarea',
592 )
593 );
594 }
595
596 if ( isset( $options[0]['featured-image'] ) && true === $options[0]['featured-image'] ) {
597 $wp_customize->add_setting(
598 'jetpack_portfolio_featured_image',
599 array(
600 'default' => '',
601 'type' => 'option',
602 'sanitize_callback' => 'attachment_url_to_postid',
603 'sanitize_js_callback' => 'attachment_url_to_postid',
604 'theme_supports' => 'post-thumbnails',
605 )
606 );
607
608 $wp_customize->add_control(
609 new WP_Customize_Image_Control(
610 $wp_customize,
611 'jetpack_portfolio_featured_image',
612 array(
613 'section' => 'jetpack_portfolio',
614 'label' => esc_html__( 'Portfolio Archive Featured Image', 'jetpack' ),
615 )
616 )
617 );
618 }
619 }
620
621 /**
622 * Follow CPT reading setting on CPT archive and taxonomy pages
623 *
624 * @param WP_Query $query A WP_Query instance.
625 */
626 public function query_reading_setting( $query ) {
627 if ( ( ! is_admin() || ( is_admin() && defined( 'DOING_AJAX' ) && DOING_AJAX ) )
628 && $query->is_main_query()
629 && ( $query->is_post_type_archive( self::CUSTOM_POST_TYPE )
630 || $query->is_tax( self::CUSTOM_TAXONOMY_TYPE )
631 || $query->is_tax( self::CUSTOM_TAXONOMY_TAG ) )
632 ) {
633 $query->set( 'posts_per_page', get_option( self::OPTION_READING_SETTING, '10' ) );
634 }
635 }
636
637 /**
638 * If Infinite Scroll is set to 'click', use our custom reading setting instead of core's `posts_per_page`.
639 *
640 * @param array $settings Array of Infinite Scroll settings.
641 */
642 public function infinite_scroll_click_posts_per_page( $settings ) {
643 global $wp_query;
644
645 if ( ( ! is_admin() || ( is_admin() && defined( 'DOING_AJAX' ) && DOING_AJAX ) )
646 && true === $settings['click_handle']
647 && ( $wp_query->is_post_type_archive( self::CUSTOM_POST_TYPE )
648 || $wp_query->is_tax( self::CUSTOM_TAXONOMY_TYPE )
649 || $wp_query->is_tax( self::CUSTOM_TAXONOMY_TAG ) )
650 ) {
651 $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
652 }
653
654 return $settings;
655 }
656
657 /**
658 * Filter the results of infinite scroll to make sure we get `lastbatch` right.
659 *
660 * @param array $results Array of Infinite Scroll results.
661 * @param array $query_args Array of main query arguments.
662 * @param WP_Query $query WP Query.
663 */
664 public function infinite_scroll_results( $results, $query_args, $query ) {
665 $results['lastbatch'] = $query_args['paged'] >= $query->max_num_pages;
666 return $results;
667 }
668
669 /**
670 * Add CPT to Dotcom sitemap
671 *
672 * @param array $post_types Array of post types included in sitemap.
673 */
674 public function add_to_sitemap( $post_types ) {
675 $post_types[] = self::CUSTOM_POST_TYPE;
676
677 return $post_types;
678 }
679
680 /**
681 * Add to REST API post type allowed list.
682 *
683 * @param array $post_types Array of post types to add to the allowed list. Default to `array( 'post', 'page', 'revision' )`.
684 */
685 public function allow_portfolio_rest_api_type( $post_types ) {
686 $post_types[] = self::CUSTOM_POST_TYPE;
687
688 return $post_types;
689 }
690
691 /**
692 * Our [portfolio] shortcode.
693 * Prints Portfolio data styled to look good on *any* theme.
694 *
695 * @param array $atts Shortcode attributes.
696 *
697 * @return string html
698 */
699 public static function portfolio_shortcode( $atts ) {
700 // Default attributes.
701 $atts = shortcode_atts(
702 array(
703 'display_types' => true,
704 'display_tags' => true,
705 'display_content' => true, // Can be false, true, or full.
706 'display_author' => false,
707 'show_filter' => false,
708 'include_type' => false,
709 'include_tag' => false,
710 'columns' => 2,
711 'showposts' => -1,
712 'order' => 'asc',
713 'orderby' => 'date',
714 ),
715 $atts,
716 'portfolio'
717 );
718
719 /*
720 * A little sanitization for our shortcode attributes aiming to use booleans.
721 * Attributes can be booleans (from the default values) or strings.
722 */
723 foreach ( $atts as $attribute_name => $attribute_value ) {
724 if ( preg_match( '#^(?:display_|show_)#i', $attribute_name ) ) {
725 // display_content is a special case.
726 if ( 'display_content' === $attribute_name && 'full' === $attribute_value ) {
727 $atts['display_content'] = 'full';
728 continue;
729 }
730
731 $atts[ $attribute_name ] = self::sanitize_boolean_attribute( $attribute_value );
732 }
733 }
734
735 if ( $atts['include_type'] ) {
736 $atts['include_type'] = explode( ',', str_replace( ' ', '', $atts['include_type'] ) );
737 }
738
739 if ( $atts['include_tag'] ) {
740 $atts['include_tag'] = explode( ',', str_replace( ' ', '', $atts['include_tag'] ) );
741 }
742
743 $atts['columns'] = absint( $atts['columns'] );
744
745 $atts['showposts'] = (int) $atts['showposts'];
746
747 if ( $atts['order'] ) {
748 $atts['order'] = urldecode( $atts['order'] );
749 $atts['order'] = strtoupper( $atts['order'] );
750 if ( 'DESC' !== $atts['order'] ) {
751 $atts['order'] = 'ASC';
752 }
753 }
754
755 if ( $atts['orderby'] ) {
756 $atts['orderby'] = urldecode( $atts['orderby'] );
757 $atts['orderby'] = strtolower( $atts['orderby'] );
758 $allowed_keys = array( 'author', 'date', 'title', 'rand' );
759
760 $parsed = array();
761 foreach ( explode( ',', $atts['orderby'] ) as $orderby ) {
762 if ( ! in_array( $orderby, $allowed_keys, true ) ) {
763 continue;
764 }
765 $parsed[] = $orderby;
766 }
767
768 if ( empty( $parsed ) ) {
769 unset( $atts['orderby'] );
770 } else {
771 $atts['orderby'] = implode( ' ', $parsed );
772 }
773 }
774
775 // enqueue shortcode styles when shortcode is used.
776 if ( ! wp_style_is( 'jetpack-portfolio-style', 'enqueued' ) ) {
777 wp_enqueue_style( 'jetpack-portfolio-style', plugins_url( 'css/portfolio-shortcode.css', __FILE__ ), array(), '20140326' );
778 }
779
780 return self::portfolio_shortcode_html( $atts );
781 }
782
783 /**
784 * Sanitizes an attribute value.
785 * Attributes can be booleans (from the default values) or strings.
786 *
787 * @since 11.0
788 *
789 * @param bool|string $attr Shortcode attribute value.
790 *
791 * @return bool
792 */
793 private static function sanitize_boolean_attribute( $attr ) {
794 if ( $attr && 'true' == $attr ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
795 return true;
796 }
797
798 return false;
799 }
800
801 /**
802 * Query to retrieve entries from the Portfolio post_type.
803 *
804 * @param array $atts Shortcode attributes.
805 *
806 * @return object
807 */
808 private static function portfolio_query( $atts ) {
809 // Default query arguments.
810 $default = array(
811 'order' => $atts['order'],
812 'orderby' => $atts['orderby'],
813 'posts_per_page' => $atts['showposts'],
814 );
815
816 $args = wp_parse_args( $atts, $default );
817 $args['post_type'] = self::CUSTOM_POST_TYPE; // Force this post type.
818
819 if ( $atts['include_type'] || $atts['include_tag'] ) {
820 $args['tax_query'] = array();
821 }
822
823 // If 'include_type' has been set use it on the main query.
824 if ( $atts['include_type'] ) {
825 array_push(
826 $args['tax_query'],
827 array(
828 'taxonomy' => self::CUSTOM_TAXONOMY_TYPE,
829 'field' => 'slug',
830 'terms' => $atts['include_type'],
831 )
832 );
833 }
834
835 // If 'include_tag' has been set use it on the main query.
836 if ( $atts['include_tag'] ) {
837 array_push(
838 $args['tax_query'],
839 array(
840 'taxonomy' => self::CUSTOM_TAXONOMY_TAG,
841 'field' => 'slug',
842 'terms' => $atts['include_tag'],
843 )
844 );
845 }
846
847 if ( $atts['include_type'] && $atts['include_tag'] ) {
848 $args['tax_query']['relation'] = 'AND';
849 }
850
851 // Run the query and return.
852 $query = new WP_Query( $args );
853 return $query;
854 }
855
856 /**
857 * The Portfolio shortcode loop.
858 *
859 * @todo add theme color styles
860 *
861 * @param array $atts Shortcode attributes.
862 *
863 * @return string html
864 */
865 private static function portfolio_shortcode_html( $atts ) {
866 $query = self::portfolio_query( $atts );
867 $portfolio_index_number = 0;
868
869 ob_start();
870
871 /*
872 * If we have posts, create the html
873 * with portfolio markup
874 */
875 if ( $query->have_posts() ) {
876 /*
877 * Render styles
878 * See self::themecolor_styles();
879 */
880 ?>
881 <div class="jetpack-portfolio-shortcode column-<?php echo esc_attr( $atts['columns'] ); ?>">
882 <?php
883 // open .jetpack-portfolio.
884
885 // Construct the loop...
886 while ( $query->have_posts() ) {
887 $query->the_post();
888 $post_id = get_the_ID();
889 ?>
890 <div class="portfolio-entry <?php echo esc_attr( self::get_project_class( $portfolio_index_number, (int) $atts['columns'] ) ); ?>">
891 <header class="portfolio-entry-header">
892 <?php
893 // Featured image.
894 echo self::get_portfolio_thumbnail_link( $post_id ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in method.
895 ?>
896
897 <h2 class="portfolio-entry-title"><a href="<?php echo esc_url( get_permalink() ); ?>" title="<?php echo esc_attr( the_title_attribute() ); ?>"><?php the_title(); ?></a></h2>
898
899 <div class="portfolio-entry-meta">
900 <?php
901 if ( $atts['display_types'] ) {
902 echo self::get_project_type( $post_id ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in method.
903 }
904
905 if ( $atts['display_tags'] ) {
906 echo self::get_project_tags( $post_id ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in method.
907 }
908
909 if ( $atts['display_author'] ) {
910 echo self::get_project_author(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in method.
911 }
912 ?>
913 </div>
914
915 </header>
916
917 <?php
918 // The content.
919 if ( $atts['display_content'] ) {
920 add_filter( 'wordads_inpost_disable', '__return_true', 20 );
921 if ( 'full' === $atts['display_content'] ) {
922 ?>
923 <div class="portfolio-entry-content"><?php the_content(); ?></div>
924 <?php
925 } else {
926 ?>
927 <div class="portfolio-entry-content"><?php the_excerpt(); ?></div>
928 <?php
929 }
930 remove_filter( 'wordads_inpost_disable', '__return_true', 20 );
931 }
932 ?>
933 </div><!-- close .portfolio-entry -->
934 <?php
935 ++$portfolio_index_number;
936 } // end of while loop.
937
938 wp_reset_postdata();
939 ?>
940 </div><!-- close .jetpack-portfolio -->
941 <?php
942 } else {
943 ?>
944 <p><em><?php esc_html_e( 'Your Portfolio Archive currently has no entries. You can start creating them on your dashboard.', 'jetpack' ); ?></p></em>
945 <?php
946 }
947 $html = ob_get_clean();
948
949 // If there is a [portfolio] within a [portfolio], remove the shortcode.
950 if ( has_shortcode( $html, 'portfolio' ) ) {
951 remove_shortcode( 'portfolio' );
952 }
953
954 // Return the HTML block.
955 return $html;
956 }
957
958 /**
959 * Individual project class
960 *
961 * @param int $portfolio_index_number Index number when looping through Portfolio items.
962 * @param int $columns Number of columns in shortcode output.
963 * @return string
964 */
965 private static function get_project_class( $portfolio_index_number, $columns ) {
966 $columns = is_numeric( $columns ) ? max( 1, $columns ) : 1;
967 $project_types = wp_get_object_terms( get_the_ID(), self::CUSTOM_TAXONOMY_TYPE, array( 'fields' => 'slugs' ) );
968 $class = array();
969
970 $class[] = 'portfolio-entry-column-' . $columns;
971 // add a type- class for each project type.
972 foreach ( $project_types as $project_type ) {
973 $class[] = 'type-' . esc_html( $project_type );
974 }
975 if ( $columns > 1 ) {
976 if ( ( $portfolio_index_number % 2 ) === 0 ) {
977 $class[] = 'portfolio-entry-mobile-first-item-row';
978 } else {
979 $class[] = 'portfolio-entry-mobile-last-item-row';
980 }
981 }
982
983 // add first and last classes to first and last items in a row.
984 if ( ( $portfolio_index_number % $columns ) === 0 ) {
985 $class[] = 'portfolio-entry-first-item-row';
986 } elseif ( ( $portfolio_index_number % $columns ) === ( $columns - 1 ) ) {
987 $class[] = 'portfolio-entry-last-item-row';
988 }
989
990 /**
991 * Filter the class applied to project div in the portfolio
992 *
993 * @module custom-content-types
994 *
995 * @since 3.1.0
996 *
997 * @param string $class class name of the div.
998 * @param int $portfolio_index_number iterator count the number of columns up starting from 0.
999 * @param int $columns number of columns to display the content in.
1000 */
1001 return apply_filters(
1002 'portfolio-project-post-class', // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
1003 implode( ' ', $class ),
1004 $portfolio_index_number,
1005 $columns
1006 );
1007 }
1008
1009 /**
1010 * Displays the project type that a project belongs to.
1011 *
1012 * @param int $post_id Post ID.
1013 *
1014 * @return string html
1015 */
1016 private static function get_project_type( $post_id ) {
1017 $project_types = get_the_terms( $post_id, self::CUSTOM_TAXONOMY_TYPE );
1018
1019 // If no types, return empty string.
1020 if ( empty( $project_types ) || is_wp_error( $project_types ) ) {
1021 return;
1022 }
1023
1024 $html = '<div class="project-types"><span>' . __( 'Types:', 'jetpack' ) . '</span>';
1025 $types = array();
1026 // Loop through all the types.
1027 foreach ( $project_types as $project_type ) {
1028 $project_type_link = get_term_link( $project_type, self::CUSTOM_TAXONOMY_TYPE );
1029
1030 if ( is_wp_error( $project_type_link ) ) {
1031 return $project_type_link;
1032 }
1033
1034 $types[] = '<a href="' . esc_url( $project_type_link ) . '" rel="tag">' . esc_html( $project_type->name ) . '</a>';
1035 }
1036 $html .= ' ' . implode( ', ', $types );
1037 $html .= '</div>';
1038
1039 return $html;
1040 }
1041
1042 /**
1043 * Displays the project tags that a project belongs to.
1044 *
1045 * @param int $post_id Post ID.
1046 *
1047 * @return string html
1048 */
1049 private static function get_project_tags( $post_id ) {
1050 $project_tags = get_the_terms( $post_id, self::CUSTOM_TAXONOMY_TAG );
1051
1052 // If no tags, return empty string.
1053 if ( empty( $project_tags ) || is_wp_error( $project_tags ) ) {
1054 return false;
1055 }
1056
1057 $html = '<div class="project-tags"><span>' . __( 'Tags:', 'jetpack' ) . '</span>';
1058 $tags = array();
1059 // Loop through all the tags.
1060 foreach ( $project_tags as $project_tag ) {
1061 $project_tag_link = get_term_link( $project_tag, self::CUSTOM_TAXONOMY_TYPE );
1062
1063 if ( is_wp_error( $project_tag_link ) ) {
1064 return $project_tag_link;
1065 }
1066
1067 $tags[] = '<a href="' . esc_url( $project_tag_link ) . '" rel="tag">' . esc_html( $project_tag->name ) . '</a>';
1068 }
1069 $html .= ' ' . implode( ', ', $tags );
1070 $html .= '</div>';
1071
1072 return $html;
1073 }
1074
1075 /**
1076 * Displays the author of the current portfolio project.
1077 *
1078 * @return string html
1079 */
1080 private static function get_project_author() {
1081 $html = '<div class="project-author">';
1082 $html .= sprintf(
1083 /* translators: %1$s is link to author posts, %2$s is author display name */
1084 __( '<span>Author:</span> <a href="%1$s">%2$s</a>', 'jetpack' ),
1085 esc_url( get_author_posts_url( get_the_author_meta( 'ID' ) ) ),
1086 esc_html( get_the_author() )
1087 );
1088 $html .= '</div>';
1089
1090 return $html;
1091 }
1092
1093 /**
1094 * Display the featured image if it's available
1095 *
1096 * @param int $post_id Post ID.
1097 *
1098 * @return string html
1099 */
1100 private static function get_portfolio_thumbnail_link( $post_id ) {
1101 if ( has_post_thumbnail( $post_id ) ) {
1102 /**
1103 * Change the Portfolio thumbnail size.
1104 *
1105 * @module custom-content-types
1106 *
1107 * @since 3.4.0
1108 *
1109 * @param string|array $var Either a registered size keyword or size array.
1110 */
1111 return '<a class="portfolio-featured-image" href="' . esc_url( get_permalink( $post_id ) ) . '">' . get_the_post_thumbnail( $post_id, apply_filters( 'jetpack_portfolio_thumbnail_size', 'large' ) ) . '</a>';
1112 }
1113 }
1114 }
1115
1116 add_action( 'init', array( 'Jetpack_Portfolio', 'init' ) );
1117
1118 // Check on plugin activation if theme supports CPT.
1119 register_activation_hook( __FILE__, array( 'Jetpack_Portfolio', 'activation_post_type_support' ) );
1120 add_action( 'jetpack_activate_module_custom-content-types', array( 'Jetpack_Portfolio', 'activation_post_type_support' ) );
1121