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