Jetpack_Portfolio

The Jetpack by WordPress.com Jetpack Portfolio class.

Defined (1)

The class is defined in the following location(s).

/modules/custom-post-types/portfolios.php  
  1. class Jetpack_Portfolio { 
  2. const CUSTOM_POST_TYPE = 'jetpack-portfolio'; 
  3. const CUSTOM_TAXONOMY_TYPE = 'jetpack-portfolio-type'; 
  4. const CUSTOM_TAXONOMY_TAG = 'jetpack-portfolio-tag'; 
  5. const OPTION_NAME = 'jetpack_portfolio'; 
  6. const OPTION_READING_SETTING = 'jetpack_portfolio_posts_per_page'; 
  7.  
  8. public $version = '0.1'; 
  9.  
  10. static function init() { 
  11. static $instance = false; 
  12.  
  13. if ( ! $instance ) { 
  14. $instance = new Jetpack_Portfolio; 
  15.  
  16. return $instance; 
  17.  
  18. /** 
  19. * Conditionally hook into WordPress. 
  20. * Setup user option for enabling CPT 
  21. * If user has CPT enabled, show in admin 
  22. */ 
  23. function __construct() { 
  24. // Add an option to enable the CPT 
  25. add_action( 'admin_init', array( $this, 'settings_api_init' ) ); 
  26.  
  27. // Check on theme switch if theme supports CPT and setting is disabled 
  28. add_action( 'after_switch_theme', array( $this, 'activation_post_type_support' ) ); 
  29.  
  30. // Make sure the post types are loaded for imports 
  31. add_action( 'import_start', array( $this, 'register_post_types' ) ); 
  32.  
  33. $setting = get_option( self::OPTION_NAME, '0' ); 
  34.  
  35. // Bail early if Portfolio option is not set and the theme doesn't declare support 
  36. if ( empty( $setting ) && ! $this->site_supports_custom_post_type() ) { 
  37. return; 
  38.  
  39. // Enable Omnisearch for Portfolio Items. 
  40. if ( class_exists( 'Jetpack_Omnisearch_Posts' ) ) 
  41. new Jetpack_Omnisearch_Posts( self::CUSTOM_POST_TYPE ); 
  42.  
  43. // CPT magic 
  44. $this->register_post_types(); 
  45. add_action( sprintf( 'add_option_%s', self::OPTION_NAME ), array( $this, 'flush_rules_on_enable' ), 10 ); 
  46. add_action( sprintf( 'update_option_%s', self::OPTION_NAME ), array( $this, 'flush_rules_on_enable' ), 10 ); 
  47. add_action( sprintf( 'publish_%s', self::CUSTOM_POST_TYPE), array( $this, 'flush_rules_on_first_project' ) ); 
  48. add_action( 'after_switch_theme', array( $this, 'flush_rules_on_switch' ) ); 
  49.  
  50. // Admin Customization 
  51. add_filter( 'post_updated_messages', array( $this, 'updated_messages' ) ); 
  52. add_filter( sprintf( 'manage_%s_posts_columns', self::CUSTOM_POST_TYPE), array( $this, 'edit_admin_columns' ) ); 
  53. add_filter( sprintf( 'manage_%s_posts_custom_column', self::CUSTOM_POST_TYPE), array( $this, 'image_column' ), 10, 2 ); 
  54.  
  55. add_image_size( 'jetpack-portfolio-admin-thumb', 50, 50, true ); 
  56. add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) ); 
  57.  
  58. // register jetpack_portfolio shortcode and portfolio shortcode (legacy) 
  59. add_shortcode( 'portfolio', array( $this, 'portfolio_shortcode' ) ); 
  60. add_shortcode( 'jetpack_portfolio', array( $this, 'portfolio_shortcode' ) ); 
  61.  
  62. // Adjust CPT archive and custom taxonomies to obey CPT reading setting 
  63. add_filter( 'pre_get_posts', array( $this, 'query_reading_setting' ) ); 
  64.  
  65. // If CPT was enabled programatically and no CPT items exist when user switches away, disable 
  66. if ( $setting && $this->site_supports_custom_post_type() ) { 
  67. add_action( 'switch_theme', array( $this, 'deactivation_post_type_support' ) ); 
  68.  
  69. /** 
  70. * Add a checkbox field in 'Settings' > 'Writing' 
  71. * for enabling CPT functionality. 
  72. * @return null 
  73. */ 
  74. function settings_api_init() { 
  75. add_settings_field( 
  76. self::OPTION_NAME,  
  77. '<span class="cpt-options">' . __( 'Portfolio Projects', 'jetpack' ) . '</span>',  
  78. array( $this, 'setting_html' ),  
  79. 'writing',  
  80. 'jetpack_cpt_section' 
  81. ); 
  82. register_setting( 
  83. 'writing',  
  84. self::OPTION_NAME,  
  85. 'intval' 
  86. ); 
  87.  
  88. // Check if CPT is enabled first so that intval doesn't get set to NULL on re-registering 
  89. if ( get_option( self::OPTION_NAME, '0' ) || current_theme_supports( self::CUSTOM_POST_TYPE ) ) { 
  90. register_setting( 
  91. 'writing',  
  92. self::OPTION_READING_SETTING,  
  93. 'intval' 
  94. ); 
  95.  
  96. /** 
  97. * HTML code to display a checkbox true/false option 
  98. * for the Portfolio CPT setting. 
  99. * @return html 
  100. */ 
  101. function setting_html() { 
  102. if ( current_theme_supports( self::CUSTOM_POST_TYPE ) ) : ?> 
  103. <p><?php printf( __( 'Your theme supports <strong>%s</strong>', 'jetpack' ), self::CUSTOM_POST_TYPE ); ?></p> 
  104. <?php else : ?> 
  105. <label for="<?php echo esc_attr( self::OPTION_NAME ); ?>"> 
  106. <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" /> 
  107. <?php esc_html_e( 'Enable Portfolio Projects for this site.', 'jetpack' ); ?> 
  108. <a target="_blank" href="http://en.support.wordpress.com/portfolios/"><?php esc_html_e( 'Learn More', 'jetpack' ); ?></a> 
  109. </label> 
  110. <?php endif; 
  111. if ( get_option( self::OPTION_NAME, '0' ) || current_theme_supports( self::CUSTOM_POST_TYPE ) ) : 
  112. printf( '<p><label for="%1$s">%2$s</label></p>',  
  113. esc_attr( self::OPTION_READING_SETTING ),  
  114. sprintf( __( 'Portfolio pages display at most %1$s projects', 'jetpack' ),  
  115. sprintf( '<input name="%1$s" id="%1$s" type="number" step="1" min="1" value="%2$s" class="small-text" />',  
  116. esc_attr( self::OPTION_READING_SETTING ),  
  117. esc_attr( get_option( self::OPTION_READING_SETTING, '10' ) ) 
  118. ); 
  119. endif; 
  120.  
  121. /** 
  122. * Should this Custom Post Type be made available? 
  123. */ 
  124. function site_supports_custom_post_type() { 
  125. // If the current theme requests it. 
  126. if ( current_theme_supports( self::CUSTOM_POST_TYPE ) || get_option( self::OPTION_NAME, '0' ) ) { 
  127. return true; 
  128.  
  129. // Otherwise, say no unless something wants to filter us to say yes. 
  130. /** This action is documented in modules/custom-post-types/nova.php */ 
  131. return (bool) apply_filters( 'jetpack_enable_cpt', false, self::CUSTOM_POST_TYPE ); 
  132.  
  133. /** 
  134. * Flush permalinks when CPT option is turned on/off 
  135. */ 
  136. function flush_rules_on_enable() { 
  137. flush_rewrite_rules(); 
  138.  
  139. /** 
  140. * Count published projects and flush permalinks when first projects is published 
  141. */ 
  142. function flush_rules_on_first_project() { 
  143. $projects = get_transient( 'jetpack-portfolio-count-cache' ); 
  144.  
  145. if ( false === $projects ) { 
  146. flush_rewrite_rules(); 
  147. $projects = (int) wp_count_posts( self::CUSTOM_POST_TYPE )->publish; 
  148.  
  149. if ( ! empty( $projects ) ) { 
  150. set_transient( 'jetpack-portfolio-count-cache', $projects, HOUR_IN_SECONDS * 12 ); 
  151.  
  152. /** 
  153. * Flush permalinks when CPT supported theme is activated 
  154. */ 
  155. function flush_rules_on_switch() { 
  156. if ( current_theme_supports( self::CUSTOM_POST_TYPE ) ) { 
  157. flush_rewrite_rules(); 
  158.  
  159. /** 
  160. * On plugin/theme activation, check if current theme supports CPT 
  161. */ 
  162. static function activation_post_type_support() { 
  163. if ( current_theme_supports( self::CUSTOM_POST_TYPE ) ) { 
  164. update_option( self::OPTION_NAME, '1' ); 
  165.  
  166. /** 
  167. * On theme switch, check if CPT item exists and disable if not 
  168. */ 
  169. function deactivation_post_type_support() { 
  170. $portfolios = get_posts( array( 
  171. 'fields' => 'ids',  
  172. 'posts_per_page' => 1,  
  173. 'post_type' => self::CUSTOM_POST_TYPE,  
  174. 'suppress_filters' => false 
  175. ) ); 
  176.  
  177. if ( empty( $portfolios ) ) { 
  178. update_option( self::OPTION_NAME, '0' ); 
  179.  
  180. /** 
  181. * Register Post Type 
  182. */ 
  183. function register_post_types() { 
  184. if ( post_type_exists( self::CUSTOM_POST_TYPE ) ) { 
  185. return; 
  186.  
  187. register_post_type( self::CUSTOM_POST_TYPE, array( 
  188. 'description' => __( 'Portfolio Items', 'jetpack' ),  
  189. 'labels' => array( 
  190. 'name' => esc_html__( 'Projects', 'jetpack' ),  
  191. 'singular_name' => esc_html__( 'Project', 'jetpack' ),  
  192. 'menu_name' => esc_html__( 'Portfolio', 'jetpack' ),  
  193. 'all_items' => esc_html__( 'All Projects', 'jetpack' ),  
  194. 'add_new' => esc_html__( 'Add New', 'jetpack' ),  
  195. 'add_new_item' => esc_html__( 'Add New Project', 'jetpack' ),  
  196. 'edit_item' => esc_html__( 'Edit Project', 'jetpack' ),  
  197. 'new_item' => esc_html__( 'New Project', 'jetpack' ),  
  198. 'view_item' => esc_html__( 'View Project', 'jetpack' ),  
  199. 'search_items' => esc_html__( 'Search Projects', 'jetpack' ),  
  200. 'not_found' => esc_html__( 'No Projects found', 'jetpack' ),  
  201. 'not_found_in_trash' => esc_html__( 'No Projects found in Trash', 'jetpack' ),  
  202. 'filter_items_list' => esc_html__( 'Filter projects list', 'jetpack' ),  
  203. 'items_list_navigation' => esc_html__( 'Project list navigation', 'jetpack' ),  
  204. 'items_list' => esc_html__( 'Projects list', 'jetpack' ),  
  205. ),  
  206. 'supports' => array( 
  207. 'title',  
  208. 'editor',  
  209. 'thumbnail',  
  210. 'comments',  
  211. 'publicize',  
  212. 'wpcom-markdown',  
  213. ),  
  214. 'rewrite' => array( 
  215. 'slug' => 'portfolio',  
  216. 'with_front' => false,  
  217. 'feeds' => true,  
  218. 'pages' => true,  
  219. ),  
  220. 'public' => true,  
  221. 'show_ui' => true,  
  222. 'menu_position' => 20, // below Pages 
  223. 'menu_icon' => 'dashicons-portfolio', // 3.8+ dashicon option 
  224. 'capability_type' => 'page',  
  225. 'map_meta_cap' => true,  
  226. 'taxonomies' => array( self::CUSTOM_TAXONOMY_TYPE, self::CUSTOM_TAXONOMY_TAG ),  
  227. 'has_archive' => true,  
  228. 'query_var' => 'portfolio',  
  229. ) ); 
  230.  
  231. register_taxonomy( self::CUSTOM_TAXONOMY_TYPE, self::CUSTOM_POST_TYPE, array( 
  232. 'hierarchical' => true,  
  233. 'labels' => array( 
  234. 'name' => esc_html__( 'Project Types', 'jetpack' ),  
  235. 'singular_name' => esc_html__( 'Project Type', 'jetpack' ),  
  236. 'menu_name' => esc_html__( 'Project Types', 'jetpack' ),  
  237. 'all_items' => esc_html__( 'All Project Types', 'jetpack' ),  
  238. 'edit_item' => esc_html__( 'Edit Project Type', 'jetpack' ),  
  239. 'view_item' => esc_html__( 'View Project Type', 'jetpack' ),  
  240. 'update_item' => esc_html__( 'Update Project Type', 'jetpack' ),  
  241. 'add_new_item' => esc_html__( 'Add New Project Type', 'jetpack' ),  
  242. 'new_item_name' => esc_html__( 'New Project Type Name', 'jetpack' ),  
  243. 'parent_item' => esc_html__( 'Parent Project Type', 'jetpack' ),  
  244. 'parent_item_colon' => esc_html__( 'Parent Project Type:', 'jetpack' ),  
  245. 'search_items' => esc_html__( 'Search Project Types', 'jetpack' ),  
  246. 'items_list_navigation' => esc_html__( 'Project type list navigation', 'jetpack' ),  
  247. 'items_list' => esc_html__( 'Project type list', 'jetpack' ),  
  248. ),  
  249. 'public' => true,  
  250. 'show_ui' => true,  
  251. 'show_in_nav_menus' => true,  
  252. 'show_admin_column' => true,  
  253. 'query_var' => true,  
  254. 'rewrite' => array( 'slug' => 'project-type' ),  
  255. ) ); 
  256.  
  257. register_taxonomy( self::CUSTOM_TAXONOMY_TAG, self::CUSTOM_POST_TYPE, array( 
  258. 'hierarchical' => false,  
  259. 'labels' => array( 
  260. 'name' => esc_html__( 'Project Tags', 'jetpack' ),  
  261. 'singular_name' => esc_html__( 'Project Tag', 'jetpack' ),  
  262. 'menu_name' => esc_html__( 'Project Tags', 'jetpack' ),  
  263. 'all_items' => esc_html__( 'All Project Tags', 'jetpack' ),  
  264. 'edit_item' => esc_html__( 'Edit Project Tag', 'jetpack' ),  
  265. 'view_item' => esc_html__( 'View Project Tag', 'jetpack' ),  
  266. 'update_item' => esc_html__( 'Update Project Tag', 'jetpack' ),  
  267. 'add_new_item' => esc_html__( 'Add New Project Tag', 'jetpack' ),  
  268. 'new_item_name' => esc_html__( 'New Project Tag Name', 'jetpack' ),  
  269. 'search_items' => esc_html__( 'Search Project Tags', 'jetpack' ),  
  270. 'popular_items' => esc_html__( 'Popular Project Tags', 'jetpack' ),  
  271. 'separate_items_with_commas' => esc_html__( 'Separate tags with commas', 'jetpack' ),  
  272. 'add_or_remove_items' => esc_html__( 'Add or remove tags', 'jetpack' ),  
  273. 'choose_from_most_used' => esc_html__( 'Choose from the most used tags', 'jetpack' ),  
  274. 'not_found' => esc_html__( 'No tags found.', 'jetpack' ),  
  275. 'items_list_navigation' => esc_html__( 'Project tag list navigation', 'jetpack' ),  
  276. 'items_list' => esc_html__( 'Project tag list', 'jetpack' ),  
  277. ),  
  278. 'public' => true,  
  279. 'show_ui' => true,  
  280. 'show_in_nav_menus' => true,  
  281. 'show_admin_column' => true,  
  282. 'query_var' => true,  
  283. 'rewrite' => array( 'slug' => 'project-tag' ),  
  284. ) ); 
  285.  
  286. /** 
  287. * Update messages for the Portfolio admin. 
  288. */ 
  289. function updated_messages( $messages ) { 
  290. global $post; 
  291.  
  292. $messages[self::CUSTOM_POST_TYPE] = array( 
  293. 0 => '', // Unused. Messages start at index 1. 
  294. 1 => sprintf( __( 'Project updated. <a href="%s">View item</a>', 'jetpack'), esc_url( get_permalink( $post->ID ) ) ),  
  295. 2 => esc_html__( 'Custom field updated.', 'jetpack' ),  
  296. 3 => esc_html__( 'Custom field deleted.', 'jetpack' ),  
  297. 4 => esc_html__( 'Project updated.', 'jetpack' ),  
  298. /** translators: %s: date and time of the revision */ 
  299. 5 => isset( $_GET['revision'] ) ? sprintf( esc_html__( 'Project restored to revision from %s', 'jetpack'), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,  
  300. 6 => sprintf( __( 'Project published. <a href="%s">View project</a>', 'jetpack' ), esc_url( get_permalink( $post->ID ) ) ),  
  301. 7 => esc_html__( 'Project saved.', 'jetpack' ),  
  302. 8 => sprintf( __( 'Project submitted. <a target="_blank" href="%s">Preview project</a>', 'jetpack'), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) ),  
  303. 9 => sprintf( __( 'Project scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview project</a>', 'jetpack' ),  
  304. // translators: Publish box date format, see http://php.net/date 
  305. date_i18n( __( 'M j, Y @ G:i', 'jetpack' ), strtotime( $post->post_date ) ), esc_url( get_permalink( $post->ID ) ) ),  
  306. 10 => sprintf( __( 'Project item draft updated. <a target="_blank" href="%s">Preview project</a>', 'jetpack' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) ),  
  307. ); 
  308.  
  309. return $messages; 
  310.  
  311. /** 
  312. * Change *Title* column label 
  313. * Add Featured Image column 
  314. */ 
  315. function edit_admin_columns( $columns ) { 
  316. // change 'Title' to 'Project' 
  317. $columns['title'] = __( 'Project', 'jetpack' ); 
  318. if ( current_theme_supports( 'post-thumbnails' ) ) { 
  319. // add featured image before 'Project' 
  320. $columns = array_slice( $columns, 0, 1, true ) + array( 'thumbnail' => '' ) + array_slice( $columns, 1, NULL, true ); 
  321.  
  322. return $columns; 
  323.  
  324. /** 
  325. * Add featured image to column 
  326. */ 
  327. function image_column( $column, $post_id ) { 
  328. global $post; 
  329. switch ( $column ) { 
  330. case 'thumbnail': 
  331. echo get_the_post_thumbnail( $post_id, 'jetpack-portfolio-admin-thumb' ); 
  332. break; 
  333.  
  334. /** 
  335. * Adjust image column width 
  336. */ 
  337. function enqueue_admin_styles( $hook ) { 
  338. $screen = get_current_screen(); 
  339.  
  340. if ( 'edit.php' == $hook && self::CUSTOM_POST_TYPE == $screen->post_type && current_theme_supports( 'post-thumbnails' ) ) { 
  341. wp_add_inline_style( 'wp-admin', '.manage-column.column-thumbnail { width: 50px; } @media screen and (max-width: 360px) { .column-thumbnail{ display:none; } }' ); 
  342.  
  343. /** 
  344. * Follow CPT reading setting on CPT archive and taxonomy pages 
  345. */ 
  346. function query_reading_setting( $query ) { 
  347. if ( ! is_admin() && 
  348. $query->is_main_query() && 
  349. ( $query->is_post_type_archive( self::CUSTOM_POST_TYPE ) || $query->is_tax( self::CUSTOM_TAXONOMY_TYPE ) || $query->is_tax( self::CUSTOM_TAXONOMY_TAG ) ) 
  350. ) { 
  351. $query->set( 'posts_per_page', get_option( self::OPTION_READING_SETTING, '10' ) ); 
  352.  
  353. /** 
  354. * Our [portfolio] shortcode. 
  355. * Prints Portfolio data styled to look good on *any* theme. 
  356. * @return portfolio_shortcode_html 
  357. */ 
  358. static function portfolio_shortcode( $atts ) { 
  359. // Default attributes 
  360. $atts = shortcode_atts( array( 
  361. 'display_types' => true,  
  362. 'display_tags' => true,  
  363. 'display_content' => true,  
  364. 'show_filter' => false,  
  365. 'include_type' => false,  
  366. 'include_tag' => false,  
  367. 'columns' => 2,  
  368. 'showposts' => -1,  
  369. 'order' => 'asc',  
  370. 'orderby' => 'date',  
  371. ), $atts, 'portfolio' ); 
  372.  
  373. // A little sanitization 
  374. if ( $atts['display_types'] && 'true' != $atts['display_types'] ) { 
  375. $atts['display_types'] = false; 
  376.  
  377. if ( $atts['display_tags'] && 'true' != $atts['display_tags'] ) { 
  378. $atts['display_tags'] = false; 
  379.  
  380. if ( $atts['display_content'] && 'true' != $atts['display_content'] && 'full' != $atts['display_content'] ) { 
  381. $atts['display_content'] = false; 
  382.  
  383. if ( $atts['include_type'] ) { 
  384. $atts['include_type'] = explode( ', ', str_replace( ' ', '', $atts['include_type'] ) ); 
  385.  
  386. if ( $atts['include_tag'] ) { 
  387. $atts['include_tag'] = explode( ', ', str_replace( ' ', '', $atts['include_tag'] ) ); 
  388.  
  389. $atts['columns'] = absint( $atts['columns'] ); 
  390.  
  391. $atts['showposts'] = intval( $atts['showposts'] ); 
  392.  
  393.  
  394. if ( $atts['order'] ) { 
  395. $atts['order'] = urldecode( $atts['order'] ); 
  396. $atts['order'] = strtoupper( $atts['order'] ); 
  397. if ( 'DESC' != $atts['order'] ) { 
  398. $atts['order'] = 'ASC'; 
  399.  
  400. if ( $atts['orderby'] ) { 
  401. $atts['orderby'] = urldecode( $atts['orderby'] ); 
  402. $atts['orderby'] = strtolower( $atts['orderby'] ); 
  403. $allowed_keys = array( 'author', 'date', 'title', 'rand' ); 
  404.  
  405. $parsed = array(); 
  406. foreach ( explode( ', ', $atts['orderby'] ) as $portfolio_index_number => $orderby ) { 
  407. if ( ! in_array( $orderby, $allowed_keys ) ) { 
  408. continue; 
  409. $parsed[] = $orderby; 
  410.  
  411. if ( empty( $parsed ) ) { 
  412. unset( $atts['orderby'] ); 
  413. } else { 
  414. $atts['orderby'] = implode( ' ', $parsed ); 
  415.  
  416. // enqueue shortcode styles when shortcode is used 
  417. wp_enqueue_style( 'jetpack-portfolio-style', plugins_url( 'css/portfolio-shortcode.css', __FILE__ ), array(), '20140326' ); 
  418.  
  419. return self::portfolio_shortcode_html( $atts ); 
  420.  
  421. /** 
  422. * Query to retrieve entries from the Portfolio post_type. 
  423. * @return object 
  424. */ 
  425. static function portfolio_query( $atts ) { 
  426. // Default query arguments 
  427. $default = array( 
  428. 'order' => $atts['order'],  
  429. 'orderby' => $atts['orderby'],  
  430. 'posts_per_page' => $atts['showposts'],  
  431. ); 
  432.  
  433. $args = wp_parse_args( $atts, $default ); 
  434. $args['post_type'] = self::CUSTOM_POST_TYPE; // Force this post type 
  435.  
  436. if ( false != $atts['include_type'] || false != $atts['include_tag'] ) { 
  437. $args['tax_query'] = array(); 
  438.  
  439. // If 'include_type' has been set use it on the main query 
  440. if ( false != $atts['include_type'] ) { 
  441. array_push( $args['tax_query'], array( 
  442. 'taxonomy' => self::CUSTOM_TAXONOMY_TYPE,  
  443. 'field' => 'slug',  
  444. 'terms' => $atts['include_type'],  
  445. ) ); 
  446.  
  447. // If 'include_tag' has been set use it on the main query 
  448. if ( false != $atts['include_tag'] ) { 
  449. array_push( $args['tax_query'], array( 
  450. 'taxonomy' => self::CUSTOM_TAXONOMY_TAG,  
  451. 'field' => 'slug',  
  452. 'terms' => $atts['include_tag'],  
  453. ) ); 
  454.  
  455. if ( false != $atts['include_type'] && false != $atts['include_tag'] ) { 
  456. $args['tax_query']['relation'] = 'AND'; 
  457.  
  458. // Run the query and return 
  459. $query = new WP_Query( $args ); 
  460. return $query; 
  461.  
  462. /** 
  463. * The Portfolio shortcode loop. 
  464. * @todo add theme color styles 
  465. * @return html 
  466. */ 
  467. static function portfolio_shortcode_html( $atts ) { 
  468.  
  469. $query = self::portfolio_query( $atts ); 
  470. $portfolio_index_number = 0; 
  471.  
  472. ob_start(); 
  473.  
  474. // If we have posts, create the html 
  475. // with hportfolio markup 
  476. if ( $query->have_posts() ) { 
  477.  
  478. // Render styles 
  479. //self::themecolor_styles(); 
  480.  
  481. ?> 
  482. <div class="jetpack-portfolio-shortcode column-<?php echo esc_attr( $atts['columns'] ); ?>"> 
  483. <?php // open .jetpack-portfolio 
  484.  
  485. // Construct the loop... 
  486. while ( $query->have_posts() ) { 
  487. $query->the_post(); 
  488. $post_id = get_the_ID(); 
  489. ?> 
  490. <div class="portfolio-entry <?php echo esc_attr( self::get_project_class( $portfolio_index_number, $atts['columns'] ) ); ?>"> 
  491. <header class="portfolio-entry-header"> 
  492. <?php 
  493. // Featured image 
  494. echo self::get_portfolio_thumbnail_link( $post_id ); 
  495. ?> 
  496.  
  497. <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> 
  498.  
  499. <div class="portfolio-entry-meta"> 
  500. <?php 
  501. if ( false != $atts['display_types'] ) { 
  502. echo self::get_project_type( $post_id ); 
  503.  
  504. if ( false != $atts['display_tags'] ) { 
  505. echo self::get_project_tags( $post_id ); 
  506. ?> 
  507. </div> 
  508.  
  509. </header> 
  510.  
  511. <?php 
  512. // The content 
  513. if ( false !== $atts['display_content'] ) { 
  514. if ( 'full' === $atts['display_content'] ) { 
  515. echo '<div class="portfolio-entry-content">' . the_content() . '</div>'; 
  516. } else { 
  517. echo '<div class="portfolio-entry-content">' . the_excerpt() . '</div>'; 
  518. } ?> 
  519. </div><!-- close .portfolio-entry --> 
  520. <?php $portfolio_index_number++; 
  521. } // end of while loop 
  522.  
  523. wp_reset_postdata(); 
  524. ?> 
  525. </div><!-- close .jetpack-portfolio --> 
  526. <?php 
  527. } else { ?> 
  528. <p><em><?php _e( 'Your Portfolio Archive currently has no entries. You can start creating them on your dashboard.', 'jetpack' ); ?></p></em> 
  529. <?php 
  530. $html = ob_get_clean(); 
  531.  
  532. // If there is a [portfolio] within a [portfolio], remove the shortcode 
  533. if ( has_shortcode( $html, 'portfolio' ) ) { 
  534. remove_shortcode( 'portfolio' ); 
  535.  
  536. // Return the HTML block 
  537. return $html; 
  538.  
  539. /** 
  540. * Individual project class 
  541. * @return string 
  542. */ 
  543. static function get_project_class( $portfolio_index_number, $columns ) { 
  544. $project_types = wp_get_object_terms( get_the_ID(), self::CUSTOM_TAXONOMY_TYPE, array( 'fields' => 'slugs' ) ); 
  545. $class = array(); 
  546.  
  547. $class[] = 'portfolio-entry-column-'.$columns; 
  548. // add a type- class for each project type 
  549. foreach ( $project_types as $project_type ) { 
  550. $class[] = 'type-' . esc_html( $project_type ); 
  551. if( $columns > 1) { 
  552. if ( ( $portfolio_index_number % 2 ) == 0 ) { 
  553. $class[] = 'portfolio-entry-mobile-first-item-row'; 
  554. } else { 
  555. $class[] = 'portfolio-entry-mobile-last-item-row'; 
  556.  
  557. // add first and last classes to first and last items in a row 
  558. if ( ( $portfolio_index_number % $columns ) == 0 ) { 
  559. $class[] = 'portfolio-entry-first-item-row'; 
  560. } elseif ( ( $portfolio_index_number % $columns ) == ( $columns - 1 ) ) { 
  561. $class[] = 'portfolio-entry-last-item-row'; 
  562.  
  563.  
  564. /** 
  565. * Filter the class applied to project div in the portfolio 
  566. * @module custom-content-types 
  567. * @since 3.1.0 
  568. * @param string $class class name of the div. 
  569. * @param int $portfolio_index_number iterator count the number of columns up starting from 0. 
  570. * @param int $columns number of columns to display the content in. 
  571. */ 
  572. return apply_filters( 'portfolio-project-post-class', implode( " ", $class ) , $portfolio_index_number, $columns ); 
  573.  
  574. /** 
  575. * Displays the project type that a project belongs to. 
  576. * @return html 
  577. */ 
  578. static function get_project_type( $post_id ) { 
  579. $project_types = get_the_terms( $post_id, self::CUSTOM_TAXONOMY_TYPE ); 
  580.  
  581. // If no types, return empty string 
  582. if ( empty( $project_types ) || is_wp_error( $project_types ) ) { 
  583. return; 
  584.  
  585. $html = '<div class="project-types"><span>' . __( 'Types', 'jetpack' ) . ':</span>'; 
  586. $types = array(); 
  587. // Loop thorugh all the types 
  588. foreach ( $project_types as $project_type ) { 
  589. $project_type_link = get_term_link( $project_type, self::CUSTOM_TAXONOMY_TYPE ); 
  590.  
  591. if ( is_wp_error( $project_type_link ) ) { 
  592. return $project_type_link; 
  593.  
  594. $types[] = '<a href="' . esc_url( $project_type_link ) . '" rel="tag">' . esc_html( $project_type->name ) . '</a>'; 
  595. $html .= ' '.implode( ', ', $types ); 
  596. $html .= '</div>'; 
  597.  
  598. return $html; 
  599.  
  600. /** 
  601. * Displays the project tags that a project belongs to. 
  602. * @return html 
  603. */ 
  604. static function get_project_tags( $post_id ) { 
  605. $project_tags = get_the_terms( $post_id, self::CUSTOM_TAXONOMY_TAG ); 
  606.  
  607. // If no tags, return empty string 
  608. if ( empty( $project_tags ) || is_wp_error( $project_tags ) ) { 
  609. return false; 
  610.  
  611. $html = '<div class="project-tags"><span>' . __( 'Tags', 'jetpack' ) . ':</span>'; 
  612. $tags = array(); 
  613. // Loop thorugh all the tags 
  614. foreach ( $project_tags as $project_tag ) { 
  615. $project_tag_link = get_term_link( $project_tag, self::CUSTOM_TAXONOMY_TYPE ); 
  616.  
  617. if ( is_wp_error( $project_tag_link ) ) { 
  618. return $project_tag_link; 
  619.  
  620. $tags[] = '<a href="' . esc_url( $project_tag_link ) . '" rel="tag">' . esc_html( $project_tag->name ) . '</a>'; 
  621. $html .= ' '. implode( ', ', $tags ); 
  622. $html .= '</div>'; 
  623.  
  624. return $html; 
  625.  
  626. /** 
  627. * Display the featured image if it's available 
  628. * @return html 
  629. */ 
  630. static function get_portfolio_thumbnail_link( $post_id ) { 
  631. if ( has_post_thumbnail( $post_id ) ) { 
  632. /** 
  633. * Change the Portfolio thumbnail size. 
  634. * @module custom-content-types 
  635. * @since 3.4.0 
  636. * @param string|array $var Either a registered size keyword or size array. 
  637. */ 
  638. 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>';