<?php
namespace wpbuddy\rich_snippets;
use wpbuddy\rich_snippets\Cache_Model;
use wpbuddy\rich_snippets\View;
use function wpbuddy\rich_snippets\rich_snippets;
if ( ! defined( 'ABSPATH' ) ) {
exit;
} // Exit if accessed directly
/**
* Class Admin_Position_Controller
*
* Starts up all the admin things needed to positioning snippets.
*
* @package wpbuddy\rich_snippets
*
* @since 2.0.0
*/
class Admin_Position_Controller {
/**
* The instance.
*
* @var Admin_Position_Controller
*
* @since 2.0.0
*/
protected static $_instance = null;
/**
* If this instance has been initialized already.
*
* @since 2.0.0
*
* @var bool
*/
protected $_initialized = false;
/**
* The current rule row number.
*
* @since 2.0.0
*
* @var int
*/
protected $_current_rule_row_no = 0;
/**
* Get the singleton instance.
*
* Creates a new instance of the class if it does not exists.
*
* @return Admin_Position_Controller
*
* @since 2.0.0
*/
public static function instance() {
if ( null === self::$_instance ) {
self::$_instance = new self;
}
return self::$_instance;
}
/**
* Magic function for cloning.
*
* Disallow cloning as this is a singleton class.
*
* @since 2.0.0
*/
protected function __clone() {
}
/**
* Magic method for setting upt the class.
*
* Disallow external instances.
*
* @since 2.0.0
*/
protected function __construct() {
}
/**
* Init metaboxes.
*
* @since 2.0.0
*/
public function init() {
if ( $this->_initialized ) {
return;
}
add_action( 'wpbuddy/rich_snippets/schemas/metaboxes', array( self::$_instance, 'add_metaboxes' ) );
add_action( 'admin_enqueue_scripts', array( self::$_instance, 'enqueue_scripts' ) );
add_action( 'wpbuddy/rich_snippets/global_schemas/save', array( self::$_instance, 'save_positions' ) );
$this->_initialized = true;
}
/**
* Enqueue Scripts
*
* @param string $hook_suffix
*
* @since 2.0.0
*/
public function enqueue_scripts( $hook_suffix ) {
if ( ! ( 'post.php' === $hook_suffix || 'post-new.php' === $hook_suffix ) ) {
return;
}
if ( ! function_exists( 'get_current_screen' ) ) {
return;
}
$screen = get_current_screen();
if ( 'wpb-rs-global' !== $screen->id ) {
return;
}
wp_enqueue_style(
'wpb-rs-admin-position',
plugins_url( 'css/admin-position.css', rich_snippets()->get_plugin_file() ),
array( 'wpb-rs-admin-snippets' ),
filemtime( plugin_dir_path( rich_snippets()->get_plugin_file() ) . 'css/admin-position.css' )
);
wp_enqueue_script(
'wpb-rs-admin-position',
plugins_url( 'js/admin-position.js', rich_snippets()->get_plugin_file() ),
array( 'wpb-rs-admin-snippets' ),
filemtime( plugin_dir_path( rich_snippets()->get_plugin_file() ) . 'js/admin-position.js' )
);
$args = call_user_func( function () {
$o = new \stdClass();
$o->nonce = wp_create_nonce( 'wp_rest' );
$o->rest_url = untrailingslashit( rest_url( 'wpbuddy/rich_snippets/v1' ) );
return $o;
} );
wp_add_inline_script( 'wpb-rs-admin-position', "var WPB_RS_POS = " . \json_encode( $args ) . ";", 'before' );
}
/**
* Adds the metaboxes.
*
* @since 2.0.0
*/
public function add_metaboxes() {
# the position rules metabox
add_meta_box(
'wp-rs-mb-position',
_x( 'Position', 'metabox title', 'rich-snippets-schema' ),
array( self::$_instance, 'render_position_meta_box' ),
'wpb-rs-global',
'advanced',
'high'
);
}
/**
* Renders the meta box.
*
* @param \WP_Post $post
* @param array $metabox
*
* @since 2.0.0
*
*/
public function render_position_meta_box( $post, $metabox ) {
View::admin_position_metabox( $post );
}
/**
* Returns an array of possible page rules.
*
* @return array
* @since 2.0.0
*
*/
public function get_params() {
$params = array(
array(
'label' => _x( 'Built-in Parameters', 'rich-snippets-schema' ),
'params' => array(
'post_type' => [
'active' => true,
'label' => __( 'Post Type', 'rich-snippets-schema' ),
],
'post' => [
'active' => true,
'label' => __( 'Post, Page, Custom Post Type' ),
],
'post_template' => [
'active' => false,
'label' => __( 'Post Template', 'rich-snippets-schema' ),
],
'post_status' => [
'active' => false,
'label' => __( 'Post Status', 'rich-snippets-schema' ),
],
'post_format' => [
'active' => false,
'label' => __( 'Post Format', 'rich-snippets-schema' ),
],
'post_category' => [
'active' => true,
'label' => __( 'Post Category', 'rich-snippets-schema' ),
],
'child_terms' => [
'active' => false,
'label' => __( 'Child Terms of', 'rich-snippets-schema' ),
],
'post_taxonomy' => [
'active' => false,
'label' => __( 'Post Taxonomy', 'rich-snippets-schema' ),
],
'page_template' => [
'active' => false,
'label' => __( 'Page Template', 'rich-snippets-schema' ),
],
'page_type' => [
'active' => false,
'label' => __( 'Page Type', 'rich-snippets-schema' ),
],
'page_parent' => [
'active' => false,
'label' => __( 'Page Parent', 'rich-snippets-schema' ),
],
'user' => [
'active' => false,
'label' => __( 'User', 'rich-snippets-schema' ),
],
),
),
);
/**
* Custom page rules.
*
* Returns custom page rules.
*
* @hook wpbuddy/rich_snippets/position/custom_params
*
* @param {array} $array Custom params.
*
* @returns {array} Custom params.
*
* @since 2.0.0
*/
$custom_params = apply_filters( 'wpbuddy/rich_snippets/position/custom_params', array() );
if ( count( $custom_params ) > 0 ) {
$prams[] = array(
'label' => _x( 'Custom Parameters', 'Custom page rules for selecting a rule.', 'rich-snippets-schema' ),
'params' => $custom_params,
);
}
/**
* Page rule filter.
*
* Allows to customize possible rules.
*
* @hook wpbuddy/rich_snippets/position/params
*
* @param {array} $params Params.
*
* @returns {array} The params.
*
* @since 2.0.0
*/
return apply_filters( 'wpbuddy/rich_snippets/position/params', $params );
}
/**
* Returns a list of operators.
*
* @return array
* @since 2.0.0
*
*/
public function get_operators() {
$operators = array(
'==' => __( 'is equal to', 'rich-snippets-schema' ),
'!=' => __( 'is not equal to', 'rich-snippets-schema' ),
);
/**
* Position operators filter.
*
* Allows to add or customize the operators used in the position metabox.
*
* @hook wpbuddy/rich_snippets/position/operators
*
* @param {array} $operators A list of operators.
*
* @returns {array} The operator list.
*
* @since 2.0.0
*/
return apply_filters( 'wpbuddy/rich_snippets/position/operators', $operators );
}
/**
* Prints the param dropdown.
*
* @param null|Position_Rule $rule
*
* @since 2.0.0
*
*/
public function print_param_select( $rule = null ) {
?>
<select name="wpb_rs_position_rule[%rule_group%][%rule%][param]">
<?php
foreach ( $this->get_params() as $optgroup ) {
printf( '<optgroup label="%s">', esc_html( $optgroup['label'] ) );
foreach ( $optgroup['params'] as $param_value => $param ) {
printf(
'<option %s value="%s" %s>%s%s</option>',
( $rule instanceof Position_Rule ) ?
selected( $rule->param, $param_value, false ) : '',
esc_attr( $param_value ),
disabled( $param['active'], false, false ),
esc_html( $param['label'] ),
! $param['active'] ? ' ' . __( '(Pro version only)', 'rich-snippets-schema' ) : ''
);
}
echo '</optgroup>';
}
?>
</select>
<?php
}
/**
* Prints the operator dropdown.
*
* @param null|Position_Rule $rule
*
* @since 2.0.0
*
*/
public function print_operator_select( $rule = null ) {
?>
<select name="wpb_rs_position_rule[%rule_group%][%rule%][operator]">
<?php
foreach ( $this->get_operators() as $operator => $operator_label ) {
printf(
'<option %s value="%s">%s</option>',
( $rule instanceof Position_Rule ) ?
selected( $rule->operator, $operator, false ) : '',
esc_attr( $operator ),
esc_html( $operator_label )
);
}
?>
</select>
<?php
}
/**
* Returns possible param selections for a rule.
*
* @param Position_Rule $rule
*
* @return array
* @since 2.14.0
*
*/
public function get_value_select_options( $rule = null ) {
$rule_param = $rule->param ?? 'post_type';
$values = array();
switch ( $rule_param ) {
case 'post_template':
$values = array( 'default' => __( 'Default template', 'rich-snippets-schema' ) );
$all_templates = wp_get_theme()->get_post_templates();
foreach ( $all_templates as $post_type => $templates ) {
$values[ $post_type ]['label'] = call_user_func( function ( $pt ) {
$post_type_obj = get_post_type_object( $pt );
if ( ! $post_type_obj instanceof \WP_Post_Type ) {
return $pt;
}
$labels = get_post_type_labels( $post_type_obj );
return $labels->singular_name;
}, $post_type );
$values[ $post_type ]['values'] = call_user_func( function ( $tpls, $pt ) {
$vals = array();
foreach ( $tpls as $file => $name ) {
$key = sprintf( '%s:%s', $pt, $file );
$vals[ $key ] = $name;
}
return $vals;
}, $templates, $post_type );
}
break;
case 'page_template':
$values = array( 'default' => __( 'Default template', 'rich-snippets-schema' ) );
$all_templates = wp_get_theme()->get_post_templates();
if ( isset( $all_templates['page'] ) ) {
$values = array_merge( $values, $all_templates['page'] );
}
break;
case 'post_status':
global $wp_post_statuses;
if ( ! empty( $wp_post_statuses ) ) {
foreach ( $wp_post_statuses as $status ) {
$values[ $status->name ] = $status->label;
}
}
break;
case 'post_format':
$values = get_post_format_strings();
break;
case 'post_category':
$values = get_categories( array(
'hide_empty' => false,
) );
$values = wp_list_pluck( $values, 'cat_name', 'cat_ID' );
array_walk( $values, function ( &$value, $key ) {
$value = sprintf( '%s (%d)', $value, $key );
} );
break;
case 'child_terms':
case 'post_taxonomy':
$taxonomies = get_taxonomies( false, 'objects' );
$ignore = array( 'nav_menu', 'link_category' );
$values = array();
/**
* @var \WP_Taxonomy $taxonomy
*/
foreach ( $taxonomies as $taxonomy ) {
if ( in_array( $taxonomy->name, $ignore ) ) {
continue;
}
$values[ $taxonomy->name ] = array(
'label' => sprintf(
'%s (%s)',
$taxonomy->label,
$taxonomy->name
),
);
$terms = get_terms( array(
'taxonomy' => $taxonomy->name,
'hide_empty' => false,
) );
if ( is_wp_error( $terms ) ) {
continue;
}
if ( empty( $terms ) ) {
continue;
}
if ( $taxonomy->hierarchical ) {
$terms = _get_term_children( 0, $terms, $taxonomy->name );
}
foreach ( $terms as $term ) {
$k = sprintf( '%s:%d', $taxonomy->name, $term->term_id );
$values[ $taxonomy->name ]['values'][ $k ] = $this->get_term_title( $term );
}
}
break;
case 'user':
# Load at least the selected value
if ( ! empty( $rule->value ) ) {
$user = get_user_by( 'ID', (int) $rule->value );
if ( $user instanceof \WP_User ) {
$values[ $rule->value ] = sprintf(
'%s (user, %d)',
$user->nickname,
$rule->value
);
}
}
break;
case 'post':
case 'page_parent':
# Load at least the selected value
if ( ! empty( $rule->value ) ) {
$values[ $rule->value ] = sprintf(
'%s (%s, %d)',
get_the_title( $rule->value ),
get_post_type( $rule->value ),
$rule->value
);
}
break;
case 'page_type':
$values = array(
'all' => __( "Everything (globally active)", 'rich-snippets-schema' ),
'front_page' => __( "Front Page", 'rich-snippets-schema' ),
'posts_page' => __( "Posts Page", 'rich-snippets-schema' ),
'top_level' => __( "Top Level Page (has no parent)", 'rich-snippets-schema' ),
'parent' => __( "Parent Page (has children)", 'rich-snippets-schema' ),
'child' => __( "Child Page (has parent)", 'rich-snippets-schema' ),
'search' => __( "Search Results Page", 'rich-snippets-schema' ),
'author' => __( "Author Archive", 'rich-snippets-schema' ),
'archive' => __( "All Archive Pages", 'rich-snippets-schema' ),
);
$taxonomies = get_taxonomies( [ 'public' => true ], 'objects' );
foreach ( $taxonomies as $taxonomy ) {
$values[ 'archive_' . $taxonomy->name ] = sprintf(
__( 'Archive page: %s', 'rich-snippets-schema' ),
esc_html( $taxonomy->label )
);
}
$post_types = get_post_types( [ 'has_archive' => true ], 'objects' );
foreach ( $post_types as $post_type ) {
$values[ 'archive_' . $post_type->name ] = sprintf(
__( 'Archive page: %s', 'rich-snippets-schema' ),
esc_html( $post_type->label )
);
}
break;
case 'post_type':
default:
/**
* Value-Select Post-type filter.
*
* Allows to change the post type arguments when fetching post types in the value select.
*
* @hook wpbuddy/rich_snippets/position/values/post_type_args
*
* @param array $post_type_args
*
* @return array Post type arguments.
*
* @since 2.14.0
*/
$post_type_args = apply_filters(
'wpbuddy/rich_snippets/position/values/post_type_args',
array( 'publicly_queryable' => true, )
);
$values = get_post_types(
$post_type_args,
'objects'
);
$values = wp_list_pluck( $values, 'label', 'name' );
/**
* Add page to the list
*/
$post_type = get_post_type_object( 'page' );
if ( $post_type instanceof \WP_Post_Type ) {
$values['page'] = $post_type->label;
}
}
return $values;
}
/**
* Prints the value dropdown.
*
* @param null|Position_Rule $rule
*
* @since 2.0.0
*
*/
public function print_value_select( $rule = null ) {
$rule_param = $rule->param ?? 'post_type';
$rule_value = $rule->value ?? null;
$make_select2 = 'page_parent' === $rule_param || 'post' === $rule_param || 'user' === $rule_param;
$values = $this->get_value_select_options( $rule );
/**
* Position value filter.
*
* This filter can be used to filter position values.
*
* @hook wpbuddy/rich_snippets/position/values
*
* @param {array} $values The current values.
* @param {null|Position_Rule} $rule The current rule.
*
* @returns {array}
*
* @since 2.0.0
*/
$values = apply_filters( 'wpbuddy/rich_snippets/position/values', $values, $rule );
?>
<select name="wpb_rs_position_rule[%rule_group%][%rule%][value]"
data-make_select2="<?php echo absint( $make_select2 ); ?>"
data-param="<?php echo esc_attr( $rule_param ); ?>"
id="<?php echo esc_attr( uniqid() ); ?>">
<?php
foreach ( $values as $value => $label ) {
if ( is_array( $label ) ) {
$sub_values = $label;
if ( ! isset( $sub_values['label'] ) ) {
continue;
}
if ( ! isset( $sub_values['values'] ) ) {
continue;
}
printf(
'<optgroup label="%s">',
esc_attr( $sub_values['label'] )
);
foreach ( $sub_values['values'] as $sub_value => $sub_label ) {
printf(
'<option %s value="%s">%s</option>',
selected( $rule_value, $sub_value, false ),
esc_attr( $sub_value ),
esc_html( $sub_label )
);
}
print( '</optgroup>' );
continue;
}
printf(
'<option %s value="%s">%s</option>',
selected( $rule_value, $value, false ),
esc_attr( $value ),
esc_html( $label )
);
}
?>
</select>
<?php
}
/**
* Prints a rule row.
*
* @param null|Position_Rule $rule
*
* @since 2.0.0
*
*/
public function print_rule_row( $rule = null ) {
?>
<tr class="wpb-rs-rule" data-rule_row="<?php echo esc_attr( $this->_current_rule_row_no ); ?>">
<td class="wpb-rs-param">
<?php $this->print_param_select( $rule ); ?>
</td>
<td class="wpb-rs-operator">
<?php $this->print_operator_select( $rule ); ?>
</td>
<td class="wpb-rs-value">
<?php $this->print_value_select( $rule ); ?>
</td>
<td class="wpb-rs-rule-action wpb-rs-rule-add">
<a href="#" class="button"><?php
echo esc_html_x( 'and', 'Local AND when adding a new position rule', 'rich-snippets-schema' );
?></a>
</td>
<td class="wpb-rs-rule-action wpb-rs-rule-remove">
<a href="#" class="button"><span class="dashicons dashicons-no-alt"></span></a>
</td>
</tr>
<?php
$this->_current_rule_row_no ++;
}
/**
* Saves all position rules.
*
* @param int $post_id
*
* @since 2.0.0
*
*/
public function save_positions( $post_id ) {
if ( ! isset( $_POST['wpb_rs_position_rule'] ) ) {
return;
}
if ( ! is_array( $_POST['wpb_rs_position_rule'] ) ) {
return;
}
# clear rule cache
if ( 'wpb-rs-global' === get_post_type( $post_id ) ) {
Cache_Model::clear_singular_rule( $post_id );
}
$ruleset = Rules_Model::sanitize_and_convert_to_ruleset( $_POST['wpb_rs_position_rule'] );
Rules_Model::update_ruleset( $post_id, $ruleset );
}
/**
* Prints a rule group break
*
* @since 2.0.0
*/
public function print_group_break() {
?>
<tr class="wpb-rs-rule-group-break">
<td colspan="4">
<?php echo esc_html_x( 'or', 'logical OR when defining a ruleset', 'rich-snippets-schema' ); ?>
</td>
<td class="wpb-rs-rule-action wpb-rs-rulegroup-remove">
<a href="#" class="button"><span class="dashicons dashicons-no-alt"></span></a>
</td>
</tr>
<?php
}
/**
* Returns the term title for a select box.
*
* @param \WP_Term $term
*
* @return string
* @since 2.0.0
*
*/
private function get_term_title( $term ) {
$title = $term->name;
if ( empty( $title ) ) {
$title = _x( '(no title)', 'term title', 'rich-snippets-schema' );
}
if ( is_taxonomy_hierarchical( $term->taxonomy ) ) {
$ancestors = get_ancestors( $term->term_id, $term->taxonomy );
$title = str_repeat( '- ', count( $ancestors ) ) . $title;
}
return $title;
}
}