diff --git a/wp-admin/edit-form-advanced.php b/wp-admin/edit-form-advanced.php index 540f1edab2..73cb63b8e3 100644 --- a/wp-admin/edit-form-advanced.php +++ b/wp-admin/edit-form-advanced.php @@ -259,8 +259,9 @@ foreach ( get_object_taxonomies( $post ) as $tax_name ) { add_meta_box( $tax_meta_box_id, $label, $taxonomy->meta_box_cb, null, 'side', 'core', array( 'taxonomy' => $tax_name ) ); } -if ( post_type_supports($post_type, 'page-attributes') ) - add_meta_box('pageparentdiv', 'page' == $post_type ? __('Page Attributes') : __('Attributes'), 'page_attributes_meta_box', null, 'side', 'core'); +if ( post_type_supports( $post_type, 'page-attributes' ) || count( get_page_templates( null, $post_type ) ) > 0 ) { + add_meta_box( 'pageparentdiv', $post_type_object->labels->attributes, 'page_attributes_meta_box', null, 'side', 'core' ); +} if ( $thumbnail_support && current_user_can( 'upload_files' ) ) add_meta_box('postimagediv', esc_html( $post_type_object->labels->featured_image ), 'post_thumbnail_meta_box', null, 'side', 'low'); diff --git a/wp-admin/includes/class-wp-posts-list-table.php b/wp-admin/includes/class-wp-posts-list-table.php index 714a491d2e..d26ab2f11b 100644 --- a/wp-admin/includes/class-wp-posts-list-table.php +++ b/wp-admin/includes/class-wp-posts-list-table.php @@ -1528,31 +1528,28 @@ class WP_Posts_List_Table extends WP_List_Table { - post_type ) : - ?> - - - + post_type ) ) ) : ?> + + + diff --git a/wp-admin/includes/meta-boxes.php b/wp-admin/includes/meta-boxes.php index 48c5e37a53..13d463d2c9 100644 --- a/wp-admin/includes/meta-boxes.php +++ b/wp-admin/includes/meta-boxes.php @@ -788,8 +788,7 @@ function post_revisions_meta_box( $post ) { * @param object $post */ function page_attributes_meta_box($post) { - $post_type_object = get_post_type_object($post->post_type); - if ( $post_type_object->hierarchical ) { + if ( is_post_type_hierarchical( $post->post_type ) ) : $dropdown_args = array( 'post_type' => $post->post_type, 'exclude_tree' => $post->ID, @@ -812,16 +811,17 @@ function page_attributes_meta_box($post) { */ $dropdown_args = apply_filters( 'page_attributes_dropdown_pages_args', $dropdown_args, $post ); $pages = wp_dropdown_pages( $dropdown_args ); - if ( ! empty($pages) ) { + if ( ! empty($pages) ) : ?>

post_type && 0 != count( get_page_templates( $post ) ) && get_option( 'page_for_posts' ) != $post->ID ) { - $template = !empty($post->page_template) ? $post->page_template : false; + endif; // end empty pages check + endif; // end hierarchical check. + + if ( count( get_page_templates( $post ) ) > 0 && get_option( 'page_for_posts' ) != $post->ID ) : + $template = ! empty( $post->page_template ) ? $post->page_template : false; ?>

- - +post_type ); ?> - + +post_type, 'page-attributes' ) ) : ?>

-post_type && get_current_screen()->get_help_tabs() ) { ?> +post_type && get_current_screen()->get_help_tabs() ) : ?>

-' . mysql2date( 's', $post->post_date, false ) . '
' . esc_html( $post->post_password ) . '
'; - if ( $post_type_object->hierarchical ) + if ( $post_type_object->hierarchical ) { echo '
' . $post->post_parent . '
'; + } - if ( $post->post_type == 'page' ) - echo '
' . esc_html( get_post_meta( $post->ID, '_wp_page_template', true ) ) . '
'; + echo '
' . esc_html( $post->page_template ) . '
'; - if ( post_type_supports( $post->post_type, 'page-attributes' ) ) + if ( post_type_supports( $post->post_type, 'page-attributes' ) ) { echo ''; + } $taxonomy_names = get_object_taxonomies( $post->post_type ); foreach ( $taxonomy_names as $taxonomy_name) { @@ -761,11 +762,13 @@ function touch_time( $edit = 1, $for_post = 1, $tab_index = 0, $multi = 0 ) { * Print out option HTML elements for the page templates drop-down. * * @since 1.5.0 + * @since 4.7.0 Added the `$post_type` parameter. * - * @param string $default Optional. The template file name. Default empty. + * @param string $default Optional. The template file name. Default empty. + * @param string $post_type Optional. Post type to get templates for. Default 'post'. */ -function page_template_dropdown( $default = '' ) { - $templates = get_page_templates( get_post() ); +function page_template_dropdown( $default = '', $post_type = 'page' ) { + $templates = get_page_templates( null, $post_type ); ksort( $templates ); foreach ( array_keys( $templates ) as $template ) { $selected = selected( $default, $templates[ $template ], false ); diff --git a/wp-admin/includes/theme.php b/wp-admin/includes/theme.php index 4eb01ea996..b2cc0eb61c 100644 --- a/wp-admin/includes/theme.php +++ b/wp-admin/includes/theme.php @@ -102,12 +102,14 @@ function delete_theme($stylesheet, $redirect = '') { * Get the Page Templates available in this theme * * @since 1.5.0 + * @since 4.7.0 Added the `$post_type` parameter. * - * @param WP_Post|null $post Optional. The post being edited, provided for context. + * @param WP_Post|null $post Optional. The post being edited, provided for context. + * @param string $post_type Optional. Post type to get the templates for. Default 'page'. * @return array Key is the template name, value is the filename of the template */ -function get_page_templates( $post = null ) { - return array_flip( wp_get_theme()->get_page_templates( $post ) ); +function get_page_templates( $post = null, $post_type = 'page' ) { + return array_flip( wp_get_theme()->get_page_templates( $post, $post_type ) ); } /** diff --git a/wp-includes/class-wp-post.php b/wp-includes/class-wp-post.php index a21776f192..c966934899 100644 --- a/wp-includes/class-wp-post.php +++ b/wp-includes/class-wp-post.php @@ -254,7 +254,7 @@ final class WP_Post { return true; if ( 'page_template' == $key ) - return ( 'page' == $this->post_type ); + return true; if ( 'post_category' == $key ) return true; diff --git a/wp-includes/class-wp-theme.php b/wp-includes/class-wp-theme.php index 05211d8689..66f623ee90 100644 --- a/wp-includes/class-wp-theme.php +++ b/wp-includes/class-wp-theme.php @@ -538,7 +538,7 @@ final class WP_Theme implements ArrayAccess { * @since 3.4.0 * @access private * - * @param string $key Type of data to store (theme, screenshot, headers, page_templates) + * @param string $key Type of data to store (theme, screenshot, headers, post_templates) * @param string $data Data to store * @return bool Return value from wp_cache_add() */ @@ -554,7 +554,7 @@ final class WP_Theme implements ArrayAccess { * @since 3.4.0 * @access private * - * @param string $key Type of data to retrieve (theme, screenshot, headers, page_templates) + * @param string $key Type of data to retrieve (theme, screenshot, headers, post_templates) * @return mixed Retrieved data */ private function cache_get( $key ) { @@ -568,7 +568,7 @@ final class WP_Theme implements ArrayAccess { * @access public */ public function cache_delete() { - foreach ( array( 'theme', 'screenshot', 'headers', 'page_templates' ) as $key ) + foreach ( array( 'theme', 'screenshot', 'headers', 'post_templates' ) as $key ) wp_cache_delete( $key . '-' . $this->cache_hash, 'themes' ); $this->template = $this->textdomain_loaded = $this->theme_root_uri = $this->parent = $this->errors = $this->headers_sanitized = $this->name_translated = null; $this->headers = array(); @@ -1006,56 +1006,101 @@ final class WP_Theme implements ArrayAccess { } /** - * Returns the theme's page templates. + * Returns the theme's post templates. * - * @since 3.4.0 + * @since 4.7.0 * @access public * - * @param WP_Post|null $post Optional. The post being edited, provided for context. - * @return array Array of page templates, keyed by filename, with the value of the translated header name. + * @return array Array of page templates, keyed by filename and post type, + * with the value of the translated header name. */ - public function get_page_templates( $post = null ) { + public function get_post_templates() { // If you screw up your current theme and we invalidate your parent, most things still work. Let it slide. - if ( $this->errors() && $this->errors()->get_error_codes() !== array( 'theme_parent_invalid' ) ) + if ( $this->errors() && $this->errors()->get_error_codes() !== array( 'theme_parent_invalid' ) ) { return array(); + } - $page_templates = $this->cache_get( 'page_templates' ); + $post_templates = $this->cache_get( 'post_templates' ); - if ( ! is_array( $page_templates ) ) { - $page_templates = array(); + if ( ! is_array( $post_templates ) ) { + $post_templates = array(); $files = (array) $this->get_files( 'php', 1 ); foreach ( $files as $file => $full_path ) { - if ( ! preg_match( '|Template Name:(.*)$|mi', file_get_contents( $full_path ), $header ) ) + if ( ! preg_match( '|Template Name:(.*)$|mi', file_get_contents( $full_path ), $header ) ) { continue; - $page_templates[ $file ] = _cleanup_header_comment( $header[1] ); + } + + $types = array( 'page' ); + if ( preg_match( '|Template Post Type:(.*)$|mi', file_get_contents( $full_path ), $type ) ) { + $types = explode( ',', _cleanup_header_comment( $type[1] ) ); + } + + foreach ( $types as $type ) { + $type = trim( $type ); + if ( ! isset( $post_templates[ $type ] ) ) { + $post_templates[ $type ] = array(); + } + + $post_templates[ $type ][ $file ] = _cleanup_header_comment( $header[1] ); + } } - $this->cache_add( 'page_templates', $page_templates ); + $this->cache_add( 'post_templates', $post_templates ); } if ( $this->load_textdomain() ) { - foreach ( $page_templates as &$page_template ) { - $page_template = $this->translate_header( 'Template Name', $page_template ); + foreach ( $post_templates as &$post_type ) { + foreach ( $post_type as &$post_template ) { + $post_template = $this->translate_header( 'Template Name', $post_template ); + } } } - if ( $this->parent() ) - $page_templates += $this->parent()->get_page_templates( $post ); + return $post_templates; + } + + /** + * Returns the theme's post templates for a given post type. + * + * @since 3.4.0 + * @since 4.7.0 Added the `$post_type` parameter. + * @access public + * + * @param WP_Post|null $post Optional. The post being edited, provided for context. + * @param string $post_type Optional. Post type to get the templates for. Default 'page'. + * If a post is provided, its post type is used. + * @return array Array of page templates, keyed by filename, with the value of the translated header name. + */ + public function get_page_templates( $post = null, $post_type = 'page' ) { + if ( $post ) { + $post_type = get_post_type( $post ); + } + + $post_templates = $this->get_post_templates(); + $post_templates = isset( $post_templates[ $post_type ] ) ? $post_templates[ $post_type ] : array(); + + if ( $this->parent() ) { + $post_templates += $this->parent()->get_page_templates( $post ); + } /** * Filters list of page templates for a theme. * + * The dynamic portion of the hook name, `$post_type`, refers to the post type. + * * @since 3.9.0 * @since 4.4.0 Converted to allow complete control over the `$page_templates` array. + * @since 4.7.0 Added the `$post_type` parameter. * - * @param array $page_templates Array of page templates. Keys are filenames, + * @param array $post_templates Array of page templates. Keys are filenames, * values are translated names. * @param WP_Theme $this The theme object. * @param WP_Post|null $post The post being edited, provided for context, or null. + * @param string $post_type Post type to get the templates for. */ - return (array) apply_filters( 'theme_page_templates', $page_templates, $this, $post ); + return (array) apply_filters( "theme_{$post_type}_templates", $post_templates, $this, $post, $post_type ); } /** diff --git a/wp-includes/post-template.php b/wp-includes/post-template.php index 9e2b3a09e5..ac8864334a 100644 --- a/wp-includes/post-template.php +++ b/wp-includes/post-template.php @@ -594,23 +594,40 @@ function get_body_class( $class = '' ) { if ( is_404() ) $classes[] = 'error404'; - if ( is_single() ) { + if ( is_singular() ) { $post_id = $wp_query->get_queried_object_id(); $post = $wp_query->get_queried_object(); + $post_type = $post->post_type; - $classes[] = 'single'; - if ( isset( $post->post_type ) ) { - $classes[] = 'single-' . sanitize_html_class($post->post_type, $post_id); - $classes[] = 'postid-' . $post_id; + if ( is_page_template() ) { + $classes[] = "{$post_type}-template"; - // Post Format - if ( post_type_supports( $post->post_type, 'post-formats' ) ) { - $post_format = get_post_format( $post->ID ); + $template_slug = get_page_template_slug( $post_id ); + $template_parts = explode( '/', $template_slug ); - if ( $post_format && !is_wp_error($post_format) ) - $classes[] = 'single-format-' . sanitize_html_class( $post_format ); - else - $classes[] = 'single-format-standard'; + foreach ( $template_parts as $part ) { + $classes[] = "{$post_type}-template-" . sanitize_html_class( str_replace( array( '.', '/' ), '-', basename( $part, '.php' ) ) ); + } + $classes[] = "{$post_type}-template-" . sanitize_html_class( str_replace( '.', '-', $template_slug ) ); + } else { + $classes[] = "{$post_type}-template-default"; + } + + if ( is_single() ) { + $classes[] = 'single'; + if ( isset( $post->post_type ) ) { + $classes[] = 'single-' . sanitize_html_class( $post->post_type, $post_id ); + $classes[] = 'postid-' . $post_id; + + // Post Format + if ( post_type_supports( $post->post_type, 'post-formats' ) ) { + $post_format = get_post_format( $post->ID ); + + if ( $post_format && !is_wp_error($post_format) ) + $classes[] = 'single-format-' . sanitize_html_class( $post_format ); + else + $classes[] = 'single-format-standard'; + } } } @@ -619,6 +636,23 @@ function get_body_class( $class = '' ) { $mime_prefix = array( 'application/', 'image/', 'text/', 'audio/', 'video/', 'music/' ); $classes[] = 'attachmentid-' . $post_id; $classes[] = 'attachment-' . str_replace( $mime_prefix, '', $mime_type ); + } elseif ( is_page() ) { + $classes[] = 'page'; + + $page_id = $wp_query->get_queried_object_id(); + + $post = get_post($page_id); + + $classes[] = 'page-id-' . $page_id; + + if ( get_pages( array( 'parent' => $page_id, 'number' => 1 ) ) ) { + $classes[] = 'page-parent'; + } + + if ( $post->post_parent ) { + $classes[] = 'page-child'; + $classes[] = 'parent-pageid-' . $post->post_parent; + } } } elseif ( is_archive() ) { if ( is_post_type_archive() ) { @@ -671,36 +705,6 @@ function get_body_class( $class = '' ) { $classes[] = 'term-' . $term->term_id; } } - } elseif ( is_page() ) { - $classes[] = 'page'; - - $page_id = $wp_query->get_queried_object_id(); - - $post = get_post($page_id); - - $classes[] = 'page-id-' . $page_id; - - if ( get_pages( array( 'parent' => $page_id, 'number' => 1 ) ) ) { - $classes[] = 'page-parent'; - } - - if ( $post->post_parent ) { - $classes[] = 'page-child'; - $classes[] = 'parent-pageid-' . $post->post_parent; - } - if ( is_page_template() ) { - $classes[] = 'page-template'; - - $template_slug = get_page_template_slug( $page_id ); - $template_parts = explode( '/', $template_slug ); - - foreach ( $template_parts as $part ) { - $classes[] = 'page-template-' . sanitize_html_class( str_replace( array( '.', '/' ), '-', basename( $part, '.php' ) ) ); - } - $classes[] = 'page-template-' . sanitize_html_class( str_replace( '.', '-', $template_slug ) ); - } else { - $classes[] = 'page-template-default'; - } } if ( is_user_logged_in() ) @@ -1621,14 +1625,12 @@ function get_the_password_form( $post = 0 ) { * * @since 2.5.0 * @since 4.2.0 The `$template` parameter was changed to also accept an array of page templates. + * @since 4.7.0 Now works with any post type, not just pages. * * @param string|array $template The specific template name or array of templates to match. * @return bool True on success, false on failure. */ function is_page_template( $template = '' ) { - if ( ! is_page() ) - return false; - $page_template = get_page_template_slug( get_queried_object_id() ); if ( empty( $template ) ) @@ -1649,21 +1651,28 @@ function is_page_template( $template = '' ) { } /** - * Get the specific template name for a page. + * Get the specific template name for a given post. * * @since 3.4.0 + * @since 4.7.0 Now works with any post type, not just pages. * - * @param int $post_id Optional. The page ID to check. Defaults to the current post, when used in the loop. + * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post. * @return string|false Page template filename. Returns an empty string when the default page template - * is in use. Returns false if the post is not a page. + * is in use. Returns false if the post does not exist. */ -function get_page_template_slug( $post_id = null ) { - $post = get_post( $post_id ); - if ( ! $post || 'page' != $post->post_type ) +function get_page_template_slug( $post = null ) { + $post = get_post( $post ); + + if ( ! $post ) { return false; + } + $template = get_post_meta( $post->ID, '_wp_page_template', true ); - if ( ! $template || 'default' == $template ) + + if ( ! $template || 'default' == $template ) { return ''; + } + return $template; } diff --git a/wp-includes/post.php b/wp-includes/post.php index cfb941f8c8..89ddaaa711 100644 --- a/wp-includes/post.php +++ b/wp-includes/post.php @@ -66,6 +66,7 @@ function create_initial_post_types() { 'add_new' => _x( 'Add New', 'add new media' ), 'edit_item' => __( 'Edit Media' ), 'view_item' => __( 'View Attachment Page' ), + 'attributes' => __( 'Attachment Attributes' ), ), 'public' => true, 'show_ui' => true, @@ -1326,6 +1327,7 @@ function _post_type_meta_capabilities( $capabilities = null ) { * post types. Default is 'Parent Page:'. * - `all_items` - Label to signify all items in a submenu link. Default is 'All Posts' / 'All Pages'. * - `archives` - Label for archives in nav menus. Default is 'Post Archives' / 'Page Archives'. + * - `attributes` - Label for the attributes meta box. Default is 'Post Attributes' / 'Page Attributes'. * - `insert_into_item` - Label for the media frame button. Default is 'Insert into post' / 'Insert into page'. * - `uploaded_to_this_item` - Label for the media frame filter. Default is 'Uploaded to this post' / * 'Uploaded to this page'. @@ -1351,7 +1353,7 @@ function _post_type_meta_capabilities( $capabilities = null ) { * @since 4.4.0 Added the `insert_into_item`, `uploaded_to_this_item`, `filter_items_list`, * `items_list_navigation`, and `items_list` labels. * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object. - * @since 4.7.0 Added the `view_items` label. + * @since 4.7.0 Added the `view_items` and `attributes` labels. * * @access private * @@ -1374,6 +1376,7 @@ function get_post_type_labels( $post_type_object ) { 'parent_item_colon' => array( null, __('Parent Page:') ), 'all_items' => array( __( 'All Posts' ), __( 'All Pages' ) ), 'archives' => array( __( 'Post Archives' ), __( 'Page Archives' ) ), + 'attributes' => array( __( 'Post Attributes' ), __( 'Page Attributes' ) ), 'insert_into_item' => array( __( 'Insert into post' ), __( 'Insert into page' ) ), 'uploaded_to_this_item' => array( __( 'Uploaded to this post' ), __( 'Uploaded to this page' ) ), 'featured_image' => array( __( 'Featured Image' ), __( 'Featured Image' ) ), @@ -3393,7 +3396,7 @@ function wp_insert_post( $postarr, $wp_error = false ) { $post = get_post( $post_ID ); - if ( ! empty( $postarr['page_template'] ) && 'page' == $data['post_type'] ) { + if ( ! empty( $postarr['page_template'] ) ) { $post->page_template = $postarr['page_template']; $page_templates = wp_get_theme()->get_page_templates( $post ); if ( 'default' != $postarr['page_template'] && ! isset( $page_templates[ $postarr['page_template'] ] ) ) { diff --git a/wp-includes/template.php b/wp-includes/template.php index fd0476bae1..636cb9005c 100644 --- a/wp-includes/template.php +++ b/wp-includes/template.php @@ -449,15 +449,17 @@ function get_search_template() { * * The hierarchy for this template looks like: * - * 1. single-{post_type}-{post_name}.php - * 2. single-{post_type}.php - * 3. single.php + * 1. {Post Type Template}.php + * 2. single-{post_type}-{post_name}.php + * 3. single-{post_type}.php + * 4. single.php * * An example of this is: * - * 1. single-post-hello-world.php - * 2. single-post.php - * 3. single.php + * 1. templates/full-width.php + * 2. single-post-hello-world.php + * 3. single-post.php + * 4. single.php * * The template hierarchy is filterable via the {@see 'single_template_hierarchy'} hook. * The template path is filterable via the {@see 'single_template'} hook. @@ -466,6 +468,7 @@ function get_search_template() { * @since 4.4.0 `single-{post_type}-{post_name}.php` was added to the top of the template hierarchy. * @since 4.7.0 The decoded form of `single-{post_type}-{post_name}.php` was added to the top of the * template hierarchy when the post name contains multibyte characters. + * @since 4.7.0 {Post Type Template}.php was added to the top of the template hierarchy. * * @see get_query_template() * @@ -477,6 +480,10 @@ function get_single_template() { $templates = array(); if ( ! empty( $object->post_type ) ) { + $template = get_page_template_slug( $object ); + if ( $template && 0 === validate_file( $template ) ) { + $templates[] = $template; + } $name_decoded = urldecode( $object->post_name ); if ( $name_decoded !== $object->post_name ) { diff --git a/wp-includes/version.php b/wp-includes/version.php index 4d627bef1d..17798803f2 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -4,7 +4,7 @@ * * @global string $wp_version */ -$wp_version = '4.7-alpha-38950'; +$wp_version = '4.7-alpha-38951'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.