label pairs. * * @return array */ public static function get_groups() { $groups = [ 'basic' => [ 'label' => esc_html__( 'Basic', 'headfoot' ), 'rules' => [ 'global' => esc_html__( 'Entire Website', 'headfoot' ), 'singulars' => esc_html__( 'All Singulars', 'headfoot' ), 'archives' => esc_html__( 'All Archives', 'headfoot' ), ], ], 'special' => [ 'label' => esc_html__( 'Special Pages', 'headfoot' ), 'rules' => [ 'front' => esc_html__( 'Front Page', 'headfoot' ), 'blog' => esc_html__( 'Blog / Posts Page', 'headfoot' ), '404' => esc_html__( '404 Page', 'headfoot' ), 'search' => esc_html__( 'Search Page', 'headfoot' ), 'date' => esc_html__( 'Date Archive', 'headfoot' ), 'author' => esc_html__( 'Author Archive', 'headfoot' ), ], ], 'post_types' => [ 'label' => esc_html__( 'Post Types', 'headfoot' ), 'rules' => self::get_post_type_rules(), ], 'taxonomies' => [ 'label' => esc_html__( 'Taxonomies', 'headfoot' ), 'rules' => self::get_taxonomy_rules(), ], 'specific' => [ 'label' => esc_html__( 'Specific Target', 'headfoot' ), 'rules' => [ self::SPECIFIC => esc_html__( 'Specific Pages / Posts / Terms', 'headfoot' ), ], ], ]; /** * Filters the rule groups of the meta box. * * @param array $groups Groups with a label and identifier => label pairs. */ return apply_filters( 'headfoot_rules', $groups ); } /** * Returns every fixed rule identifier with its label. * * The placeholder of the specific target group is not a rule and is left out. * * @return array identifier => label */ public static function get_all() { $all = []; foreach ( self::get_groups() as $key => $group ) { if ( 'specific' === $key || empty( $group['rules'] ) || ! is_array( $group['rules'] ) ) { continue; } foreach ( $group['rules'] as $rule => $label ) { $all[ (string) $rule ] = $label; } } unset( $all[ self::SPECIFIC ] ); return $all; } /** * Builds the singular and archive rules of the public post types. * * @return array */ private static function get_post_type_rules() { $rules = []; $skip = [ Post_Type::POST_TYPE, 'attachment', 'elementor_library', 'e-landing-page', 'e-floating-buttons' ]; foreach ( get_post_types( [ 'public' => true ], 'objects' ) as $post_type ) { if ( in_array( $post_type->name, $skip, true ) || ! preg_match( '/^[a-z0-9_-]+$/', $post_type->name ) ) { continue; } /* translators: %s: Post type plural name. */ $rules[ 'singular:' . $post_type->name ] = sprintf( esc_html__( 'All %s', 'headfoot' ), $post_type->labels->name ); if ( $post_type->has_archive ) { /* translators: %s: Post type singular name. */ $rules[ 'archive:' . $post_type->name ] = sprintf( esc_html__( '%s Archive', 'headfoot' ), $post_type->labels->singular_name ); } } return $rules; } /** * Builds the archive rules of the public taxonomies. * * @return array */ private static function get_taxonomy_rules() { $rules = []; foreach ( get_taxonomies( [ 'public' => true ], 'objects' ) as $taxonomy ) { if ( 'post_format' === $taxonomy->name || ! preg_match( '/^[a-z0-9_-]+$/', $taxonomy->name ) ) { continue; } /* translators: %s: Taxonomy singular name. */ $rules[ 'tax:' . $taxonomy->name ] = sprintf( esc_html__( 'All %s Archives', 'headfoot' ), $taxonomy->labels->singular_name ); } return $rules; } /** * Returns a label for one rule in plain words. * * @param string $rule Rule identifier. * @return string */ public static function get_label( $rule ) { $all = self::get_all(); if ( isset( $all[ $rule ] ) ) { return $all[ $rule ]; } list( $prefix, $value ) = self::split( $rule ); if ( 'post' === $prefix ) { $post = get_post( (int) $value ); if ( ! $post ) { /* translators: %d: Post ID. */ return sprintf( esc_html__( 'Deleted post #%d', 'headfoot' ), (int) $value ); } $type_object = get_post_type_object( $post->post_type ); $type_label = $type_object ? $type_object->labels->singular_name : $post->post_type; return sprintf( '%s (%s)', get_the_title( $post ), $type_label ); } if ( 'term' === $prefix ) { $term = get_term( (int) $value ); if ( ! $term || is_wp_error( $term ) ) { /* translators: %d: Term ID. */ return sprintf( esc_html__( 'Deleted term #%d', 'headfoot' ), (int) $value ); } $tax_object = get_taxonomy( $term->taxonomy ); $tax_label = $tax_object ? $tax_object->labels->singular_name : $term->taxonomy; return sprintf( '%s (%s)', $term->name, $tax_label ); } return $rule; } /** * Tells whether a rule is a post:{ID} or term:{ID} rule. * * @param string $rule Rule identifier. * @return bool */ public static function is_specific( $rule ) { return (bool) preg_match( '/^(post|term):[1-9][0-9]*$/', (string) $rule ); } /** * Splits a rule into prefix and value. * * @param string $rule Rule identifier. * @return array [ prefix, value ]. The value is an empty string for rules without a colon. */ private static function split( $rule ) { $parts = explode( ':', (string) $rule, 2 ); return [ $parts[0], isset( $parts[1] ) ? $parts[1] : '' ]; } /** * Tests one rule against the current request. * * @param string $rule Rule identifier. * @return bool */ public static function matches( $rule ) { list( $prefix, $value ) = self::split( $rule ); switch ( $prefix ) { case 'global': $matched = true; break; case 'singulars': $matched = is_singular(); break; case 'archives': $matched = is_archive(); break; case 'front': $matched = is_front_page(); break; case 'blog': $matched = is_home(); break; case '404': $matched = is_404(); break; case 'search': $matched = is_search(); break; case 'date': $matched = is_date(); break; case 'author': $matched = is_author(); break; case 'singular': $matched = '' !== $value && is_singular( $value ); break; case 'archive': $matched = '' !== $value && is_post_type_archive( $value ); break; case 'tax': if ( 'category' === $value ) { $matched = is_category(); } elseif ( 'post_tag' === $value ) { $matched = is_tag(); } else { $matched = '' !== $value && is_tax( $value ); } break; case 'post': $matched = is_singular() && get_queried_object_id() === (int) $value; break; case 'term': $matched = ( is_category() || is_tag() || is_tax() ) && get_queried_object_id() === (int) $value; break; default: $matched = false; } /** * Filters the result of one rule. * * @param bool $matched Whether the rule matches the current request. * @param string $rule Rule identifier. */ return (bool) apply_filters( 'headfoot_rule_match', $matched, $rule ); } /** * Returns the priority score of a rule. * * @param string $rule Rule identifier. * @return int */ public static function score( $rule ) { list( $prefix ) = self::split( $rule ); switch ( $prefix ) { case 'post': case 'term': return 100; case 'front': case 'blog': case '404': case 'search': return 80; case 'date': case 'author': return 70; case 'singular': case 'archive': case 'tax': return 50; case 'singulars': case 'archives': return 20; case 'global': return 10; } return 0; } /** * Sanitizes a list of rules. * * Drops every value that does not match the pattern, and every fixed rule * that the plugin does not know. * * @param mixed $rules Raw list. * @return array */ public static function sanitize_list( $rules ) { if ( ! is_array( $rules ) ) { return []; } $known = self::get_all(); $clean = []; foreach ( $rules as $rule ) { if ( ! is_string( $rule ) ) { continue; } $rule = trim( $rule ); if ( ! preg_match( self::PATTERN, $rule ) ) { continue; } if ( ! isset( $known[ $rule ] ) && ! self::is_specific( $rule ) ) { continue; } $clean[ $rule ] = $rule; } return array_values( $clean ); } /** * Returns the choices of the user role field. * * @return array value => label */ public static function get_role_options() { $options = [ 'all' => esc_html__( 'All', 'headfoot' ), 'logged-in' => esc_html__( 'Logged In', 'headfoot' ), 'logged-out' => esc_html__( 'Logged Out', 'headfoot' ), ]; foreach ( wp_roles()->roles as $slug => $role ) { $options[ $slug ] = translate_user_role( $role['name'] ); } return $options; } /** * Sanitizes the user role list. * * @param mixed $roles Raw list. * @return array */ public static function sanitize_roles( $roles ) { if ( ! is_array( $roles ) ) { return []; } $allowed = self::get_role_options(); $clean = []; foreach ( $roles as $role ) { if ( ! is_string( $role ) ) { continue; } $role = sanitize_key( $role ); if ( isset( $allowed[ $role ] ) ) { $clean[ $role ] = $role; } } return array_values( $clean ); } /** * Tests the user role rule. * * An empty list behaves like all. * * @param array $roles Stored role values. * @return bool */ public static function role_matches( $roles ) { if ( empty( $roles ) || ! is_array( $roles ) || in_array( 'all', $roles, true ) ) { return true; } $logged_in = is_user_logged_in(); $user_roles = $logged_in ? (array) wp_get_current_user()->roles : []; foreach ( $roles as $role ) { if ( 'logged-in' === $role && $logged_in ) { return true; } if ( 'logged-out' === $role && ! $logged_in ) { return true; } if ( in_array( $role, $user_roles, true ) ) { return true; } } return false; } }