2480 lines
71 KiB
PHP
2480 lines
71 KiB
PHP
<?php
|
|
/**
|
|
* Advanced Conditions Rest API functions
|
|
*
|
|
* @package GenerateBlocks
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
require_once GENERATEBLOCKS_PRO_DIR . 'includes/utils/trait-usage-search.php';
|
|
|
|
/**
|
|
* Class GenerateBlocks_Pro_Conditions_REST
|
|
*/
|
|
class GenerateBlocks_Pro_Conditions_REST extends WP_REST_Controller {
|
|
|
|
use GenerateBlocks_Pro_Usage_Search;
|
|
/**
|
|
* Instance.
|
|
*
|
|
* @access private
|
|
* @var object Instance
|
|
*/
|
|
private static $instance;
|
|
|
|
/**
|
|
* Namespace.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $namespace = 'generateblocks-pro/advanced-conditions/v';
|
|
|
|
/**
|
|
* Version.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $version = '1';
|
|
|
|
/**
|
|
* Initiator.
|
|
*
|
|
* @return object initialized object of class.
|
|
*/
|
|
public static function get_instance() {
|
|
if ( ! isset( self::$instance ) ) {
|
|
self::$instance = new self();
|
|
}
|
|
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
public function __construct() {
|
|
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
|
|
}
|
|
|
|
/**
|
|
* Register rest routes.
|
|
*/
|
|
public function register_routes() {
|
|
$namespace = $this->namespace . $this->version;
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/get_condition_types/',
|
|
[
|
|
'methods' => WP_REST_Server::READABLE,
|
|
'callback' => [ $this, 'get_condition_types' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/get_condition_categories/',
|
|
[
|
|
'methods' => WP_REST_Server::READABLE,
|
|
'callback' => [ $this, 'get_condition_categories' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/manage_category/',
|
|
[
|
|
'methods' => WP_REST_Server::CREATABLE,
|
|
'callback' => [ $this, 'create_category' ],
|
|
'permission_callback' => [ $this, 'manage_permission' ],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/manage_category/(?P<id>[\d]+)',
|
|
[
|
|
'methods' => WP_REST_Server::EDITABLE,
|
|
'callback' => [ $this, 'update_category' ],
|
|
'permission_callback' => [ $this, 'manage_permission' ],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/manage_category/(?P<id>[\d]+)',
|
|
[
|
|
'methods' => WP_REST_Server::DELETABLE,
|
|
'callback' => [ $this, 'delete_category' ],
|
|
'permission_callback' => [ $this, 'manage_permission' ],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/get_condition_rules/',
|
|
[
|
|
'methods' => WP_REST_Server::READABLE,
|
|
'callback' => [ $this, 'get_condition_rules' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
'args' => [
|
|
'type' => [
|
|
'required' => true,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/get_condition_operators/',
|
|
[
|
|
'methods' => WP_REST_Server::READABLE,
|
|
'callback' => [ $this, 'get_condition_operators' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
'args' => [
|
|
'type' => [
|
|
'required' => true,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
'rule' => [
|
|
'required' => false,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/search_posts/',
|
|
[
|
|
'methods' => WP_REST_Server::READABLE,
|
|
'callback' => [ $this, 'search_posts' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
'args' => [
|
|
'post_type' => [
|
|
'required' => true,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
'search' => [
|
|
'required' => false,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
'id' => [
|
|
'required' => false,
|
|
'type' => 'integer',
|
|
'sanitize_callback' => 'absint',
|
|
],
|
|
],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/search_terms/',
|
|
[
|
|
'methods' => WP_REST_Server::READABLE,
|
|
'callback' => [ $this, 'search_terms' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
'args' => [
|
|
'taxonomy' => [
|
|
'required' => true,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
'search' => [
|
|
'required' => false,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
'id' => [
|
|
'required' => false,
|
|
'type' => 'integer',
|
|
'sanitize_callback' => 'absint',
|
|
],
|
|
],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/validate_conditions/',
|
|
[
|
|
'methods' => WP_REST_Server::CREATABLE,
|
|
'callback' => [ $this, 'validate_conditions' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
'args' => [
|
|
'conditions' => [
|
|
'required' => true,
|
|
'type' => 'object',
|
|
],
|
|
],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/get_rule_metadata/',
|
|
[
|
|
'methods' => WP_REST_Server::READABLE,
|
|
'callback' => [ $this, 'get_rule_metadata' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
'args' => [
|
|
'type' => [
|
|
'required' => true,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
'rule' => [
|
|
'required' => true,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/batch_object_titles/',
|
|
[
|
|
'methods' => WP_REST_Server::CREATABLE,
|
|
'callback' => [ $this, 'batch_object_titles' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
'args' => [
|
|
'requests' => [
|
|
'required' => true,
|
|
'type' => 'array',
|
|
],
|
|
],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/conditions/',
|
|
[
|
|
[
|
|
'methods' => WP_REST_Server::READABLE,
|
|
'callback' => [ $this, 'get_conditions' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
'args' => [
|
|
'page' => [
|
|
'required' => false,
|
|
'type' => 'integer',
|
|
'default' => 1,
|
|
'sanitize_callback' => 'absint',
|
|
],
|
|
'per_page' => [
|
|
'required' => false,
|
|
'type' => 'integer',
|
|
'default' => 10,
|
|
'sanitize_callback' => 'absint',
|
|
],
|
|
],
|
|
],
|
|
[
|
|
'methods' => WP_REST_Server::CREATABLE,
|
|
'callback' => [ $this, 'create_condition' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
'args' => [
|
|
'title' => [
|
|
'required' => true,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
'conditions' => [
|
|
'required' => false,
|
|
'type' => 'object',
|
|
'default' => [
|
|
'logic' => 'OR',
|
|
'groups' => [],
|
|
],
|
|
],
|
|
'status' => [
|
|
'required' => false,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
'default' => 'publish',
|
|
'enum' => [ 'publish', 'draft' ],
|
|
],
|
|
'category' => [
|
|
'required' => false,
|
|
'type' => [ 'array', 'string' ],
|
|
],
|
|
],
|
|
],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/conditions/(?P<id>\d+)',
|
|
[
|
|
[
|
|
'methods' => WP_REST_Server::READABLE,
|
|
'callback' => [ $this, 'get_condition' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
'args' => [
|
|
'id' => [
|
|
'required' => true,
|
|
'type' => 'integer',
|
|
'sanitize_callback' => 'absint',
|
|
],
|
|
],
|
|
],
|
|
[
|
|
'methods' => WP_REST_Server::EDITABLE,
|
|
'callback' => [ $this, 'update_condition' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
'args' => [
|
|
'id' => [
|
|
'required' => true,
|
|
'type' => 'integer',
|
|
'sanitize_callback' => 'absint',
|
|
],
|
|
'title' => [
|
|
'required' => false,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
'conditions' => [
|
|
'required' => false,
|
|
'type' => 'object',
|
|
],
|
|
'status' => [
|
|
'required' => false,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
],
|
|
],
|
|
[
|
|
'methods' => WP_REST_Server::DELETABLE,
|
|
'callback' => [ $this, 'delete_condition' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
'args' => [
|
|
'id' => [
|
|
'required' => true,
|
|
'type' => 'integer',
|
|
'sanitize_callback' => 'absint',
|
|
],
|
|
'force' => [
|
|
'required' => false,
|
|
'type' => 'boolean',
|
|
'default' => false,
|
|
],
|
|
],
|
|
],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/search_hierarchical_posts/',
|
|
[
|
|
'methods' => WP_REST_Server::READABLE,
|
|
'callback' => [ $this, 'search_hierarchical_posts' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
'args' => [
|
|
'search' => [
|
|
'required' => false,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
'id' => [
|
|
'required' => false,
|
|
'type' => 'integer',
|
|
'sanitize_callback' => 'absint',
|
|
],
|
|
],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/get_day_options/',
|
|
[
|
|
'methods' => WP_REST_Server::READABLE,
|
|
'callback' => [ $this, 'get_day_options' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/conditions/(?P<id>\d+)/usage',
|
|
[
|
|
'methods' => WP_REST_Server::READABLE,
|
|
'callback' => [ $this, 'get_condition_usage' ],
|
|
'permission_callback' => [ $this, 'manage_permission' ],
|
|
'args' => [
|
|
'id' => [
|
|
'required' => true,
|
|
'type' => 'integer',
|
|
'sanitize_callback' => 'absint',
|
|
],
|
|
'limit' => [
|
|
'required' => false,
|
|
'type' => 'integer',
|
|
'default' => 50,
|
|
'sanitize_callback' => 'absint',
|
|
],
|
|
'refresh' => [
|
|
'required' => false,
|
|
'type' => 'boolean',
|
|
'default' => false,
|
|
'sanitize_callback' => 'rest_sanitize_boolean',
|
|
],
|
|
],
|
|
]
|
|
);
|
|
|
|
register_rest_route(
|
|
$namespace,
|
|
'/search_users/',
|
|
[
|
|
'methods' => WP_REST_Server::READABLE,
|
|
'callback' => [ $this, 'search_users' ],
|
|
'permission_callback' => [ $this, 'edit_posts_permission' ],
|
|
'args' => [
|
|
'search' => [
|
|
'required' => false,
|
|
'type' => 'string',
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
],
|
|
'id' => [
|
|
'required' => false,
|
|
'type' => 'integer',
|
|
'sanitize_callback' => 'absint',
|
|
],
|
|
],
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Batch object titles endpoint
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function batch_object_titles( WP_REST_Request $request ) {
|
|
$requests = $request->get_param( 'requests' );
|
|
|
|
if ( empty( $requests ) || ! is_array( $requests ) ) {
|
|
return $this->error( 'invalid_requests', __( 'Invalid requests parameter.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
$results = [];
|
|
|
|
// Group requests by type for efficient batch processing.
|
|
$grouped_requests = [
|
|
'posts' => [],
|
|
'terms' => [],
|
|
'hierarchical_posts' => [],
|
|
'days' => [],
|
|
];
|
|
|
|
foreach ( $requests as $request_item ) {
|
|
if ( ! isset( $request_item['type'], $request_item['rule'], $request_item['value'] ) ) {
|
|
continue;
|
|
}
|
|
|
|
$type = sanitize_text_field( $request_item['type'] );
|
|
$rule = sanitize_text_field( $request_item['rule'] );
|
|
$value = sanitize_text_field( $request_item['value'] );
|
|
$cache_key = "{$type}_{$rule}_{$value}";
|
|
|
|
// Handle day of week specially.
|
|
if ( 'date_time' === $type && 'day_of_week' === $rule ) {
|
|
$grouped_requests['days'][] = [
|
|
'cache_key' => $cache_key,
|
|
'value' => $value,
|
|
];
|
|
continue;
|
|
}
|
|
|
|
// Handle hierarchical posts.
|
|
if ( in_array( $rule, [ 'child_of', 'parent_of' ], true ) ) {
|
|
$grouped_requests['hierarchical_posts'][] = [
|
|
'cache_key' => $cache_key,
|
|
'value' => $value,
|
|
];
|
|
continue;
|
|
}
|
|
|
|
// Handle post types.
|
|
if ( 0 === strpos( $rule, 'post:' ) ) {
|
|
$post_type = str_replace( 'post:', '', $rule );
|
|
$grouped_requests['posts'][] = [
|
|
'cache_key' => $cache_key,
|
|
'post_type' => $post_type,
|
|
'value' => $value,
|
|
];
|
|
continue;
|
|
}
|
|
|
|
// Handle taxonomies.
|
|
if ( 0 === strpos( $rule, 'taxonomy:' ) || 0 === strpos( $rule, 'post_terms:' ) ) {
|
|
$taxonomy = str_replace( [ 'taxonomy:', 'post_terms:' ], '', $rule );
|
|
$grouped_requests['terms'][] = [
|
|
'cache_key' => $cache_key,
|
|
'taxonomy' => $taxonomy,
|
|
'value' => $value,
|
|
];
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Process days.
|
|
foreach ( $grouped_requests['days'] as $day_request ) {
|
|
$results[ $day_request['cache_key'] ] = $this->get_day_name( $day_request['value'] );
|
|
}
|
|
|
|
// Process posts in batches.
|
|
if ( ! empty( $grouped_requests['posts'] ) ) {
|
|
$post_results = $this->batch_load_post_titles( $grouped_requests['posts'] );
|
|
$results = array_merge( $results, $post_results );
|
|
}
|
|
|
|
// Process terms in batches.
|
|
if ( ! empty( $grouped_requests['terms'] ) ) {
|
|
$term_results = $this->batch_load_term_names( $grouped_requests['terms'] );
|
|
$results = array_merge( $results, $term_results );
|
|
}
|
|
|
|
// Process hierarchical posts in batches.
|
|
if ( ! empty( $grouped_requests['hierarchical_posts'] ) ) {
|
|
$hierarchical_results = $this->batch_load_hierarchical_post_titles( $grouped_requests['hierarchical_posts'] );
|
|
$results = array_merge( $results, $hierarchical_results );
|
|
}
|
|
|
|
return $this->success( $results );
|
|
}
|
|
|
|
/**
|
|
* Batch load post titles
|
|
*
|
|
* @param array $requests Post requests.
|
|
* @return array
|
|
*/
|
|
private function batch_load_post_titles( $requests ) {
|
|
$results = [];
|
|
$posts_by_type = [];
|
|
|
|
// Group by post type.
|
|
foreach ( $requests as $request ) {
|
|
$post_type = $request['post_type'];
|
|
if ( ! isset( $posts_by_type[ $post_type ] ) ) {
|
|
$posts_by_type[ $post_type ] = [];
|
|
}
|
|
$posts_by_type[ $post_type ][] = $request;
|
|
}
|
|
|
|
// Query each post type.
|
|
foreach ( $posts_by_type as $post_type => $type_requests ) {
|
|
$post_type_object = get_post_type_object( $post_type );
|
|
|
|
if ( ! $post_type_object || ! $post_type_object->public ) {
|
|
foreach ( $type_requests as $request ) {
|
|
$results[ $request['cache_key'] ] = 'ID: ' . intval( $request['value'] );
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
$post_ids = array_column( $type_requests, 'value' );
|
|
$post_ids = array_map( 'intval', $post_ids );
|
|
$post_ids = array_unique( $post_ids );
|
|
|
|
if ( empty( $post_ids ) ) {
|
|
continue;
|
|
}
|
|
|
|
$posts = get_posts(
|
|
[
|
|
'post_type' => $post_type,
|
|
'post__in' => $post_ids,
|
|
'post_status' => $this->get_readable_post_statuses( $post_type ),
|
|
'posts_per_page' => -1,
|
|
]
|
|
);
|
|
|
|
$posts_by_id = [];
|
|
foreach ( $posts as $post ) {
|
|
if ( ! current_user_can( 'read_post', $post->ID ) ) {
|
|
continue;
|
|
}
|
|
|
|
$posts_by_id[ $post->ID ] = $post;
|
|
}
|
|
|
|
// Map results back to cache keys.
|
|
foreach ( $type_requests as $request ) {
|
|
$post_id = intval( $request['value'] );
|
|
if ( isset( $posts_by_id[ $post_id ] ) ) {
|
|
$post = $posts_by_id[ $post_id ];
|
|
$title = $post->post_title ? $post->post_title : sprintf(
|
|
/* translators: %1$s: post type, %2$d: post ID */
|
|
__( '%1$s #%2$d', 'generateblocks-pro' ),
|
|
$post_type_object->labels->singular_name,
|
|
$post->ID
|
|
);
|
|
$results[ $request['cache_key'] ] = $title;
|
|
} else {
|
|
$results[ $request['cache_key'] ] = "ID: {$post_id}";
|
|
}
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Batch load term names
|
|
*
|
|
* @param array $requests Term requests.
|
|
* @return array
|
|
*/
|
|
private function batch_load_term_names( $requests ) {
|
|
$results = [];
|
|
$terms_by_taxonomy = [];
|
|
|
|
// Group by taxonomy.
|
|
foreach ( $requests as $request ) {
|
|
$taxonomy = $request['taxonomy'];
|
|
if ( ! isset( $terms_by_taxonomy[ $taxonomy ] ) ) {
|
|
$terms_by_taxonomy[ $taxonomy ] = [];
|
|
}
|
|
$terms_by_taxonomy[ $taxonomy ][] = $request;
|
|
}
|
|
|
|
// Query each taxonomy.
|
|
foreach ( $terms_by_taxonomy as $taxonomy => $taxonomy_requests ) {
|
|
$term_ids = array_column( $taxonomy_requests, 'value' );
|
|
$term_ids = array_map( 'intval', $term_ids );
|
|
$term_ids = array_unique( $term_ids );
|
|
|
|
if ( empty( $term_ids ) ) {
|
|
continue;
|
|
}
|
|
|
|
$terms = get_terms(
|
|
[
|
|
'taxonomy' => $taxonomy,
|
|
'include' => $term_ids,
|
|
'hide_empty' => false,
|
|
]
|
|
);
|
|
|
|
if ( is_wp_error( $terms ) ) {
|
|
continue;
|
|
}
|
|
|
|
$terms_by_id = [];
|
|
foreach ( $terms as $term ) {
|
|
$terms_by_id[ $term->term_id ] = $term;
|
|
}
|
|
|
|
// Map results back to cache keys.
|
|
foreach ( $taxonomy_requests as $request ) {
|
|
$term_id = intval( $request['value'] );
|
|
if ( isset( $terms_by_id[ $term_id ] ) ) {
|
|
$results[ $request['cache_key'] ] = $terms_by_id[ $term_id ]->name;
|
|
} else {
|
|
$results[ $request['cache_key'] ] = "ID: {$term_id}";
|
|
}
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Batch load hierarchical post titles
|
|
*
|
|
* @param array $requests Hierarchical post requests.
|
|
* @return array
|
|
*/
|
|
private function batch_load_hierarchical_post_titles( $requests ) {
|
|
$results = [];
|
|
$post_ids = array_column( $requests, 'value' );
|
|
$post_ids = array_map( 'intval', $post_ids );
|
|
$post_ids = array_unique( $post_ids );
|
|
|
|
if ( empty( $post_ids ) ) {
|
|
return $results;
|
|
}
|
|
|
|
// Get hierarchical post types.
|
|
$hierarchical_types = get_post_types(
|
|
[
|
|
'public' => true,
|
|
'hierarchical' => true,
|
|
],
|
|
'names'
|
|
);
|
|
|
|
if ( empty( $hierarchical_types ) ) {
|
|
return $results;
|
|
}
|
|
|
|
$posts = get_posts(
|
|
[
|
|
'post_type' => array_values( $hierarchical_types ),
|
|
'post__in' => $post_ids,
|
|
'post_status' => $this->get_readable_post_statuses( array_values( $hierarchical_types ), false ),
|
|
'posts_per_page' => -1,
|
|
]
|
|
);
|
|
|
|
$posts_by_id = [];
|
|
foreach ( $posts as $post ) {
|
|
if ( ! current_user_can( 'read_post', $post->ID ) ) {
|
|
continue;
|
|
}
|
|
|
|
$ancestors = get_post_ancestors( $post->ID );
|
|
$hierarchy_indicator = str_repeat( '— ', count( $ancestors ) );
|
|
$title = $hierarchy_indicator . ( $post->post_title ? $post->post_title : sprintf(
|
|
/* translators: %1$s: post type, %2$d: post ID */
|
|
__( '%1$s #%2$d', 'generateblocks-pro' ),
|
|
get_post_type_object( $post->post_type )->labels->singular_name,
|
|
$post->ID
|
|
) );
|
|
$posts_by_id[ $post->ID ] = $title;
|
|
}
|
|
|
|
// Map results back to cache keys.
|
|
foreach ( $requests as $request ) {
|
|
$post_id = intval( $request['value'] );
|
|
if ( isset( $posts_by_id[ $post_id ] ) ) {
|
|
// Strip hierarchy indicators for display.
|
|
$clean_title = preg_replace( '/^(—\s*)+/', '', $posts_by_id[ $post_id ] );
|
|
$results[ $request['cache_key'] ] = $clean_title;
|
|
} else {
|
|
$results[ $request['cache_key'] ] = "ID: {$post_id}";
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Get day name
|
|
*
|
|
* @param string $day_number Day number.
|
|
* @return string
|
|
*/
|
|
private function get_day_name( $day_number ) {
|
|
$days = [
|
|
'1' => __( 'Monday', 'generateblocks-pro' ),
|
|
'2' => __( 'Tuesday', 'generateblocks-pro' ),
|
|
'3' => __( 'Wednesday', 'generateblocks-pro' ),
|
|
'4' => __( 'Thursday', 'generateblocks-pro' ),
|
|
'5' => __( 'Friday', 'generateblocks-pro' ),
|
|
'6' => __( 'Saturday', 'generateblocks-pro' ),
|
|
'7' => __( 'Sunday', 'generateblocks-pro' ),
|
|
];
|
|
|
|
return $days[ $day_number ] ?? $day_number;
|
|
}
|
|
|
|
/**
|
|
* Enhanced but safe permission callback.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function edit_posts_permission() {
|
|
// For modifying operations, check manage permission.
|
|
$request_method = $_SERVER['REQUEST_METHOD'] ?? '';
|
|
|
|
if ( in_array( $request_method, [ 'POST', 'PUT', 'PATCH', 'DELETE' ], true ) ) {
|
|
// Creating, updating, or deleting requires manage permission.
|
|
return GenerateBlocks_Pro_Conditions::current_user_can_use_conditions( 'manage' );
|
|
}
|
|
|
|
// Reading/listing only requires use permission.
|
|
return GenerateBlocks_Pro_Conditions::current_user_can_use_conditions( 'use' );
|
|
}
|
|
|
|
/**
|
|
* Permission callback for category management operations.
|
|
* Categories should only be manageable by administrators.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function manage_permission() {
|
|
return GenerateBlocks_Pro_Conditions::current_user_can_use_conditions( 'manage' );
|
|
}
|
|
|
|
/**
|
|
* Verify nonce for state-changing operations.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return bool|WP_Error True if valid, WP_Error if invalid.
|
|
*/
|
|
private function verify_nonce( WP_REST_Request $request ) {
|
|
$nonce = $request->get_header( 'X-WP-Nonce' );
|
|
|
|
if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
|
|
return new WP_Error(
|
|
'rest_cookie_invalid_nonce',
|
|
__( 'Cookie check failed.', 'generateblocks-pro' ),
|
|
[ 'status' => 403 ]
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get all condition types.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function get_condition_types( WP_REST_Request $request ) {
|
|
$types = GenerateBlocks_Pro_Conditions::get_condition_types();
|
|
|
|
$formatted_types = [];
|
|
foreach ( $types as $key => $type ) {
|
|
$formatted_types[ $key ] = [
|
|
'label' => $type['label'],
|
|
'operators' => $type['operators'],
|
|
];
|
|
}
|
|
|
|
return $this->success( $formatted_types );
|
|
}
|
|
|
|
/**
|
|
* Get all condition categories.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function get_condition_categories( WP_REST_Request $request ) {
|
|
$categories = get_terms(
|
|
[
|
|
'taxonomy' => 'gblocks_condition_cat',
|
|
'hide_empty' => false,
|
|
]
|
|
);
|
|
|
|
if ( is_wp_error( $categories ) ) {
|
|
return $this->error( 'taxonomy_error', __( 'Could not retrieve categories.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
$formatted_categories = [];
|
|
foreach ( $categories as $category ) {
|
|
$formatted_categories[ $category->slug ] = [
|
|
'slug' => $category->slug,
|
|
'name' => $category->name,
|
|
'count' => $category->count,
|
|
];
|
|
}
|
|
|
|
return $this->success( $formatted_categories );
|
|
}
|
|
|
|
/**
|
|
* Get rules for a specific condition type.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function get_condition_rules( WP_REST_Request $request ) {
|
|
$type = $request->get_param( 'type' );
|
|
|
|
if ( empty( $type ) ) {
|
|
return $this->error( 'missing_type', __( 'Condition type is required.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
$rules = GenerateBlocks_Pro_Conditions::get_condition_rules( $type );
|
|
|
|
return $this->success( $rules );
|
|
}
|
|
|
|
/**
|
|
* Get operators for a specific condition type.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function get_condition_operators( WP_REST_Request $request ) {
|
|
$type = $request->get_param( 'type' );
|
|
$rule = $request->get_param( 'rule' );
|
|
|
|
if ( empty( $type ) ) {
|
|
return $this->error( 'missing_type', __( 'Condition type is required.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
$operators = [];
|
|
|
|
// Get the condition instance to check for rule-specific operators.
|
|
$instance = GenerateBlocks_Pro_Conditions_Registry::get_instance( $type );
|
|
|
|
if ( $instance ) {
|
|
// Try to get rule-specific operators if rule is provided and method exists.
|
|
if ( ! empty( $rule ) && method_exists( $instance, 'get_operators_for_rule' ) ) {
|
|
$operators = $instance->get_operators_for_rule( $rule );
|
|
}
|
|
}
|
|
|
|
// Fallback to all operators for the type if no rule-specific operators found.
|
|
if ( empty( $operators ) ) {
|
|
$types = GenerateBlocks_Pro_Conditions::get_condition_types();
|
|
if ( isset( $types[ $type ] ) ) {
|
|
$operators = $types[ $type ]['operators'];
|
|
}
|
|
}
|
|
|
|
if ( empty( $operators ) ) {
|
|
return $this->error( 'no_operators', __( 'No operators found for this condition type.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
$formatted_operators = [];
|
|
foreach ( $operators as $operator ) {
|
|
$formatted_operators[ $operator ] = $this->get_operator_label( $operator );
|
|
}
|
|
|
|
return $this->success( $formatted_operators );
|
|
}
|
|
|
|
/**
|
|
* Get post statuses that are safe to query for the current user.
|
|
*
|
|
* Results are still filtered with current_user_can( 'read_post' ) before
|
|
* being returned, so draft/private objects are never exposed by status alone.
|
|
*
|
|
* @param string|array $post_types Post type name or names.
|
|
* @param bool $include_drafts Whether draft posts can be queried.
|
|
* @return array
|
|
*/
|
|
private function get_readable_post_statuses( $post_types, $include_drafts = true ) {
|
|
$post_types = array_filter( array_map( 'sanitize_key', (array) $post_types ), 'post_type_exists' );
|
|
$statuses = [ 'publish' ];
|
|
|
|
foreach ( $post_types as $post_type ) {
|
|
$post_type_object = get_post_type_object( $post_type );
|
|
|
|
if ( ! $post_type_object ) {
|
|
continue;
|
|
}
|
|
|
|
if ( current_user_can( $post_type_object->cap->read_private_posts ) ) {
|
|
$statuses[] = 'private';
|
|
}
|
|
|
|
if ( $include_drafts && current_user_can( $post_type_object->cap->edit_posts ) ) {
|
|
$statuses[] = 'draft';
|
|
}
|
|
}
|
|
|
|
return array_values( array_unique( $statuses ) );
|
|
}
|
|
|
|
/**
|
|
* Search posts for a specific post type.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function search_posts( WP_REST_Request $request ) {
|
|
$post_type = $request->get_param( 'post_type' );
|
|
$search = $request->get_param( 'search' );
|
|
$id = $request->get_param( 'id' );
|
|
|
|
if ( empty( $post_type ) ) {
|
|
return $this->error( 'missing_post_type', __( 'Post type is required.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
$post_type_object = get_post_type_object( $post_type );
|
|
|
|
if ( ! $post_type_object || ! $post_type_object->public ) {
|
|
return $this->success( [] );
|
|
}
|
|
|
|
// Handle ID search.
|
|
if ( ! empty( $id ) ) {
|
|
$post = get_post( $id );
|
|
if ( $post && $post_type === $post->post_type && current_user_can( 'read_post', $post->ID ) ) {
|
|
// Exclude the Posts Page from page results.
|
|
if ( 'page' === $post_type ) {
|
|
$page_for_posts = get_option( 'page_for_posts' );
|
|
if ( $page_for_posts && intval( $page_for_posts ) === $post->ID ) {
|
|
return $this->success( [] );
|
|
}
|
|
}
|
|
|
|
$formatted_posts = [
|
|
[
|
|
'id' => $post->ID,
|
|
'title' => $post->post_title ? $post->post_title : sprintf(
|
|
/* translators: %1$s: post type, %2$d: post ID */
|
|
__( '%1$s #%2$d', 'generateblocks-pro' ),
|
|
$post_type_object->labels->singular_name,
|
|
$post->ID
|
|
),
|
|
],
|
|
];
|
|
return $this->success( $formatted_posts );
|
|
} else {
|
|
return $this->success( [] );
|
|
}
|
|
}
|
|
|
|
$args = [
|
|
'post_type' => $post_type,
|
|
'post_status' => $this->get_readable_post_statuses( $post_type ),
|
|
'posts_per_page' => empty( $search ) ? 10 : 50, // phpcs:ignore -- WordPress.WP.PostsPerPage.posts_per_page
|
|
'orderby' => empty( $search ) ? 'date' : 'title',
|
|
'order' => empty( $search ) ? 'DESC' : 'ASC',
|
|
];
|
|
|
|
if ( ! empty( $search ) ) {
|
|
$args['s'] = $search;
|
|
}
|
|
|
|
// Exclude the Posts Page from page results.
|
|
if ( 'page' === $post_type ) {
|
|
$page_for_posts = get_option( 'page_for_posts' );
|
|
if ( $page_for_posts ) {
|
|
$args['post__not_in'] = [ $page_for_posts ];
|
|
}
|
|
}
|
|
|
|
$posts = get_posts( $args );
|
|
|
|
$formatted_posts = [];
|
|
foreach ( $posts as $post ) {
|
|
if ( ! current_user_can( 'read_post', $post->ID ) ) {
|
|
continue;
|
|
}
|
|
|
|
$formatted_posts[] = [
|
|
'id' => $post->ID,
|
|
// translators: %1$s is the post type singular name, %2$d is the post ID.
|
|
'title' => $post->post_title ? $post->post_title : sprintf( __( '%1$s #%2$d', 'generateblocks-pro' ), $post_type_object->labels->singular_name, $post->ID ),
|
|
];
|
|
}
|
|
|
|
return $this->success( $formatted_posts );
|
|
}
|
|
|
|
/**
|
|
* Search terms for a specific taxonomy.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function search_terms( WP_REST_Request $request ) {
|
|
$taxonomy = $request->get_param( 'taxonomy' );
|
|
$search = $request->get_param( 'search' );
|
|
$id = $request->get_param( 'id' );
|
|
|
|
if ( empty( $taxonomy ) ) {
|
|
return $this->error( 'missing_taxonomy', __( 'Taxonomy is required.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
// Handle ID search.
|
|
if ( ! empty( $id ) ) {
|
|
$term = get_term( $id, $taxonomy );
|
|
if ( ! is_wp_error( $term ) && $term ) {
|
|
$formatted_terms = [
|
|
[
|
|
'id' => $term->term_id,
|
|
'name' => $term->name,
|
|
],
|
|
];
|
|
return $this->success( $formatted_terms );
|
|
} else {
|
|
return $this->success( [] );
|
|
}
|
|
}
|
|
|
|
$args = [
|
|
'taxonomy' => $taxonomy,
|
|
'hide_empty' => false,
|
|
'number' => empty( $search ) ? 10 : 50, // Recent items vs search results.
|
|
'orderby' => empty( $search ) ? 'count' : 'name',
|
|
'order' => empty( $search ) ? 'DESC' : 'ASC',
|
|
];
|
|
|
|
if ( ! empty( $search ) ) {
|
|
$args['search'] = $search;
|
|
}
|
|
|
|
$terms = get_terms( $args );
|
|
|
|
if ( is_wp_error( $terms ) ) {
|
|
return $this->error( 'terms_error', $terms->get_error_message() );
|
|
}
|
|
|
|
$formatted_terms = [];
|
|
foreach ( $terms as $term ) {
|
|
$formatted_terms[] = [
|
|
'id' => $term->term_id,
|
|
'name' => $term->name,
|
|
];
|
|
}
|
|
|
|
return $this->success( $formatted_terms );
|
|
}
|
|
|
|
/**
|
|
* Validate conditions structure.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function validate_conditions( WP_REST_Request $request ) {
|
|
$conditions = $request->get_param( 'conditions' );
|
|
|
|
$validation_result = $this->validate_conditions_structure( $conditions );
|
|
|
|
if ( true === $validation_result ) {
|
|
return $this->success( [ 'valid' => true ] );
|
|
}
|
|
|
|
return $this->error( 'validation_failed', $validation_result );
|
|
}
|
|
|
|
/**
|
|
* Get metadata for a specific rule.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function get_rule_metadata( WP_REST_Request $request ) {
|
|
$type = $request->get_param( 'type' );
|
|
$rule = $request->get_param( 'rule' );
|
|
|
|
if ( empty( $type ) || empty( $rule ) ) {
|
|
return $this->error( 'missing_parameters', __( 'Both type and rule parameters are required.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
$metadata = GenerateBlocks_Pro_Conditions::get_rule_metadata( $type, $rule );
|
|
|
|
return $this->success( $metadata );
|
|
}
|
|
|
|
/**
|
|
* Get all conditions.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function get_conditions( WP_REST_Request $request ) {
|
|
$page = $request->get_param( 'page' );
|
|
$per_page = $request->get_param( 'per_page' );
|
|
$post_statuses = GenerateBlocks_Pro_Conditions::current_user_can_use_conditions( 'manage' )
|
|
? [ 'publish', 'draft' ]
|
|
: [ 'publish' ];
|
|
|
|
$args = [
|
|
'post_type' => 'gblocks_condition',
|
|
'post_status' => $post_statuses,
|
|
'posts_per_page' => $per_page,
|
|
'paged' => $page,
|
|
'orderby' => 'date',
|
|
'order' => 'DESC',
|
|
];
|
|
|
|
$query = new WP_Query( $args );
|
|
$posts = $query->posts;
|
|
|
|
$conditions = [];
|
|
foreach ( $posts as $post ) {
|
|
$conditions[] = $this->format_condition_response( $post );
|
|
}
|
|
|
|
$response = [
|
|
'conditions' => $conditions,
|
|
'total' => $query->found_posts,
|
|
'pages' => $query->max_num_pages,
|
|
];
|
|
|
|
return $this->success( $response );
|
|
}
|
|
|
|
/**
|
|
* Get a single condition.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function get_condition( WP_REST_Request $request ) {
|
|
$id = $request->get_param( 'id' );
|
|
|
|
$post = get_post( $id );
|
|
|
|
if ( ! $post || 'gblocks_condition' !== $post->post_type ) {
|
|
return $this->error( 'condition_not_found', __( 'Condition not found.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
if ( 'publish' !== $post->post_status && ! GenerateBlocks_Pro_Conditions::current_user_can_use_conditions( 'manage' ) ) {
|
|
return $this->error( 'condition_not_found', __( 'Condition not found.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
return $this->success( $this->format_condition_response( $post ) );
|
|
}
|
|
|
|
/**
|
|
* Create a new condition.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function create_condition( WP_REST_Request $request ) {
|
|
$nonce_check = $this->verify_nonce( $request );
|
|
if ( is_wp_error( $nonce_check ) ) {
|
|
return $nonce_check;
|
|
}
|
|
|
|
$title = $request->get_param( 'title' );
|
|
$conditions = $request->get_param( 'conditions' );
|
|
$category = $request->get_param( 'category' );
|
|
$status = $request->get_param( 'status' );
|
|
|
|
if ( empty( $title ) ) {
|
|
return $this->error( 'missing_title', __( 'Title is required.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
// Validate status.
|
|
if ( ! empty( $status ) && ! in_array( $status, [ 'publish', 'draft' ], true ) ) {
|
|
$status = 'publish';
|
|
}
|
|
|
|
$post_data = [
|
|
'post_title' => $title,
|
|
'post_type' => 'gblocks_condition',
|
|
'post_status' => $status ? $status : 'publish',
|
|
'post_content' => '',
|
|
];
|
|
|
|
$post_id = wp_insert_post( $post_data );
|
|
|
|
if ( is_wp_error( $post_id ) ) {
|
|
return $this->error( 'creation_failed', $post_id->get_error_message() );
|
|
}
|
|
|
|
// Set categories if provided.
|
|
if ( ! empty( $category ) ) {
|
|
// Handle both single category (string) and multiple categories (array) for future compatibility
|
|
// Currently UI only supports single category selection, but backend stores as array
|
|
// for potential future multi-category support without data migration.
|
|
$categories = is_array( $category ) ? $category : [ $category ];
|
|
$term_ids = [];
|
|
|
|
foreach ( $categories as $cat_slug ) {
|
|
if ( empty( $cat_slug ) ) {
|
|
continue;
|
|
}
|
|
|
|
// Check if the term exists, if not create it.
|
|
$term = get_term_by( 'slug', $cat_slug, 'gblocks_condition_cat' );
|
|
if ( ! $term ) {
|
|
// Create the term if it doesn't exist.
|
|
$term_result = wp_insert_term( $cat_slug, 'gblocks_condition_cat', [ 'slug' => $cat_slug ] );
|
|
if ( ! is_wp_error( $term_result ) ) {
|
|
$term_ids[] = $term_result['term_id'];
|
|
} else {
|
|
// If creation fails, try to get by name.
|
|
$term = get_term_by( 'name', $cat_slug, 'gblocks_condition_cat' );
|
|
if ( $term ) {
|
|
$term_ids[] = $term->term_id;
|
|
}
|
|
}
|
|
} else {
|
|
$term_ids[] = $term->term_id;
|
|
}
|
|
}
|
|
|
|
if ( ! empty( $term_ids ) ) {
|
|
wp_set_post_terms( $post_id, $term_ids, 'gblocks_condition_cat' );
|
|
}
|
|
}
|
|
|
|
// Use the main class sanitization method.
|
|
$conditions_instance = GenerateBlocks_Pro_Conditions::get_instance();
|
|
$sanitized_conditions = $conditions_instance->sanitize_conditions( $conditions );
|
|
update_post_meta( $post_id, '_gb_conditions', $sanitized_conditions );
|
|
|
|
$post = get_post( $post_id );
|
|
return $this->success( $this->format_condition_response( $post ) );
|
|
}
|
|
|
|
/**
|
|
* Update an existing condition.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function update_condition( WP_REST_Request $request ) {
|
|
$nonce_check = $this->verify_nonce( $request );
|
|
if ( is_wp_error( $nonce_check ) ) {
|
|
return $nonce_check;
|
|
}
|
|
|
|
$id = $request->get_param( 'id' );
|
|
$title = $request->get_param( 'title' );
|
|
$conditions = $request->get_param( 'conditions' );
|
|
$status = $request->get_param( 'status' );
|
|
$category = $request->get_param( 'category' );
|
|
|
|
$post = get_post( $id );
|
|
|
|
if ( ! $post || 'gblocks_condition' !== $post->post_type ) {
|
|
return $this->error( 'condition_not_found', __( 'Condition not found.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
$update_data = [ 'ID' => $id ];
|
|
|
|
if ( ! empty( $title ) ) {
|
|
$update_data['post_title'] = $title;
|
|
}
|
|
|
|
if ( ! empty( $status ) && in_array( $status, [ 'publish', 'draft' ], true ) ) {
|
|
$update_data['post_status'] = $status;
|
|
}
|
|
|
|
if ( ! empty( $update_data ) && 1 < count( $update_data ) ) {
|
|
$result = wp_update_post( $update_data );
|
|
if ( is_wp_error( $result ) ) {
|
|
return $this->error( 'update_failed', $result->get_error_message() );
|
|
}
|
|
}
|
|
|
|
// Update categories if provided.
|
|
if ( isset( $category ) ) {
|
|
if ( ! empty( $category ) ) {
|
|
// Handle both single category (string) and multiple categories (array) for future compatibility
|
|
// Currently UI only supports single category selection, but backend stores as array
|
|
// for potential future multi-category support without data migration.
|
|
$categories = is_array( $category ) ? $category : [ $category ];
|
|
$term_ids = [];
|
|
|
|
foreach ( $categories as $cat_slug ) {
|
|
if ( empty( $cat_slug ) ) {
|
|
continue;
|
|
}
|
|
|
|
// Check if the term exists, if not create it.
|
|
$term = get_term_by( 'slug', $cat_slug, 'gblocks_condition_cat' );
|
|
if ( ! $term ) {
|
|
// Create the term if it doesn't exist.
|
|
$term_result = wp_insert_term( $cat_slug, 'gblocks_condition_cat', [ 'slug' => $cat_slug ] );
|
|
if ( ! is_wp_error( $term_result ) ) {
|
|
$term_ids[] = $term_result['term_id'];
|
|
} else {
|
|
// If creation fails, try to get by name.
|
|
$term = get_term_by( 'name', $cat_slug, 'gblocks_condition_cat' );
|
|
if ( $term ) {
|
|
$term_ids[] = $term->term_id;
|
|
}
|
|
}
|
|
} else {
|
|
$term_ids[] = $term->term_id;
|
|
}
|
|
}
|
|
|
|
wp_set_post_terms( $id, $term_ids, 'gblocks_condition_cat' );
|
|
} else {
|
|
// Clear all categories.
|
|
wp_set_post_terms( $id, [], 'gblocks_condition_cat' );
|
|
}
|
|
}
|
|
|
|
if ( ! empty( $conditions ) ) {
|
|
// Use the main class sanitization method.
|
|
$conditions_instance = GenerateBlocks_Pro_Conditions::get_instance();
|
|
$sanitized_conditions = $conditions_instance->sanitize_conditions( $conditions );
|
|
update_post_meta( $id, '_gb_conditions', $sanitized_conditions );
|
|
}
|
|
|
|
$updated_post = get_post( $id );
|
|
return $this->success( $this->format_condition_response( $updated_post ) );
|
|
}
|
|
|
|
/**
|
|
* Delete a condition.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function delete_condition( WP_REST_Request $request ) {
|
|
$nonce_check = $this->verify_nonce( $request );
|
|
if ( is_wp_error( $nonce_check ) ) {
|
|
return $nonce_check;
|
|
}
|
|
|
|
$id = $request->get_param( 'id' );
|
|
$force = (bool) $request->get_param( 'force' );
|
|
|
|
$post = get_post( $id );
|
|
|
|
if ( ! $post || 'gblocks_condition' !== $post->post_type ) {
|
|
return $this->error( 'condition_not_found', __( 'Condition not found.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
// Block deletion of a condition that is still in use unless the caller
|
|
// explicitly confirmed with force=true. Prevents silent content leaks
|
|
// (blocks using a trashed condition render unconditionally on the
|
|
// frontend, bypassing the rule).
|
|
//
|
|
// The /usage REST endpoint is intentionally capped at 5000 recent
|
|
// posts for dashboard performance. At delete time we raise the cap
|
|
// via a scoped filter so we are less likely to miss a reference in
|
|
// older content. Sites can override further with the standard
|
|
// 'generateblocks_usage_search_max_posts' filter.
|
|
if ( ! $force ) {
|
|
$raise_scan_cap = static function( $max ) {
|
|
$current = is_numeric( $max ) ? (int) $max : 5000;
|
|
return max( $current, 50000 );
|
|
};
|
|
add_filter( 'generateblocks_usage_search_max_posts', $raise_scan_cap, 99 );
|
|
|
|
try {
|
|
$usage = $this->search_condition_usage( $id, 50 );
|
|
} finally {
|
|
remove_filter( 'generateblocks_usage_search_max_posts', $raise_scan_cap, 99 );
|
|
}
|
|
|
|
$usage_found = ! empty( $usage['total'] );
|
|
$usage_scan_incomplete = ! empty( $usage['limited'] ) || ! empty( $usage['has_more'] );
|
|
|
|
if ( $usage_found || $usage_scan_incomplete ) {
|
|
$this->clear_usage_caches( 'gb_condition_usage_' . $id . '_' );
|
|
return new WP_Error(
|
|
'condition_in_use',
|
|
__( 'This condition is still in use, or usage could not be fully verified. Resend the request with force=true to confirm deletion.', 'generateblocks-pro' ),
|
|
[
|
|
'status' => 409,
|
|
'usage' => $usage,
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
$result = wp_delete_post( $id, true );
|
|
|
|
if ( ! $result ) {
|
|
return $this->error( 'deletion_failed', __( 'Failed to delete condition.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
$this->clear_usage_caches( 'gb_condition_usage_' . $id . '_' );
|
|
|
|
return $this->success( [ 'deleted' => true ] );
|
|
}
|
|
|
|
/**
|
|
* Format condition response.
|
|
*
|
|
* @param WP_Post $post The post object.
|
|
* @return array
|
|
*/
|
|
private function format_condition_response( $post ) {
|
|
$conditions = get_post_meta( $post->ID, '_gb_conditions', true );
|
|
if ( ! $conditions ) {
|
|
$conditions = [
|
|
'logic' => 'OR',
|
|
'groups' => [],
|
|
];
|
|
}
|
|
|
|
// Get category terms.
|
|
$category_terms = wp_get_post_terms( $post->ID, 'gblocks_condition_cat', [ 'fields' => 'ids' ] );
|
|
$category_terms = is_wp_error( $category_terms ) ? [] : $category_terms;
|
|
|
|
return [
|
|
'id' => $post->ID,
|
|
'title' => [
|
|
'rendered' => $post->post_title,
|
|
],
|
|
'status' => $post->post_status,
|
|
'date' => $post->post_date,
|
|
'modified' => $post->post_modified,
|
|
'gbConditions' => $conditions,
|
|
'gblocks_condition_cat' => $category_terms,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Validate the structure of conditions.
|
|
*
|
|
* @param array $conditions The conditions to validate.
|
|
* @return bool|string True if valid, error message if invalid.
|
|
*/
|
|
private function validate_conditions_structure( $conditions ) {
|
|
if ( ! is_array( $conditions ) ) {
|
|
return __( 'Conditions must be an array.', 'generateblocks-pro' );
|
|
}
|
|
|
|
if ( ! isset( $conditions['logic'] ) || ! in_array( $conditions['logic'], [ 'AND', 'OR' ], true ) ) {
|
|
return __( 'Invalid or missing logic operator.', 'generateblocks-pro' );
|
|
}
|
|
|
|
if ( ! isset( $conditions['groups'] ) || ! is_array( $conditions['groups'] ) ) {
|
|
return __( 'Groups must be an array.', 'generateblocks-pro' );
|
|
}
|
|
|
|
$available_types = array_keys( GenerateBlocks_Pro_Conditions::get_condition_types() );
|
|
|
|
foreach ( $conditions['groups'] as $group_index => $group ) {
|
|
if ( ! is_array( $group ) ) {
|
|
// translators: %d is the group index.
|
|
return sprintf( __( 'Group %d must be an array.', 'generateblocks-pro' ), $group_index );
|
|
}
|
|
|
|
if ( ! isset( $group['logic'] ) || ! in_array( $group['logic'], [ 'AND', 'OR' ], true ) ) {
|
|
// translators: %d is the group index.
|
|
return sprintf( __( 'Invalid or missing logic operator for group %d.', 'generateblocks-pro' ), $group_index );
|
|
}
|
|
|
|
if ( ! isset( $group['conditions'] ) || ! is_array( $group['conditions'] ) ) {
|
|
// translators: %d is the group index.
|
|
return sprintf( __( 'Conditions for group %d must be an array.', 'generateblocks-pro' ), $group_index );
|
|
}
|
|
|
|
foreach ( $group['conditions'] as $condition_index => $condition ) {
|
|
if ( ! is_array( $condition ) ) {
|
|
// translators: %1$d is the condition index, %2$d is the group index.
|
|
return sprintf( __( 'Condition %1$d in group %2$d must be an array.', 'generateblocks-pro' ), $condition_index, $group_index );
|
|
}
|
|
|
|
$required_fields = [ 'type', 'rule', 'operator' ];
|
|
foreach ( $required_fields as $field ) {
|
|
if ( ! isset( $condition[ $field ] ) ) {
|
|
// translators: %1$s is the missing field, %2$d is the condition index, %3$d is the group index.
|
|
return sprintf( __( 'Missing required field "%1$s" in condition %2$d of group %3$d.', 'generateblocks-pro' ), $field, $condition_index, $group_index );
|
|
}
|
|
}
|
|
|
|
if ( ! in_array( $condition['type'], $available_types, true ) ) {
|
|
// translators: %1$s is the invalid condition type, %2$d is the condition index, %3$d is the group index.
|
|
return sprintf( __( 'Invalid condition type "%1$s" in condition %2$d of group %3$d.', 'generateblocks-pro' ), $condition['type'], $condition_index, $group_index );
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get human-readable label for an operator.
|
|
*
|
|
* @param string $operator The operator.
|
|
* @return string
|
|
*/
|
|
private function get_operator_label( $operator ) {
|
|
$labels = [
|
|
'is' => __( 'Is', 'generateblocks-pro' ),
|
|
'is_not' => __( 'Is Not', 'generateblocks-pro' ),
|
|
'exists' => __( 'Exists', 'generateblocks-pro' ),
|
|
'not_exists' => __( 'Does Not Exist', 'generateblocks-pro' ),
|
|
'has_value' => __( 'Has Value', 'generateblocks-pro' ),
|
|
'no_value' => __( 'Has No Value', 'generateblocks-pro' ),
|
|
'equals' => __( 'Equals', 'generateblocks-pro' ),
|
|
'contains' => __( 'Contains', 'generateblocks-pro' ),
|
|
'not_contains' => __( 'Does Not Contain', 'generateblocks-pro' ),
|
|
'starts_with' => __( 'Starts With', 'generateblocks-pro' ),
|
|
'ends_with' => __( 'Ends With', 'generateblocks-pro' ),
|
|
'before' => __( 'Before', 'generateblocks-pro' ),
|
|
'after' => __( 'After', 'generateblocks-pro' ),
|
|
'between' => __( 'Between', 'generateblocks-pro' ),
|
|
'on' => __( 'On', 'generateblocks-pro' ),
|
|
'greater_than' => __( 'Greater Than', 'generateblocks-pro' ),
|
|
'less_than' => __( 'Less Than', 'generateblocks-pro' ),
|
|
'includes_any' => __( 'Includes Any', 'generateblocks-pro' ),
|
|
'includes_all' => __( 'Includes All', 'generateblocks-pro' ),
|
|
'excludes_any' => __( 'Excludes Any', 'generateblocks-pro' ),
|
|
'excludes_all' => __( 'Excludes All', 'generateblocks-pro' ),
|
|
];
|
|
|
|
return $labels[ $operator ] ?? ucfirst( str_replace( '_', ' ', $operator ) );
|
|
}
|
|
|
|
/**
|
|
* Search hierarchical posts (pages and hierarchical custom post types).
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function search_hierarchical_posts( WP_REST_Request $request ) {
|
|
$search = $request->get_param( 'search' );
|
|
$id = $request->get_param( 'id' );
|
|
|
|
// Get all hierarchical post types.
|
|
$post_types = get_post_types(
|
|
[
|
|
'public' => true,
|
|
'hierarchical' => true,
|
|
],
|
|
'names'
|
|
);
|
|
|
|
if ( empty( $post_types ) ) {
|
|
return $this->success( [] );
|
|
}
|
|
|
|
// Handle ID search.
|
|
if ( ! empty( $id ) ) {
|
|
$post = get_post( $id );
|
|
if ( $post && in_array( $post->post_type, $post_types, true ) && current_user_can( 'read_post', $post->ID ) ) {
|
|
$ancestors = get_post_ancestors( $post->ID );
|
|
$hierarchy_indicator = str_repeat( '— ', count( $ancestors ) );
|
|
$title = $hierarchy_indicator . ( $post->post_title ? $post->post_title : sprintf(
|
|
/* translators: %1$s: post type, %2$d: post ID */
|
|
__( '%1$s #%2$d', 'generateblocks-pro' ),
|
|
get_post_type_object( $post->post_type )->labels->singular_name,
|
|
$post->ID
|
|
) );
|
|
|
|
return $this->success(
|
|
[
|
|
[
|
|
'id' => $post->ID,
|
|
'title' => $title,
|
|
],
|
|
]
|
|
);
|
|
}
|
|
|
|
return $this->success( [] );
|
|
}
|
|
|
|
$args = [
|
|
'post_type' => array_values( $post_types ),
|
|
'post_status' => $this->get_readable_post_statuses( array_values( $post_types ), false ),
|
|
'posts_per_page' => empty( $search ) ? 10 : 50, // phpcs:ignore -- WordPress.WP.PostsPerPage.posts_per_page
|
|
'orderby' => empty( $search ) ? 'date' : 'menu_order title',
|
|
'order' => empty( $search ) ? 'DESC' : 'ASC',
|
|
];
|
|
|
|
if ( ! empty( $search ) ) {
|
|
$args['s'] = $search;
|
|
}
|
|
|
|
$posts = get_posts( $args );
|
|
|
|
$formatted_posts = [];
|
|
foreach ( $posts as $post ) {
|
|
if ( ! current_user_can( 'read_post', $post->ID ) ) {
|
|
continue;
|
|
}
|
|
|
|
$ancestors = get_post_ancestors( $post->ID );
|
|
$hierarchy_indicator = str_repeat( '— ', count( $ancestors ) );
|
|
|
|
$formatted_posts[] = [
|
|
'id' => $post->ID,
|
|
'title' => $hierarchy_indicator . ( $post->post_title ? $post->post_title : sprintf(
|
|
/* translators: %1$s: post type singular name, %2$d: post ID */
|
|
__( '%1$s #%2$d', 'generateblocks-pro' ),
|
|
get_post_type_object( $post->post_type )->labels->singular_name,
|
|
$post->ID
|
|
) ),
|
|
];
|
|
}
|
|
|
|
// Sort to maintain hierarchy when showing recent items.
|
|
if ( ! empty( $search ) ) {
|
|
// For search results, sort to maintain hierarchy.
|
|
usort(
|
|
$formatted_posts,
|
|
function( $a, $b ) {
|
|
return strnatcasecmp( $a['title'], $b['title'] );
|
|
}
|
|
);
|
|
}
|
|
|
|
return $this->success( $formatted_posts );
|
|
}
|
|
|
|
/**
|
|
* Get day options for day_of_week rule.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function get_day_options( WP_REST_Request $request ) {
|
|
$instance = GenerateBlocks_Pro_Conditions_Registry::get_instance( 'date_time' );
|
|
|
|
if ( ! $instance || ! method_exists( $instance, 'get_day_options' ) ) {
|
|
return $this->error( 'not_available', __( 'Day options not available.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
$options = $instance->get_day_options();
|
|
|
|
return $this->success( $options );
|
|
}
|
|
|
|
/**
|
|
* Check whether a user can be returned by the author condition picker.
|
|
*
|
|
* Users with list_users can search the full user table. Other condition users
|
|
* are limited to accounts that already have public authored content.
|
|
*
|
|
* @param int $user_id User ID.
|
|
* @return bool
|
|
*/
|
|
private function can_return_user_for_author_condition( $user_id ) {
|
|
if ( current_user_can( 'list_users' ) ) {
|
|
return true;
|
|
}
|
|
|
|
$post_types = get_post_types(
|
|
[
|
|
'public' => true,
|
|
],
|
|
'names'
|
|
);
|
|
|
|
if ( empty( $post_types ) ) {
|
|
return false;
|
|
}
|
|
|
|
$posts = get_posts(
|
|
[
|
|
'author' => absint( $user_id ),
|
|
'post_type' => array_values( $post_types ),
|
|
'post_status' => 'publish',
|
|
'posts_per_page' => 1,
|
|
'fields' => 'ids',
|
|
'no_found_rows' => true,
|
|
]
|
|
);
|
|
|
|
return ! empty( $posts );
|
|
}
|
|
|
|
/**
|
|
* Get condition usage across the site.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function get_condition_usage( WP_REST_Request $request ) {
|
|
$condition_id = $request->get_param( 'id' );
|
|
$limit = $request->get_param( 'limit' );
|
|
$refresh = $request->get_param( 'refresh' );
|
|
|
|
if ( empty( $condition_id ) ) {
|
|
return $this->error( 'missing_condition_id', __( 'Condition ID is required.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
// Verify the condition exists.
|
|
$condition_post = get_post( $condition_id );
|
|
if ( ! $condition_post || 'gblocks_condition' !== $condition_post->post_type ) {
|
|
return $this->error( 'condition_not_found', __( 'Condition not found.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
// Check cache unless refresh is requested.
|
|
$cache_key = sprintf( 'gb_condition_usage_%d_limit%d', $condition_id, $limit );
|
|
if ( ! $refresh ) {
|
|
$cached_data = $this->get_usage_cache( $cache_key );
|
|
if ( false !== $cached_data ) {
|
|
$cached_data['cached'] = true;
|
|
$cached_data['cached_at'] = $cached_data['cached_at'] ?? time();
|
|
return $this->success( $cached_data );
|
|
}
|
|
}
|
|
|
|
$usage_data = $this->search_condition_usage( $condition_id, $limit );
|
|
|
|
// Cache for 15 minutes (900 seconds).
|
|
// Note: Cache invalidation is intentionally manual (via refresh button in UI).
|
|
// Automatic invalidation on post save/delete was deemed too complex and risky
|
|
// due to the need to clear caches across all conditions when any post changes.
|
|
// Users can click refresh to get updated results when needed.
|
|
$usage_data['cached_at'] = time();
|
|
$this->set_usage_cache( $cache_key, $usage_data, 900 );
|
|
|
|
$usage_data['cached'] = false;
|
|
|
|
return $this->success( $usage_data );
|
|
}
|
|
|
|
/**
|
|
* Search for condition usage across different data sources.
|
|
*
|
|
* @param int $condition_id The condition ID to search for.
|
|
* @param int $limit Maximum results per handler to return (default: 50).
|
|
* @return array
|
|
*/
|
|
private function search_condition_usage( $condition_id, $limit = 50 ) {
|
|
$all_usage = [];
|
|
$total_found = 0;
|
|
$has_more = false;
|
|
$limitations = [];
|
|
$has_limitations = false;
|
|
|
|
$limit = max( 1, absint( $limit ) );
|
|
|
|
// Define search handlers - easily extensible for future use cases.
|
|
$search_handlers = [
|
|
'overlay_display_conditions' => [
|
|
'method' => 'search_overlay_display_conditions',
|
|
'label' => __( 'Overlay Panel Display Conditions', 'generateblocks-pro' ),
|
|
],
|
|
];
|
|
|
|
// Apply filters to allow other plugins/themes to add search handlers.
|
|
$search_handlers = apply_filters( 'generateblocks_condition_usage_handlers', $search_handlers, $condition_id );
|
|
|
|
foreach ( $search_handlers as $handler_key => $handler_config ) {
|
|
if ( ! method_exists( $this, $handler_config['method'] ) ) {
|
|
continue;
|
|
}
|
|
|
|
$handler_results = $this->{$handler_config['method']}( $condition_id, $limit );
|
|
|
|
if ( empty( $handler_results['items'] ) && empty( $handler_results['has_more'] ) && empty( $handler_results['limited'] ) ) {
|
|
continue;
|
|
}
|
|
|
|
$items = array_slice( $handler_results['items'], 0, $limit );
|
|
|
|
$all_usage[ $handler_key ] = [
|
|
'label' => $handler_config['label'],
|
|
'items' => $items,
|
|
'has_more' => ! empty( $handler_results['has_more'] ),
|
|
];
|
|
$total_found += count( $items );
|
|
|
|
// If any handler has more results, set the overall has_more flag.
|
|
if ( ! empty( $handler_results['has_more'] ) ) {
|
|
$has_more = true;
|
|
}
|
|
|
|
if ( ! empty( $handler_results['limited'] ) ) {
|
|
$has_limitations = true;
|
|
}
|
|
|
|
if ( ! empty( $handler_results['limits'] ) && is_array( $handler_results['limits'] ) ) {
|
|
foreach ( $handler_results['limits'] as $limit_key => $limit_data ) {
|
|
if ( ! isset( $limitations[ $limit_key ] ) ) {
|
|
$limitations[ $limit_key ] = $limit_data;
|
|
continue;
|
|
}
|
|
|
|
// Merge limitation metadata, keeping the highest values where appropriate.
|
|
$existing = $limitations[ $limit_key ];
|
|
|
|
if ( isset( $limit_data['max_posts'] ) ) {
|
|
$existing['max_posts'] = isset( $existing['max_posts'] )
|
|
? max( $existing['max_posts'], $limit_data['max_posts'] )
|
|
: $limit_data['max_posts'];
|
|
}
|
|
|
|
if ( isset( $limit_data['scanned_posts'] ) ) {
|
|
$existing['scanned_posts'] = isset( $existing['scanned_posts'] )
|
|
? max( $existing['scanned_posts'], $limit_data['scanned_posts'] )
|
|
: $limit_data['scanned_posts'];
|
|
}
|
|
|
|
if ( isset( $limit_data['total_posts'] ) ) {
|
|
$existing['total_posts'] = isset( $existing['total_posts'] )
|
|
? max( $existing['total_posts'], $limit_data['total_posts'] )
|
|
: $limit_data['total_posts'];
|
|
}
|
|
|
|
$limitations[ $limit_key ] = $existing;
|
|
}
|
|
}
|
|
}
|
|
|
|
return [
|
|
'usage' => $all_usage,
|
|
'total' => $total_found,
|
|
'has_more' => $has_more,
|
|
'limited' => $has_limitations,
|
|
'limitations' => $limitations,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Build a fail-closed result for usage scans that could not be verified.
|
|
*
|
|
* @return array
|
|
*/
|
|
private function usage_search_failed_result() {
|
|
return [
|
|
'items' => [],
|
|
'has_more' => true,
|
|
'limited' => true,
|
|
'limits' => [
|
|
'usage_scan_failed' => [],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Search for posts using this condition in overlay panel display conditions.
|
|
*
|
|
* @param int $condition_id The condition ID.
|
|
* @param int $limit Maximum results to return.
|
|
* @return array
|
|
*/
|
|
private function search_overlay_display_conditions( $condition_id, $limit ) {
|
|
global $wpdb;
|
|
|
|
// Validate and sanitize inputs.
|
|
$condition_id = absint( $condition_id );
|
|
$limit = absint( $limit );
|
|
|
|
if ( ! $condition_id ) {
|
|
return [
|
|
'items' => [],
|
|
'has_more' => false,
|
|
];
|
|
}
|
|
|
|
// Query one more than requested to determine if there are more results.
|
|
$query_limit = $limit + 1;
|
|
|
|
$results_query = $wpdb->prepare(
|
|
"SELECT DISTINCT p.ID, p.post_title, p.post_type, p.post_status
|
|
FROM {$wpdb->posts} p
|
|
INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
|
|
WHERE pm.meta_key = %s
|
|
AND pm.meta_value = %s
|
|
AND p.post_status IN ('publish', 'private', 'draft')
|
|
ORDER BY p.post_modified DESC
|
|
LIMIT %d",
|
|
'_gb_overlay_display_condition',
|
|
$condition_id,
|
|
$query_limit
|
|
);
|
|
|
|
$results = $wpdb->get_results( $results_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
|
|
|
if ( ! empty( $wpdb->last_error ) || ! is_array( $results ) ) {
|
|
return $this->usage_search_failed_result();
|
|
}
|
|
|
|
// Check if there are more results.
|
|
$has_more = count( $results ) > $limit;
|
|
|
|
// Remove the extra result if we got it.
|
|
if ( $has_more ) {
|
|
array_pop( $results );
|
|
}
|
|
|
|
$formatted_results = [];
|
|
foreach ( $results as $result ) {
|
|
$post_type_object = get_post_type_object( $result->post_type );
|
|
$post_type_label = $post_type_object ? $post_type_object->labels->singular_name : $result->post_type;
|
|
|
|
// Check if post type is publicly viewable.
|
|
$is_public = $post_type_object && ( $post_type_object->public || $post_type_object->publicly_queryable );
|
|
|
|
// translators: %1$s: Post type label, %2$d: Post ID.
|
|
$fallback_title = $result->post_title ? $result->post_title : sprintf( __( '%1$s #%2$d', 'generateblocks-pro' ), $post_type_label, $result->ID );
|
|
|
|
$formatted_results[] = [
|
|
'id' => absint( $result->ID ),
|
|
'title' => $fallback_title,
|
|
'type' => $result->post_type,
|
|
'type_label' => $post_type_label,
|
|
'status' => $result->post_status,
|
|
'edit_url' => get_edit_post_link( $result->ID, 'raw' ),
|
|
'view_url' => ( 'publish' === $result->post_status && $is_public ) ? get_permalink( $result->ID ) : null,
|
|
'usage_type' => 'overlay_display_condition',
|
|
];
|
|
}
|
|
|
|
return [
|
|
'items' => $formatted_results,
|
|
'has_more' => $has_more,
|
|
'limited' => false,
|
|
'limits' => [],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Search for blocks using this condition.
|
|
*
|
|
* @param int $condition_id The condition ID.
|
|
* @param int $limit Maximum results to return.
|
|
* @return array
|
|
*/
|
|
private function search_block_conditions_usage( $condition_id, $limit ) {
|
|
global $wpdb;
|
|
|
|
// Validate and sanitize inputs.
|
|
$condition_id = absint( $condition_id );
|
|
$limit = absint( $limit );
|
|
|
|
if ( ! $condition_id ) {
|
|
return [
|
|
'items' => [],
|
|
'has_more' => false,
|
|
];
|
|
}
|
|
|
|
// Get post types that support the block editor.
|
|
$post_types = get_post_types_by_support( 'editor' );
|
|
$excluded_types = [ 'revision', 'attachment', 'nav_menu_item' ];
|
|
$post_types = array_diff( $post_types, $excluded_types );
|
|
$post_types = apply_filters( 'generateblocks_usage_search_post_types', $post_types, 'conditions' );
|
|
|
|
if ( empty( $post_types ) ) {
|
|
return [
|
|
'items' => [],
|
|
'has_more' => false,
|
|
];
|
|
}
|
|
|
|
$post_statuses = [ 'publish', 'private', 'draft' ];
|
|
|
|
// Search for the condition ID in post content as a block attribute.
|
|
// We're looking for gbBlockCondition attribute with the condition ID.
|
|
$search_string = '"gbBlockCondition":"' . absint( $condition_id ) . '"';
|
|
$search_pattern = '%' . $wpdb->esc_like( $search_string ) . '%';
|
|
|
|
// Query one more than requested to determine if there are more results.
|
|
$query_limit = $limit + 1;
|
|
|
|
// Cap the number of posts scanned (prevents slow queries on large sites).
|
|
// Users can adjust this via filter if needed.
|
|
$max_posts_to_scan = apply_filters( 'generateblocks_usage_search_max_posts', 5000 );
|
|
|
|
$post_types_placeholders = implode( ', ', array_fill( 0, count( $post_types ), '%s' ) );
|
|
$status_placeholders = implode( ', ', array_fill( 0, count( $post_statuses ), '%s' ) );
|
|
|
|
// Determine overall dataset size so we can inform users when the scan is limited.
|
|
$total_posts_sql = sprintf(
|
|
"SELECT COUNT(ID)
|
|
FROM {$wpdb->posts}
|
|
WHERE post_status IN (%s)
|
|
AND post_type IN (%s)",
|
|
$status_placeholders,
|
|
$post_types_placeholders
|
|
);
|
|
$total_posts_query = $wpdb->prepare(
|
|
$total_posts_sql, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
|
array_merge( $post_statuses, $post_types )
|
|
);
|
|
$total_posts = $wpdb->get_var( $total_posts_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
|
|
|
if ( ! empty( $wpdb->last_error ) ) {
|
|
return $this->usage_search_failed_result();
|
|
}
|
|
|
|
$total_posts = absint( $total_posts );
|
|
$scanned_posts = min( $total_posts, $max_posts_to_scan );
|
|
$limited_scan = $total_posts > $max_posts_to_scan;
|
|
|
|
// Two-phase query: First get recent posts, then search within them.
|
|
// This prevents full table scans on large sites.
|
|
// phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
|
|
$sql = sprintf(
|
|
"SELECT ID, post_title, post_type, post_status, post_content
|
|
FROM (
|
|
SELECT ID, post_title, post_type, post_status, post_content, post_modified
|
|
FROM {$wpdb->posts}
|
|
WHERE post_status IN ('publish', 'private', 'draft')
|
|
AND post_type IN (%s)
|
|
ORDER BY post_modified DESC
|
|
LIMIT %%d
|
|
) AS recent_posts
|
|
WHERE post_content LIKE %%s
|
|
LIMIT %%d",
|
|
$post_types_placeholders
|
|
);
|
|
$results_query = $wpdb->prepare(
|
|
$sql, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
|
array_merge( $post_types, [ $max_posts_to_scan, $search_pattern, $query_limit ] )
|
|
);
|
|
// phpcs:enable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
|
|
|
|
$results = $wpdb->get_results( $results_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
|
|
|
if ( ! empty( $wpdb->last_error ) || ! is_array( $results ) ) {
|
|
return $this->usage_search_failed_result();
|
|
}
|
|
|
|
// Check if there are more results.
|
|
$has_more = count( $results ) > $limit;
|
|
|
|
// Remove the extra result if we got it.
|
|
if ( $has_more ) {
|
|
array_pop( $results );
|
|
}
|
|
|
|
$formatted_results = [];
|
|
foreach ( $results as $result ) {
|
|
$post_type_object = get_post_type_object( $result->post_type );
|
|
$post_type_label = $post_type_object ? $post_type_object->labels->singular_name : $result->post_type;
|
|
|
|
// Check if post type is publicly viewable.
|
|
$is_public = $post_type_object && ( $post_type_object->public || $post_type_object->publicly_queryable );
|
|
|
|
// Count how many blocks in this post use the condition.
|
|
$block_count = substr_count( $result->post_content, '"gbBlockCondition":"' . $condition_id . '"' );
|
|
|
|
// translators: %1$s: Post type label, %2$d: Post ID.
|
|
$fallback_title = $result->post_title ? $result->post_title : sprintf( __( '%1$s #%2$d', 'generateblocks-pro' ), $post_type_label, $result->ID );
|
|
|
|
$formatted_results[] = [
|
|
'id' => absint( $result->ID ),
|
|
'title' => $fallback_title,
|
|
'type' => $result->post_type,
|
|
'type_label' => $post_type_label,
|
|
'status' => $result->post_status,
|
|
'edit_url' => get_edit_post_link( $result->ID, 'raw' ),
|
|
'view_url' => ( 'publish' === $result->post_status && $is_public ) ? get_permalink( $result->ID ) : null,
|
|
'usage_type' => 'block_conditions',
|
|
'block_count' => $block_count,
|
|
];
|
|
}
|
|
|
|
return [
|
|
'items' => $formatted_results,
|
|
'has_more' => $has_more || $limited_scan,
|
|
'limited' => $limited_scan,
|
|
'limits' => $limited_scan
|
|
? [
|
|
'recent_post_scan' => [
|
|
'max_posts' => $max_posts_to_scan,
|
|
'scanned_posts' => $scanned_posts,
|
|
'total_posts' => $total_posts,
|
|
],
|
|
]
|
|
: [],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Search for menu items using this condition.
|
|
*
|
|
* @param int $condition_id The condition ID.
|
|
* @param int $limit Maximum results to return.
|
|
* @return array
|
|
*/
|
|
private function search_menu_item_conditions_usage( $condition_id, $limit ) {
|
|
global $wpdb;
|
|
|
|
// Validate and sanitize inputs.
|
|
$condition_id = absint( $condition_id );
|
|
$limit = absint( $limit );
|
|
|
|
if ( ! $condition_id ) {
|
|
return [
|
|
'items' => [],
|
|
'has_more' => false,
|
|
];
|
|
}
|
|
|
|
// Query one more than requested to determine if there are more results.
|
|
$query_limit = $limit + 1;
|
|
|
|
$results_query = $wpdb->prepare(
|
|
"SELECT DISTINCT p.ID, p.post_title, pm2.meta_value as menu_id
|
|
FROM {$wpdb->posts} p
|
|
INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
|
|
LEFT JOIN {$wpdb->postmeta} pm2 ON p.ID = pm2.post_id AND pm2.meta_key = '_menu_item_menu_item_parent'
|
|
WHERE pm.meta_key = %s
|
|
AND pm.meta_value = %s
|
|
AND p.post_type = 'nav_menu_item'
|
|
ORDER BY p.menu_order ASC
|
|
LIMIT %d",
|
|
'_gb_menu_condition',
|
|
$condition_id,
|
|
$query_limit
|
|
);
|
|
|
|
$results = $wpdb->get_results( $results_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
|
|
|
if ( ! empty( $wpdb->last_error ) || ! is_array( $results ) ) {
|
|
return $this->usage_search_failed_result();
|
|
}
|
|
|
|
// Check if there are more results.
|
|
$has_more = count( $results ) > $limit;
|
|
|
|
// Remove the extra result if we got it.
|
|
if ( $has_more ) {
|
|
array_pop( $results );
|
|
}
|
|
|
|
$formatted_results = [];
|
|
foreach ( $results as $result ) {
|
|
// Get the menu this item belongs to.
|
|
$menu_object = wp_get_object_terms( $result->ID, 'nav_menu' );
|
|
$menu_name = ! empty( $menu_object ) && ! is_wp_error( $menu_object )
|
|
? $menu_object[0]->name
|
|
: __( 'Unknown Menu', 'generateblocks-pro' );
|
|
|
|
// Format the title.
|
|
$item_title = $result->post_title ? $result->post_title : __( 'Menu Item', 'generateblocks-pro' );
|
|
|
|
// translators: %1$s: Menu item title, %2$s: Menu name.
|
|
$full_title = sprintf( __( '%1$s (in %2$s)', 'generateblocks-pro' ), $item_title, $menu_name );
|
|
|
|
$formatted_results[] = [
|
|
'id' => absint( $result->ID ),
|
|
'title' => $full_title,
|
|
'type' => 'nav_menu_item',
|
|
'type_label' => __( 'Menu Item', 'generateblocks-pro' ),
|
|
'status' => 'publish',
|
|
'edit_url' => admin_url( 'nav-menus.php' ),
|
|
'view_url' => null, // Menu items don't have direct view URLs.
|
|
'usage_type' => 'menu_item_conditions',
|
|
];
|
|
}
|
|
|
|
return [
|
|
'items' => $formatted_results,
|
|
'has_more' => $has_more,
|
|
'limited' => false,
|
|
'limits' => [],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Search users for author conditions.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function search_users( WP_REST_Request $request ) {
|
|
$search = $request->get_param( 'search' );
|
|
$id = $request->get_param( 'id' );
|
|
|
|
// Handle ID search.
|
|
if ( ! empty( $id ) ) {
|
|
$user = get_user_by( 'ID', $id );
|
|
if ( $user && $this->can_return_user_for_author_condition( $user->ID ) ) {
|
|
$formatted_users = [
|
|
[
|
|
'id' => $user->ID,
|
|
'name' => $user->display_name ? $user->display_name : $user->user_login,
|
|
],
|
|
];
|
|
return $this->success( $formatted_users );
|
|
} else {
|
|
return $this->success( [] );
|
|
}
|
|
}
|
|
|
|
$args = [
|
|
'fields' => [ 'ID', 'display_name', 'user_login' ],
|
|
'number' => empty( $search ) ? 10 : 50,
|
|
'orderby' => empty( $search ) ? 'registered' : 'display_name',
|
|
'order' => empty( $search ) ? 'DESC' : 'ASC',
|
|
];
|
|
|
|
if ( ! empty( $search ) ) {
|
|
$args['search'] = '*' . esc_attr( $search ) . '*';
|
|
$args['search_columns'] = [ 'user_login', 'display_name' ];
|
|
|
|
if ( current_user_can( 'list_users' ) ) {
|
|
$args['search_columns'][] = 'user_email';
|
|
}
|
|
}
|
|
|
|
// Only get users who have published posts (actual authors).
|
|
$args['has_published_posts'] = true;
|
|
|
|
$users = get_users( $args );
|
|
|
|
$formatted_users = [];
|
|
foreach ( $users as $user ) {
|
|
$display_name = $user->display_name ? $user->display_name : $user->user_login;
|
|
|
|
$formatted_users[] = [
|
|
'id' => $user->ID,
|
|
'name' => $display_name,
|
|
'title' => $display_name,
|
|
];
|
|
}
|
|
|
|
return $this->success( $formatted_users );
|
|
}
|
|
|
|
/**
|
|
* Success rest.
|
|
*
|
|
* @param mixed $response response data.
|
|
* @return mixed
|
|
*/
|
|
public function success( $response ) {
|
|
return new WP_REST_Response(
|
|
array(
|
|
'success' => true,
|
|
'response' => $response,
|
|
),
|
|
200
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Failed rest.
|
|
*
|
|
* @param mixed $response response data.
|
|
* @return mixed
|
|
*/
|
|
public function failed( $response ) {
|
|
return new WP_REST_Response(
|
|
array(
|
|
'success' => false,
|
|
'response' => $response,
|
|
),
|
|
200
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create a new category.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function create_category( WP_REST_Request $request ) {
|
|
$nonce_check = $this->verify_nonce( $request );
|
|
if ( is_wp_error( $nonce_check ) ) {
|
|
return $nonce_check;
|
|
}
|
|
|
|
$name = $request->get_param( 'name' );
|
|
$slug = $request->get_param( 'slug' );
|
|
|
|
if ( empty( $name ) ) {
|
|
return $this->error( 'missing_name', __( 'Category name is required.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
$term_data = [ 'name' => $name ];
|
|
if ( ! empty( $slug ) ) {
|
|
$term_data['slug'] = $slug;
|
|
}
|
|
|
|
$term = wp_insert_term( $name, 'gblocks_condition_cat', $term_data );
|
|
|
|
if ( is_wp_error( $term ) ) {
|
|
return $this->error( 'creation_failed', $term->get_error_message() );
|
|
}
|
|
|
|
$created_term = get_term( $term['term_id'], 'gblocks_condition_cat' );
|
|
return $this->success(
|
|
[
|
|
'id' => $created_term->term_id,
|
|
'name' => $created_term->name,
|
|
'slug' => $created_term->slug,
|
|
'count' => $created_term->count,
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Update a category.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function update_category( WP_REST_Request $request ) {
|
|
$nonce_check = $this->verify_nonce( $request );
|
|
if ( is_wp_error( $nonce_check ) ) {
|
|
return $nonce_check;
|
|
}
|
|
|
|
$id = $request->get_param( 'id' );
|
|
$name = $request->get_param( 'name' );
|
|
|
|
if ( empty( $name ) ) {
|
|
return $this->error( 'missing_name', __( 'Category name is required.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
$term = wp_update_term(
|
|
$id,
|
|
'gblocks_condition_cat',
|
|
[
|
|
'name' => $name,
|
|
]
|
|
);
|
|
|
|
if ( is_wp_error( $term ) ) {
|
|
return $this->error( 'update_failed', $term->get_error_message() );
|
|
}
|
|
|
|
$updated_term = get_term( $term['term_id'], 'gblocks_condition_cat' );
|
|
return $this->success(
|
|
[
|
|
'id' => $updated_term->term_id,
|
|
'name' => $updated_term->name,
|
|
'slug' => $updated_term->slug,
|
|
'count' => $updated_term->count,
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Delete a category.
|
|
*
|
|
* @param WP_REST_Request $request The request object.
|
|
* @return WP_REST_Response
|
|
*/
|
|
public function delete_category( WP_REST_Request $request ) {
|
|
$nonce_check = $this->verify_nonce( $request );
|
|
if ( is_wp_error( $nonce_check ) ) {
|
|
return $nonce_check;
|
|
}
|
|
|
|
$id = $request->get_param( 'id' );
|
|
|
|
$term = get_term( $id, 'gblocks_condition_cat' );
|
|
if ( is_wp_error( $term ) || ! $term ) {
|
|
return $this->error( 'term_not_found', __( 'Category not found.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
$result = wp_delete_term( $id, 'gblocks_condition_cat' );
|
|
|
|
if ( is_wp_error( $result ) ) {
|
|
return $this->error( 'deletion_failed', $result->get_error_message() );
|
|
}
|
|
|
|
if ( ! $result ) {
|
|
return $this->error( 'deletion_failed', __( 'Category could not be deleted.', 'generateblocks-pro' ) );
|
|
}
|
|
|
|
return $this->success( [ 'deleted' => true ] );
|
|
}
|
|
|
|
/**
|
|
* Error rest.
|
|
*
|
|
* @param mixed $code error code.
|
|
* @param mixed $response response data.
|
|
* @return mixed
|
|
*/
|
|
public function error( $code, $response ) {
|
|
return new WP_REST_Response(
|
|
array(
|
|
'error' => true,
|
|
'success' => false,
|
|
'error_code' => $code,
|
|
'response' => $response,
|
|
),
|
|
400
|
|
);
|
|
}
|
|
}
|
|
|
|
GenerateBlocks_Pro_Conditions_REST::get_instance();
|