Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-dashboard
*/
namespace TVD\Content_Sets;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden!
}
/**
* Class Archive_Rule
*
* @package TVD\Content_Sets
* @project : thrive-dashboard
*/
class Archive_Rule extends Rule {
/**
* Returns true if the rule is valid
*
* NOTE: for now the archive rules is only supported for Authors
*
* @return bool
*/
public function is_valid() {
$valid = parent::is_valid();
if ( $this->field !== 'author' ) {
/**
* For now we only support Author field for Archive Rules
*/
$valid = false;
}
return $valid;
}
/**
* Should be extended in child classes
*
* @param string $query_string
* @param bool|int $paged if non-false, it will return limited results
* @param int $per_page number of results per page. ignored if $paged = false
*
* @return array
*/
public function get_items( $query_string = '', $paged = false, $per_page = 15 ) {
$this->paged = $paged;
$this->per_page = $per_page;
$this->query_string = $query_string;
$items = array();
if ( $this->field === 'author' ) {
$items = parent::search_users();
}
return $items;
}
/**
* Test if a rule matches the given params
*
* @param \WP_User $user
*
* @return bool
*/
public function matches( $user ) {
if ( is_author() ) {
return $this->match_value( $user->ID, $user );
}
return false;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-dashboard
*/
namespace TVD\Content_Sets;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden!
}
/**
* Class Blog_Rule
*
* @package TVD\Content_Sets
* @project : thrive-dashboard
*/
class Blog_Rule extends Rule {
/**
* @return bool
*/
public function is_valid() {
return true;
}
/**
* Returns true if the active query is for the blog homepage.
*
* @param \WP_Post|\WP_Term $post_or_term
*
* @return bool
*/
public function matches( $post_or_term ) {
return is_home();
}
}

View File

@@ -0,0 +1,305 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-dashboard
*/
namespace TVD\Content_Sets;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden!
}
/**
* Class Post_Rule
*
* @package TVD\Content_Sets
* @project : thrive-dashboard
*/
class Post_Rule extends Rule {
/**
* @var string[]
*/
private $tax_fields
= array(
self::FIELD_CATEGORY => 'category',
self::FIELD_TAG => 'post_tag',
self::FIELD_AUTHOR => 'author',
);
/**
* Should be extended in child classes
*
* @param string $query_string
* @param bool|int $paged if non-false, it will return limited results
* @param int $per_page number of results per page. ignored if $paged = false
*
* @return array
*/
public function get_items( $query_string = '', $paged = false, $per_page = 15 ) {
/**
* Needed for the filter
*/
$this->query_string = $query_string;
$this->paged = $paged;
$this->per_page = $per_page;
return $this->should_search_terms() ? $this->search_terms() : $this->search_posts();
}
/**
* posts_where hook callback
* Searches particular post by title
*
* @param string $where
* @param \WP_Query $wp_query
*
* @return string
*/
public function title_filter( $where, $wp_query ) {
if ( $this->field === self::FIELD_TITLE ) {
global $wpdb;
$operation = $this->operator === self::OPERATOR_IS ? 'LIKE' : 'NOT LIKE';
$where .= ' AND ' . $wpdb->posts . '.post_title ' . $operation . ' \'%' . esc_sql( $wpdb->esc_like( $this->query_string ) ) . '%\'';
}
return $where;
}
/**
* terms_clauses hook callback
* Searches for a particular term by name
*
* @param $pieces
* @param $taxonomies
* @param $args
*
* @return array
*/
public function name_filter( $pieces, $taxonomies, $args ) {
if ( $this->should_search_terms() ) {
global $wpdb;
$operation = 'LIKE';
$pieces['where'] .= ' AND name ' . $operation . ' ' . ' \'%' . esc_sql( $wpdb->esc_like( $this->query_string ) ) . '%\' ';
}
return $pieces;
}
/**
* Prepares the item for front-end
*
* @param int $item
* @param bool $is_term
*
* @return array
*/
public function get_frontend_item( $item ) {
if ( $this->should_search_terms() ) {
return parent::get_frontend_item( $item );
}
$post = get_post( $item );
if ( empty( $post ) ) {
return array();
}
return array(
'id' => $post->ID,
'text' => $this->alter_frontend_title( $post->post_title, $post->post_status ),
);
}
/**
* Used to get all public content types needed for the content sets
*
* @return array
*/
public static function get_content_types() {
$ignored_types = apply_filters( 'thrive_ignored_post_types', array(
'attachment',
'tcb_lightbox',
'tcb_symbol',
'tva-acc-restriction',
) );
$all = get_post_types( array( 'public' => true ) );
$post_types = array();
foreach ( $all as $key => $post_type ) {
if ( in_array( $key, $ignored_types, true ) ) {
continue;
}
$post_types[ $key ] = tvd_get_post_type_label( $key );
}
/**
* Allow other functionality to be injected here
* Used to inject the Protected File post_type in the list so it can be protected by an apprentice product
*
* @param array $post_types
*/
return apply_filters( 'tvd_content_sets_get_content_types', $post_types );
}
/**
* Returns true if the UI needs to perform a terms search
*
* @return bool
*/
private function should_search_terms() {
return array_key_exists( $this->field, $this->tax_fields );
}
/**
* Returns the terms (category|post_tags) needed for the UI
* called from get_items method
*
* @return array
*/
private function search_terms() {
$response = array();
$taxonomy = $this->tax_fields[ $this->field ];
if ( $this->field === self::FIELD_AUTHOR ) {
$response = parent::search_users();
} else {
add_filter( 'terms_clauses', array( $this, 'name_filter' ), 10, 3 );
$query = new \WP_Term_Query();
$args = array(
'taxonomy' => $taxonomy,
'hide_empty' => false,
);
if ( $this->paged !== false ) {
$args['number'] = $this->per_page;
$args['offset'] = ( $this->paged - 1 ) * $this->per_page;
}
$terms = $query->query( $args );
remove_filter( 'terms_clauses', array( $this, 'name_filter' ), 10 );
foreach ( $terms as $term ) {
$response[] = array(
'id' => $term->term_id,
'text' => $term->name,
);
}
}
return $response;
}
/**
* Returns the posts needed for the UI
* called from get_items method
*
* @return array
*/
private function search_posts() {
$response = array();
add_filter( 'posts_where', array( $this, 'title_filter' ), 10, 2 );
$query = new \WP_Query;
$args = array(
'post_type' => $this->content,
'post_status' => array( 'draft', 'publish' ),
'posts_per_page' => $this->paged !== false ? $this->per_page : - 1,
);
if ( $this->paged !== false ) {
$args['paged'] = $this->paged;
}
$posts = $query->query( $args );
remove_filter( 'posts_where', array( $this, 'title_filter' ), 10 );
foreach ( $posts as $post ) {
/**
* Allow other plugins to hook here and remove the post from the content set dropdown
*
* @param boolean return value
* @param \WP_Post $post
*/
if ( ! apply_filters( 'tvd_content_sets_allow_select_post', true, $post ) ) {
continue;
}
$response[] = array(
'id' => $post->ID,
'text' => $this->alter_frontend_title( $post->post_title, $post->post_status ),
);
}
return $response;
}
/**
* Test if a rule matches the given params
*
* @param int|string $value
* @param \WP_Post|\WP_Term $post_or_term
*
* @return bool
*/
public function match_value( $value, $post_or_term ) {
if ( $this->field === self::FIELD_PUBLISHED_DATE ) {
$post_published_date = get_the_date( 'Y-m-d', $post_or_term );
switch ( $this->operator ) {
case self::OPERATOR_LOWER_EQUAL:
return strtotime( $post_published_date ) <= strtotime( $this->value );
case self::OPERATOR_GRATER_EQUAL:
return strtotime( $post_published_date ) >= strtotime( $this->value );
case self::OPERATOR_WITHIN_LAST:
return strtotime( $post_published_date ) >= strtotime( '-' . $this->value );
default:
break;
}
return false;
}
if ( $this->field === self::FIELD_TAG ) {
$common = count( array_intersect( $this->value, wp_get_post_tags( $post_or_term->ID, array( 'fields' => 'ids' ) ) ) );
if ( $this->operator === self::OPERATOR_IS ) {
return $common > 0;
}
return $common === 0;
}
if ( $this->field === self::FIELD_CATEGORY ) {
$common = count( array_intersect( $this->value, wp_get_post_categories( $post_or_term->ID, array( 'fields' => 'ids' ) ) ) );
if ( $this->operator === self::OPERATOR_IS ) {
return $common > 0;
}
return $common === 0;
}
if ( $this->field === self::FIELD_AUTHOR ) {
return in_array( (int) $post_or_term->post_author, $this->value, true );
}
return parent::match_value( $value, $post_or_term );
}
}

View File

@@ -0,0 +1,445 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-dashboard
*/
namespace TVD\Content_Sets;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden!
}
/**
* Class Rule
*
* @package TVD\Content_Sets
* @project : thrive-dashboard
*/
class Rule implements \JsonSerializable {
const HIDDEN_POST_TYPE_SEARCH_RESULTS = 'tvd_search_result_page';
const HIDDEN_POST_TYPE_BLOG = 'tvd_blog_page';
const OPERATOR_IS = '===';
const OPERATOR_NOT_IS = '!==';
const OPERATOR_GRATER_EQUAL = '>=';
const OPERATOR_LOWER_EQUAL = '<=';
const OPERATOR_WITHIN_LAST = 'within_last';
const FIELD_ALL = '-1';
const FIELD_TITLE = 'title';
const FIELD_TAG = 'tag';
const FIELD_CATEGORY = 'category';
const FIELD_PUBLISHED_DATE = 'published_date';
const FIELD_TOPIC = 'topic';
const FIELD_DIFFICULTY = 'difficulty';
const FIELD_LABEL = 'label';
const FIELD_AUTHOR = 'author';
/**
* @var string Query String needed to for post search
*/
protected $query_string = '';
protected $paged = false;
protected $per_page = 15;
protected $user_fields = array(
self::FIELD_AUTHOR,
);
/**
* @var mixed
*/
public $type;
/**
* @var mixed
*/
public $value;
/**
* @var mixed
*/
public $content;
/**
* @var mixed
*/
public $field;
/**
* @var mixed
*/
public $operator;
/**
* Rule constructor.
*
* @param array $data
*/
public function __construct( $data ) {
$this->type = $data['content_type'];
$this->content = $data['content'];
$this->field = $data['field'];
$this->operator = $data['operator'];
$this->value = $data['value'];
}
/**
* Factory for Rule Classes
*
* @param array $data
*
* @return Term_Rule|Post_Rule
*/
public static function factory( $data ) {
switch ( $data['content_type'] ) {
case 'term':
$class_name = 'Term_Rule';
break;
case self::HIDDEN_POST_TYPE_SEARCH_RESULTS:
$class_name = 'Search_Result_Rule';
break;
case self::HIDDEN_POST_TYPE_BLOG:
$class_name = 'Blog_Rule';
break;
case 'archive':
$class_name = 'Archive_Rule';
break;
default:
$class_name = 'Post_Rule';
break;
}
$class_name = __NAMESPACE__ . '\\' . $class_name;
return new $class_name( $data );
}
/**
* Returns true if the rule is valid
*
* @return bool
*/
public function is_valid() {
return ! empty( $this->type ) && in_array( $this->type, array( 'post', 'term', 'archive' ) ) &&
! empty( $this->content ) &&
! empty( $this->field ) &&
( (int) $this->field === - 1 || ( ! empty( $this->operator ) && ( is_array( $this->value ) || ! empty( $this->value ) || is_numeric( $this->value ) ) ) );
}
/**
* Return true if the active rule is equal to the rule provided as parameter
*
* @param Rule $rule
*
* @return boolean
*/
public function is_equal_to( $rule ) {
return serialize( $rule->jsonSerialize( false ) ) === serialize( $this->jsonSerialize( false ) );
}
/**
* Returns true if rule has value equal to parameter or value in array of values
*
* @param scalar $value
*
* @return boolean
*/
public function has_value( $value ) {
if ( $this->value === $value || ( is_array( $this->value ) && in_array( $value, $this->value, true ) ) ) {
return true;
}
return false;
}
/**
* Should be extended in child classes
*
* @param string $query_string
* @param bool|int $paged if non-false, it will return limited results
* @param int $per_page number of results per page. ignored if $paged = false
*
* @return array
*/
public function get_items( $query_string = '', $paged = false, $per_page = 15 ) {
return array();
}
/**
* Test if a rule matches the given params
*
* @param \WP_Post|\WP_Term $post_or_term
*
* @return bool
*/
public function matches( $post_or_term ) {
list( $content, $value ) = Utils::get_post_or_term_parts( $post_or_term );
if ( ! $this->match_content( $content ) ) {
return false;
}
if ( (int) $this->field === - 1 ) {
return true;
}
return $this->match_value( $value, $post_or_term );
}
/**
* @param \WP_Post|\WP_Term $post_or_term
*
* @used in Thrive Apprentice - edit-post view
* @return bool
*/
public function matches_static_value( $post_or_term ) {
list( $content, $value ) = Utils::get_post_or_term_parts( $post_or_term );
if ( ! $this->match_content( $content ) ) {
return false;
}
if ( $this->field !== self::FIELD_TITLE ) {
return false;
}
return $this->match_value( $value, $post_or_term );
}
/**
* @param string $content
*
* @return bool
*/
public function match_content( $content ) {
return $this->content === $content;
}
/**
* @param string|array $value
* @param \WP_Post|\WP_Term|\WP_User $post_or_term
*
* @return bool
*/
public function match_value( $value, $post_or_term ) {
if ( is_array( $this->value ) ) {
if ( $this->operator === self::OPERATOR_IS ) {
return in_array( $value, $this->value );
}
if ( $this->operator === self::OPERATOR_NOT_IS ) {
return ! in_array( $value, $this->value );
}
}
return $value === $this->value;
}
/**
* Constructs the item needed for front-end
* Needs to be extended in child classes
*
* @param int $item
*
* @return array
*/
public function get_frontend_item( $item ) {
if ( $this->should_search_users() ) {
return $this->get_frontend_user( $item );
}
return $this->get_frontend_term( $item );
}
/**
* Constructs the item from a term, needed for front-end
*
* @param int $item
*
* @return array
*/
public function get_frontend_user( $item ) {
$user = get_user_by( 'id', $item );
if ( empty( $user ) || ! $user instanceof \WP_User ) {
return array();
}
return array(
'id' => (int) $item,
'text' => $user->display_name,
);
}
/**
* Constructs the item from a term, needed for front-end
*
* @param int $item
*
* @return array
*/
public function get_frontend_term( $item ) {
$term = get_term( $item );
if ( empty( $term ) ) {
return array();
}
return array(
'id' => $term->term_id,
'text' => $term->name,
);
}
/**
* @return string
*/
public function get_content() {
return $this->content;
}
/**
* @return array
*/
public function get_value() {
return $this->value;
}
/**
* @return array
*/
#[\ReturnTypeWillChange]
public function jsonSerialize( $frontend = true ) {
$value = $this->value;
if ( is_array( $this->value ) ) {
if ( $frontend ) {
$value = array_map( function ( $item ) {
if ( isset( $item ) && is_int( $item ) && $front_item = $this->get_frontend_item( $item ) ) {
return $front_item;
}
}, $this->value );
//Remove empty values for UI
//Solves the case when we have a content set that contains a course or a post and that post is no longer available
//Also reset the indexes of the array
$value = array_values( array_filter( $value ) );
} else {
$value = $this->value = array_map( static function ( $item ) {
if ( is_numeric( $item ) ) {
return (int) $item;
}
if ( ! empty( $item['id'] ) || is_numeric( $item['id'] ) ) {
return (int) $item['id'];
}
}, $this->value );
}
}
$return = array(
'content_type' => $this->type,
'content' => $this->content,
'field' => $this->field,
'operator' => $this->operator,
'value' => $value,
);
if ( $frontend ) {
$return['content_label'] = array(
'singular' => 'Post',
'plural' => 'Posts',
);
if ( $return['content_type'] === 'term' ) {
/**
* @var \WP_Taxonomy
*/
$taxonomy = get_taxonomy( $return['content'] );
if ( $taxonomy instanceof \WP_Taxonomy ) {
$return['content_label']['singular'] = $taxonomy->labels->singular_name;
$return['content_label']['plural'] = $taxonomy->labels->name;
}
} elseif ( $return['content_type'] === 'post' ) {
/**
* @var \WP_Post_Type
*/
$postType = get_post_type_object( $return['content'] );
if ( $postType instanceof \WP_Post_Type ) {
$return['content_label']['singular'] = $postType->labels->singular_name;
$return['content_label']['plural'] = $postType->labels->name;
}
} elseif ( $return['content_type'] === self::HIDDEN_POST_TYPE_SEARCH_RESULTS ) {
$return['content_label']['plural'] = $return['content_label']['singular'] = 'Search result page';
} elseif ( $return['content_type'] === self::HIDDEN_POST_TYPE_BLOG ) {
$return['content_label']['plural'] = $return['content_label']['singular'] = 'Blog page';
} elseif ( $return['content_type'] === 'archive' ) {
$return['content_label']['plural'] = $return['content_label']['singular'] = 'Archive page';
}
}
return $return;
}
/**
* Alter the title that is shown in the UI depending on the status
*
* @param string $title
* @param string $status
*
* @return string
*/
protected function alter_frontend_title( $title, $status = 'publish' ) {
if ( $status !== 'publish' ) {
$title .= ' [' . $status . ']';
}
return $title;
}
/**
* @return array
*/
public function search_users() {
$search_string = esc_attr( trim( $this->query_string ) );
$response = array();
$users = new \WP_User_Query( array(
'search' => '*' . $search_string . '*',
'search_columns' => array(
'display_name',
),
'number' => $this->paged !== false ? $this->per_page : - 1,
'offset' => $this->paged !== false ? ( $this->paged - 1 ) * $this->per_page : 0,
)
);
/**
* @var \WP_User $user
*/
foreach ( $users->get_results() as $user ) {
$response[] = array(
'id' => (int) $user->data->ID,
'text' => (string) $user->data->display_name,
);
}
return $response;
}
/**
* Returns true if the system should search in user tables for values
*
* @return bool
*/
protected function should_search_users() {
return ! empty( $this->user_fields ) && in_array( $this->field, $this->user_fields );
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-dashboard
*/
namespace TVD\Content_Sets;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden!
}
/**
* Class Search_Result_Rule
*
* @package TVD\Content_Sets
* @project : thrive-dashboard
*/
class Search_Result_Rule extends Rule {
/**
* @return bool
*/
public function is_valid() {
return true;
}
/**
* Returns true if the active query is for a search.
*
* @param \WP_Post|\WP_Term $post_or_term
*
* @return bool
*/
public function matches( $post_or_term ) {
return is_search();
}
}

View File

@@ -0,0 +1,669 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-dashboard
*/
namespace TVD\Content_Sets;
use TVD\Cache\Cache_Exception;
use function TVD\Cache\content_set_cache;
use function TVD\Cache\meta_cache;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden!
}
/**
* Class Set
*
* @property int ID
* @property string $post_title
* @property array $post_content
*
* @package TVD\Content_Sets
* @project : thrive-dashboard
*/
class Set implements \JsonSerializable {
/**
* Stores the identified content set
*
* @var array
*/
private static $matched;
use \TD_Magic_Methods;
/**
* @var string Post type name
*/
const POST_TYPE = 'tvd_content_set';
/**
* default properties for a TA Course
*
* @var array
*/
protected $_defaults
= array(
'ID' => 0,
'post_title' => '',
'post_content' => array(),
);
private $rules = array();
/**
* Set constructor.
*
* @param int|array $data
*/
public function __construct( $data ) {
if ( is_int( $data ) ) {
$data = get_post( (int) $data, ARRAY_A );
$data['post_content'] = thrive_safe_unserialize( $data['post_content'] );
} elseif ( is_array( $data ) && ! empty( $data['post_content'] ) ) {
$data['post_content'] = thrive_safe_unserialize( $data['post_content'] );
}
$this->_data = array_merge( $this->_defaults, (array) $data );
foreach ( $data['post_content'] as $rule_data ) {
$this->rules[] = Rule::factory( $rule_data );
}
}
/**
* Register post type
*/
public static function init() {
register_post_type( static::POST_TYPE, array(
'labels' => array(
'name' => 'Content Set',
),
'publicly_queryable' => true, //Needs to be queryable on front-end for products
'public' => false,
'query_var' => false,
'rewrite' => false,
'show_in_nav_menus' => false,
'show_in_menu' => false,
'show_ui' => false,
'exclude_from_search' => true,
'show_in_rest' => true,
'has_archive' => false,
'map_meta_cap' => true,
) );
}
/**
* @param array $args
*
* @return Set[]
*/
public static function get_items( $args = array() ) {
$posts = get_posts( array_merge( array(
'posts_per_page' => - 1,
'post_type' => static::POST_TYPE,
), $args ) );
$sets = array();
foreach ( $posts as $post ) {
$sets[] = new Set( $post->to_array() );
}
return $sets;
}
/**
* Return all content sets as key => name
* Used for adding into a select element
*
* @param array|null $sets
*
* @return array
*/
public static function get_items_for_dropdown( $sets = null ) {
$dropdown = array();
if ( ! is_array( $sets ) ) {
$sets = static::get_items();
}
foreach ( $sets as $set ) {
$dropdown[] = array( 'id' => $set->ID, 'text' => $set->post_title );
}
return $dropdown;
}
/**
* @param \WP_Post|\WP_Term $post_or_term
* @param string $return_type can be objects or ids
*
* @return array
*/
public static function get_items_that_static_match( $post_or_term, $return_type = 'objects' ) {
$return = array();
foreach ( static::get_items() as $set ) {
if ( $set->has_matching_static_rules( $post_or_term ) ) {
$return[] = $return_type === 'ids' ? $set->ID : $set;
}
}
return $return;
}
/**
* @param $post_or_term
* @param array $sets_ids_for_object
*
* @return string|void
*/
public static function toggle_object_to_set_static_rules( $post_or_term, $sets_ids_for_object = array() ) {
list( $content, $value ) = Utils::get_post_or_term_parts( $post_or_term );
if ( $post_or_term instanceof \WP_Post && ! array_key_exists( $content, Post_Rule::get_content_types() ) ) {
return;
}
$current_matches = static::get_items_that_static_match( $post_or_term, 'ids' );
sort( $current_matches );
sort( $sets_ids_for_object );
if ( $current_matches == $sets_ids_for_object ) {
//No modifications have been done to the content sets$ty
return;
}
$rule = Rule::factory( array(
'content_type' => 'post',
'content' => $content,
'field' => Rule::FIELD_TITLE,
'operator' => Rule::OPERATOR_IS,
'value' => array( $value ),
) );
//Remove rule from content set
$sets_to_remove = array_diff( $current_matches, $sets_ids_for_object );
foreach ( $sets_to_remove as $id ) {
$set = new Set( (int) $id );
$set->remove_rule( $rule )->remove_rule_value( $value )->update();
}
//Add rule to content set
$sets_to_add = array_diff( $sets_ids_for_object, $current_matches );
foreach ( $sets_to_add as $id ) {
$set = new Set( (int) $id );
$set->add_rule( $rule )->update();
}
}
/**
* @param \WP_Post|\WP_Term|\WP_User $post_or_term
*
* @return bool
*/
public function has_matching_rules( $post_or_term ) {
$return = false;
/**
* @var Rule $rule
*/
foreach ( $this->rules as $rule ) {
if ( $rule->matches( $post_or_term ) ) {
$return = true;
break;
}
}
return $return;
}
/**
* @param \WP_Post|\WP_Term $post_or_term
*
* @return bool
*/
public function has_matching_static_rules( $post_or_term ) {
$return = false;
/**
* @var Rule $rule
*/
foreach ( $this->rules as $rule ) {
if ( $rule->matches_static_value( $post_or_term ) ) {
$return = true;
break;
}
}
return $return;
}
/**
* @param Rule $rule
*
* @return $this
*/
public function remove_rule( $rule ) {
$index_to_remove = null;
/**
* @var Rule $r
*/
foreach ( $this->rules as $index => $r ) {
if ( $r->is_equal_to( $rule ) ) {
$index_to_remove = $index;
break;
}
}
if ( ! is_numeric( $index_to_remove ) ) {
return $this;
}
unset( $this->rules[ $index_to_remove ] );
return $this;
}
/**
* Remove value from the rules
*
* @param scalar $value
*
* @return $this
*/
public function remove_rule_value( $value ) {
if ( empty( $value ) ) {
return $this;
}
/**
* @var Rule $r
*/
foreach ( $this->rules as $index => $r ) {
if ( $r->has_value( $value ) ) {
if ( is_array( $r->value ) && ( $key = array_search( $value, $r->value ) ) !== false ) {
unset( $this->rules[ $index ]->value[ $key ] );
//Reset the indexes
$this->rules[ $index ]->value = array_values( $this->rules[ $index ]->value );
} elseif ( is_scalar( $r->value ) ) {
$this->rules[ $index ]->value = '';
}
}
}
return $this;
}
/**
* @param Rule $rule
*
* @return $this
*/
public function add_static_rule( $rule ) {
if ( ! $rule->is_valid() ) {
return $this;
}
$child_added = false;
/**
* @var Rule $r
*/
foreach ( $this->rules as $r ) {
if ( $rule->get_content() === $r->get_content() && $rule->field === Rule::FIELD_TITLE && $r->field === Rule::FIELD_TITLE ) {
if ( is_array( $rule->value ) && is_array( $r->value ) ) {
$r->value = array_merge( $rule->value, $r->value );
$child_added = true;
break;
}
}
}
if ( ! $child_added ) {
$this->rules[] = $rule;
}
return $this;
}
/**
* @param Rule $rule
*
* @return $this
*/
public function add_rule( $rule ) {
if ( ! $rule->is_valid() ) {
return $this;
}
/**
* @var Rule $r
*/
foreach ( $this->rules as $r ) {
if ( $r->is_valid() && $r->is_equal_to( $rule ) ) {
return $this;
}
}
$this->rules[] = $rule;
return $this;
}
/**
* Identify all the sets that contain the given object
*
* @param \WP_Post|\WP_Term $post_or_term
* @param string $return_type what to return - objects or IDs
*
* @return array
*/
public static function identify_from_object( $post_or_term, $return_type = 'objects' ) {
$sets = array();
if ( ! $post_or_term instanceof \WP_Post && ! $post_or_term instanceof \WP_Term ) {
return $sets;
}
/**
* @var Set $set
*/
foreach ( static::get_items() as $set ) {
if ( $set->has_matching_rules( $post_or_term ) ) {
$sets[] = $return_type === 'objects' ? $set : $set->ID;
}
}
return $sets;
}
/**
* @return int|\WP_Error
*/
public function create() {
$rules = $this->prepare_rules_for_db();
$valid = ! empty( $this->post_title ) && is_array( $rules );
if ( ! $valid ) {
return 0;
}
/**
* We need to make sure that fire_after_hooks is false because other plugins also call the create method on save_post hooks
*/
return wp_insert_post( array(
'post_title' => $this->post_title,
'post_content' => serialize( $rules ),
'post_type' => static::POST_TYPE,
'post_status' => 'publish',
), false, false );
}
/**
* @return array|false|\WP_Post|null
*/
public function delete() {
/**
* Fired before completely deleting a content set from the database.
*
* @param Set $instance the content set instance
*/
do_action( 'tvd_content_set_before_delete', $this );
return wp_delete_post( $this->ID, true );
}
/**
* @return int|\WP_Error
*/
public function update() {
/**
* Triggered before a content set is updated
*
* @param Set $instance the content set instance
*/
do_action( 'tvd_content_set_before_update', new static( $this->ID ) );
$rules = $this->prepare_rules_for_db();
$valid = ! empty( $this->post_title ) && is_array( $rules );
if ( ! $valid ) {
return 0;
}
/**
* We need to make sure that fire_after_hooks is false because other plugins also call the update method on save_post hooks
*/
$result = wp_update_post( array(
'ID' => $this->ID,
'post_title' => $this->post_title,
'post_content' => serialize( $rules ),
), false, false );
if ( ! is_wp_error( $result ) ) {
/**
* Triggered before a content set is updated
*
* @param Set $instance the content set instance
*/
do_action( 'tvd_content_set_after_update', $this );
}
return $result;
}
/**
* Returns the rules if the rules are valid or false otherwise
* Prepares the rules for database
*
* @return bool|array
*/
private function prepare_rules_for_db() {
$valid = true;
$rules = array();
/**
* @var Post_Rule|Term_Rule $rule
*/
foreach ( $this->rules as $rule ) {
if ( ! $rule->is_valid() ) {
$valid = false;
break;
}
$rules[] = $rule->jsonSerialize( false );
}
if ( $valid ) {
return $rules;
}
return false;
}
public function get_tva_courses_ids() {
$id_pairs = array();
foreach ( $this->rules as $rule ) {
if ( true === $rule instanceof Term_Rule && $rule->get_content() === 'tva_courses' ) {
if ( $rule->field === Rule::FIELD_TITLE ) {
$entries = $rule->get_value();
} else {
//Dynamic stuff
//Fetching courses based on dynamic properties -> such as difficulty, label, topic or author
$course_terms = get_terms( [
'taxonomy' => 'tva_courses',
'hide_empty' => false,
'meta_query' => [
'tva_status' => [
'key' => 'tva_status',
'value' => 'private',
'compare' => '!=',
],
],
] );
$entries = [];
foreach ( $course_terms as $course_term ) {
if ( $rule->matches( $course_term ) ) {
$entries[] = [ 'id' => $course_term->term_id ];
}
}
}
if ( ! empty( $entries ) && is_array( $entries ) && is_array( $entries[0] ) && array_key_exists( 'id', $entries[0] ) ) {
$entries = array_column( $entries, 'id' );
}
$id_pairs [] = empty( $entries ) || ! is_array( $entries ) ? [] : $entries;
}
}
if ( empty( $id_pairs ) ) {
return array();
}
return array_merge( ...$id_pairs );
}
/**
* @return array
*/
#[\ReturnTypeWillChange]
public function jsonSerialize() {
return array(
'ID' => $this->ID,
'post_title' => $this->post_title,
'post_content' => $this->rules,
);
}
/**
* Identify the content sets that have rules matching the current request and store it in a local cache for further calls during this request
*
* @return int[]
*/
public static function get_for_request() {
if ( ! did_action( 'parse_query' ) && ! wp_doing_ajax() ) {
trigger_error( 'Content Sets: get_for_request() called incorrectly. It must be called after the `parse_query` hook', E_USER_WARNING );
return [];
}
if ( wp_doing_ajax() ) {
// TODO find a way to reliably match the main request
}
/* search in local cache first */
if ( static::$matched !== null ) {
return static::$matched;
}
/* nothing in the local cache, compute it */
static::$matched = [];
/* currently, only supports taxonomy terms and posts */
if ( Utils::is_context_supported() ) {
$queried_object = get_queried_object();
static::$matched = empty( $queried_object ) ? static::get_for_non_object() : static::get_for_object( $queried_object );
}
return static::$matched;
}
/**
* @param \WP_Post|\WP_Term $object
* @param int|null $id
*/
public static function get_for_object( $object, $id = null ) {
if ( $id === null ) {
$id = get_queried_object_id();
}
try {
$cache = content_set_cache( $object );
} catch ( Cache_Exception $e ) {
/* when receiving invalid data, make sure the execution continues */
return [];
}
$matched = $cache->get_or_store( $id, static function () use ( $object ) {
return static::identify_from_object( $object, 'ids' );
} );
if ( $cache->hit() ) {
/**
* This generates unused queries
* The purpose of this functionality is to fetch content sets with dynamic data - such as pusblied_date
* This functionality was removed from the initial content sets release
*/
// /**
// * We need to find all sets that are matched and the rule contains the time related rules
// */
// $sets = static::get_items( array( 'post__in' => $matched, 's' => Rule::FIELD_PUBLISHED_DATE ) );
//
// /**
// * For time related rules we apply again the match logic
// *
// * @var $set Set
// */
// foreach ( $sets as $set ) {
// if ( ! $set->has_matching_rules( $object ) ) {
// /**
// * If the rules has no matches, we exclude it from the matched list
// */
// $index = array_search( $set->ID, $matched );
// unset( $matched[ $index ] );
// }
// }
$matched = array_values( $matched ); //We need this to reset the indexes
}
return $matched;
}
/**
* Returns sets with dynamic contexts
* Ex: sets that have Search Result Page or Blog Page as rules.
*
* @return array
*/
public static function get_for_non_object() {
$sets = array();
/**
* @var null|\WP_User $maybe_user
*/
$maybe_user = null;
if ( is_author() ) {
$maybe_user = get_user_by( 'slug', get_query_var( 'author_name' ) );
}
foreach ( static::get_items() as $set ) {
if ( $set->has_matching_rules( $maybe_user ) ) {
$sets[] = $set->ID;
}
}
return $sets;
}
}

View File

@@ -0,0 +1,227 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-dashboard
*/
namespace TVD\Content_Sets;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden!
}
/**
* Class Term_Rule
*
* @package TVD\Content_Sets
* @project : thrive-dashboard
*/
class Term_Rule extends Rule {
private $option_fields
= array(
'tva_courses' => array(
self::FIELD_DIFFICULTY,
self::FIELD_TOPIC,
self::FIELD_LABEL,
),
);
/**
* Should be extended in child classes
*
* @param string $query_string
* @param bool|int $paged if non-false, it will return limited results
* @param int $per_page number of results per page. ignored if $paged = false
*
* @return array
*/
public function get_items( $query_string = '', $paged = false, $per_page = 15 ) {
/**
* Needed for the filter
*/
$this->query_string = $query_string;
$response = array();
if ( $this->should_search_options() ) {
$response = $this->search_option_fields();
} elseif ( $this->should_search_users() ) {
$response = parent::search_users();
} else {
add_filter( 'terms_clauses', array( $this, 'filter_terms_clauses' ), 10, 3 );
$query = new \WP_Term_Query();
$args = array(
'taxonomy' => $this->content,
'hide_empty' => 0,
);
if ( $paged !== false ) {
$args['number'] = $per_page;
$args['offset'] = ( $paged - 1 ) * $per_page;
}
$terms = $query->query( $args );
remove_filter( 'terms_clauses', array( $this, 'filter_terms_clauses' ), 10 );
foreach ( $terms as $term ) {
/**
* Allow other plugins to hook here and remove the term from the content set dropdown
*
* @param boolean return value
* @param \WP_Term $term
*/
if ( ! apply_filters( 'tvd_content_sets_allow_select_term', true, $term ) ) {
continue;
}
$response[] = array(
'id' => $term->term_id,
'text' => $this->alter_frontend_title( $term->name, $this->get_term_status( $term ) ),
);
}
}
return $response;
}
/**
* @param \WP_Term $term
*
* @return string
*/
private function get_term_status( $term ) {
/**
* Returns the status for the term
*
* @param string $status
* @param \WP_Term $term
*/
return apply_filters( 'tvd_content_sets_get_term_status', 'publish', $term );
}
/**
* @param $pieces
* @param $taxonomies
* @param $args
*
* @return mixed
*/
public function filter_terms_clauses( $pieces, $taxonomies, $args ) {
global $wpdb;
if ( $this->field === self::FIELD_TITLE ) {
$operation = $this->operator === self::OPERATOR_IS ? 'LIKE' : 'NOT LIKE';
$pieces['where'] .= ' AND lower(name) ' . $operation . ' ' . '\'%' . esc_sql( $wpdb->esc_like( strtolower( $this->query_string ) ) ) . '%\' ';
}
return $pieces;
}
/**
* Prepares the item for front-end
*
* @param int $item
*
* @return array
*/
public function get_frontend_item( $item ) {
if ( $this->should_search_options() ) {
$items = $this->get_option_fields();
if ( empty( $items ) || empty( $items[ $item ] ) ) {
return array();
}
return array(
'id' => (string) $item,
'text' => $items[ $item ],
);
}
if ( $this->should_search_users() ) {
return parent::get_frontend_user( $item );
}
return $this->get_frontend_term( $item );
}
/**
* Constructs the item from a term, needed for front-end
*
* @param int $item
*
* @return array
*/
public function get_frontend_term( $item ) {
$term = get_term( $item );
if ( empty( $term ) || is_wp_error( $term ) ) {
return array();
}
return array(
'id' => $term->term_id,
'text' => $this->alter_frontend_title( $term->name, $this->get_term_status( $term ) ),
);
}
/**
* @return array
*/
private function search_option_fields() {
$items = $this->get_option_fields();
$response = array();
foreach ( $items as $ID => $title ) {
if ( stripos( $title, $this->query_string ) !== false ) {
$response[] = array(
'id' => (string) $ID, //can be also 0 from the DB
'text' => $title,
);
}
}
return $response;
}
/**
* @return array
*/
private function get_option_fields() {
return apply_filters( 'tvd_content_sets_get_option_fields', [], $this );
}
/**
* Returns true if the system should search the option table for values
*
* @return bool
*/
private function should_search_options() {
return ! empty( $this->option_fields[ $this->content ] ) && in_array( $this->field, $this->option_fields[ $this->content ] );
}
/**
* Test if a rule matches the given params
*
* @param int|string $value
* @param \WP_Post|\WP_Term $post_or_term
*
* @return bool
*/
public function match_value( $value, $post_or_term ) {
if ( $this->should_search_options() || $this->should_search_users() ) {
$field_value = apply_filters( 'tvd_content_sets_field_value', '', $this, $post_or_term );
if ( $this->operator === self::OPERATOR_IS ) {
return in_array( $field_value, $this->value );
}
return ! in_array( $field_value, $this->value );
}
return parent::match_value( $value, $post_or_term );
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Thrive Themes - https://thrivethemes.com
*
* @package thrive-dashboard
*/
namespace TVD\Content_Sets;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden!
}
/**
* Class Set_Utils
*
* @package TVD\Content_Sets
* @project : thrive-dashboard
*/
class Utils {
/**
* Currently, only supports taxonomy terms and posts
*
* @return bool
*/
public static function is_context_supported() {
return is_singular() || is_tax() || is_category() || is_home() || is_search() || is_author();
}
/**
* @param \WP_Post| \WP_Term $post_or_term
*
* @return array
*/
public static function get_post_or_term_parts( $post_or_term ) {
$type = '';
$id = 0;
if ( $post_or_term instanceof \WP_Post ) {
$type = $post_or_term->post_type;
$id = $post_or_term->ID;
} else if ( $post_or_term instanceof \WP_Term ) {
$type = $post_or_term->taxonomy;
$id = $post_or_term->term_id;
}
return array(
$type,
$id,
);
}
}