esc_html__( 'Header & Footer', 'headfoot' ), 'singular_name' => esc_html__( 'Template', 'headfoot' ), 'menu_name' => esc_html__( 'Header & Footer', 'headfoot' ), 'name_admin_bar' => esc_html__( 'Header / Footer Template', 'headfoot' ), 'add_new' => esc_html__( 'Add New', 'headfoot' ), 'add_new_item' => esc_html__( 'Add New Template', 'headfoot' ), 'new_item' => esc_html__( 'New Template', 'headfoot' ), 'edit_item' => esc_html__( 'Edit Template', 'headfoot' ), 'view_item' => esc_html__( 'View Template', 'headfoot' ), 'all_items' => esc_html__( 'Header & Footer', 'headfoot' ), 'search_items' => esc_html__( 'Search Templates', 'headfoot' ), 'not_found' => esc_html__( 'No templates found.', 'headfoot' ), 'not_found_in_trash' => esc_html__( 'No templates found in Trash.', 'headfoot' ), ]; register_post_type( self::POST_TYPE, [ 'labels' => $labels, 'public' => true, 'publicly_queryable' => true, 'exclude_from_search' => true, 'show_in_nav_menus' => false, 'show_in_rest' => false, // The REST API would list templates to visitors. 'embeddable' => false, 'show_ui' => true, 'show_in_menu' => 'themes.php', 'rewrite' => [ 'slug' => 'headfoot-template', 'with_front' => false, ], 'capability_type' => 'page', 'map_meta_cap' => true, 'supports' => [ 'title', 'elementor' ], 'hierarchical' => false, ] ); } /** * Registers the meta keys with sanitize callbacks. * * @return void */ public function register_meta() { $auth = function () { return current_user_can( 'edit_pages' ); }; register_post_meta( self::POST_TYPE, self::META_TYPE, [ 'type' => 'string', 'single' => true, 'default' => 'header', 'sanitize_callback' => [ __CLASS__, 'sanitize_type' ], 'auth_callback' => $auth, 'show_in_rest' => false, ] ); foreach ( [ self::META_INCLUDE, self::META_EXCLUDE ] as $key ) { register_post_meta( self::POST_TYPE, $key, [ 'type' => 'array', 'single' => true, 'default' => [], 'sanitize_callback' => [ Rules::class, 'sanitize_list' ], 'auth_callback' => $auth, 'show_in_rest' => false, ] ); } register_post_meta( self::POST_TYPE, self::META_ROLES, [ 'type' => 'array', 'single' => true, 'default' => [], 'sanitize_callback' => [ Rules::class, 'sanitize_roles' ], 'auth_callback' => $auth, 'show_in_rest' => false, ] ); register_post_meta( self::POST_TYPE, self::META_CANVAS, [ 'type' => 'string', 'single' => true, 'default' => '0', 'sanitize_callback' => [ __CLASS__, 'sanitize_flag' ], 'auth_callback' => $auth, 'show_in_rest' => false, ] ); } /** * Returns the template types with their labels. * * @return array type => label */ public static function get_types() { return [ 'header' => esc_html__( 'Header', 'headfoot' ), 'below_header' => esc_html__( 'Below Header', 'headfoot' ), 'above_footer' => esc_html__( 'Above Footer', 'headfoot' ), 'footer' => esc_html__( 'Footer', 'headfoot' ), ]; } /** * Returns the label of a template type. * * @param string $type Template type. * @return string */ public static function get_type_label( $type ) { $types = self::get_types(); $type = self::sanitize_type( $type ); return $types[ $type ]; } /** * Sanitizes the template type. * * @param mixed $value Raw value. * @return string header, below_header, above_footer, or footer. Unknown values become header. */ public static function sanitize_type( $value ) { return is_string( $value ) && isset( self::get_types()[ $value ] ) ? $value : 'header'; } /** * Sanitizes a 1 or 0 flag. * * @param mixed $value Raw value. * @return string */ public static function sanitize_flag( $value ) { return in_array( $value, [ '1', 1, true, 'on' ], true ) ? '1' : '0'; } /** * Writes the Elementor document type on every save. * * @param int $post_id Post ID. * @param \WP_Post $post Post object. * @return void */ public function set_document_type( $post_id, $post ) { if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) { return; } if ( 'auto-draft' === $post->post_status ) { return; } update_post_meta( $post_id, '_elementor_template_type', self::DOCUMENT_TYPE ); // The resolver queries this key, so every template needs a stored row. if ( ! metadata_exists( 'post', $post_id, self::META_TYPE ) ) { update_post_meta( $post_id, self::META_TYPE, 'header' ); } } /** * Clears the resolver cache when a template is trashed, restored, or deleted. * * @param int $post_id Post ID. * @param \WP_Post|null $post Post object, when the hook passes one. * @return void */ public function flush_resolver_for_post( $post_id, $post = null ) { $type = $post instanceof \WP_Post ? $post->post_type : get_post_type( $post_id ); if ( self::POST_TYPE === $type ) { Resolver::flush_templates(); } } /** * Clears the resolver cache when a rule meta key changes. * * @param int|array $meta_id Meta ID or IDs. * @param int $post_id Post ID. * @param string $meta_key Meta key. * @return void */ public function flush_resolver_for_meta( $meta_id, $post_id, $meta_key ) { if ( 0 === strpos( (string) $meta_key, '_headfoot_' ) ) { Resolver::flush_templates(); } } /** * Tells whether a query asks for the template post type. * * @param \WP_Query $query Query to test. * @return bool */ public static function queries_templates( $query ) { $type = $query->get( 'post_type' ); if ( is_array( $type ) ) { return in_array( self::POST_TYPE, $type, true ); } return self::POST_TYPE === $type; } /** * Removes the post type from the core sitemaps. * * @param array $post_types Post types in the sitemap. * @return array */ public function remove_from_sitemap( $post_types ) { unset( $post_types[ self::POST_TYPE ] ); return $post_types; } /** * Turns off embeds for templates. * * @param bool $embeddable Whether the post is embeddable. * @param \WP_Post $post Post object. * @return bool */ public function disable_embed( $embeddable, $post ) { if ( $post && self::POST_TYPE === get_post_type( $post ) ) { return false; } return $embeddable; } /** * Turns off the oEmbed endpoint for templates on WordPress versions without the embeddable flag. * * @param array|false $data oEmbed response data. * @param \WP_Post $post Post object. * @return array|false */ public function disable_oembed_response( $data, $post ) { if ( $post && self::POST_TYPE === get_post_type( $post ) ) { return false; } return $data; } /** * Keeps the single view of a template out of search engines. * * @param array $robots Robots directives. * @return array */ public function noindex( $robots ) { if ( is_singular( self::POST_TYPE ) ) { $robots['noindex'] = true; $robots['nofollow'] = true; } return $robots; } /** * Sends a 404 to visitors who cannot edit posts. * * Covers the single view, and every other front end query of the post * type such as ?post_type=headfoot_template or its feed. The Elementor * preview runs with the cookies of the editing user, so the editor keeps * working. * * @return void */ public function block_visitors() { if ( current_user_can( 'edit_posts' ) ) { return; } global $wp_query; if ( ! is_singular( self::POST_TYPE ) && ! self::queries_templates( $wp_query ) ) { return; } $wp_query->set_404(); $wp_query->posts = []; $wp_query->post = null; $wp_query->post_count = 0; $GLOBALS['post'] = null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- The template must not leak into the 404 page. status_header( 404 ); nocache_headers(); } }