File: /var/www/html/wp-content/plugins/jet-engine/includes/components/query-builder/queries/repeater.php
<?php
namespace Jet_Engine\Query_Builder\Queries;
use Jet_Engine\Query_Builder\Manager;
class Repeater_Query extends Base_Query {
public $_instance_fields = array();
private $parent_object = null;
private $object_id = null;
use Traits\Meta_Query_Trait;
public function maybe_setup_repeater_preview() {
if ( ! \Jet_Engine_Listings_Preview::$is_preview ) {
return;
}
$post_id = ! empty( $this->preview['post_id'] ) ? $this->preview['post_id'] : false;
if ( ! $post_id ) {
return;
}
$this->setup_current_object( get_post( $post_id ) );
if ( ! empty( $this->preview['query_string'] ) ) {
parse_str( $this->preview['query_string'], $query_array );
if ( ! empty( $query_array ) ) {
foreach ( $query_array as $key => $value ) {
$_GET[ $key ] = $value;
$_REQUEST[ $key ] = $value;
}
}
}
// Update query
$this->setup_query();
}
/**
* Ensure `Jet_Engine_Queried_Repeater_Item` class is included
*/
public function include_queried_repeater_item() {
if ( ! class_exists( '\Jet_Engine_Queried_Repeater_Item' ) ) {
require_once jet_engine()->plugin_path( 'includes/classes/repeater-item.php' );
}
}
/**
* Returns queried items array
*
* @return array
*/
public function get_items() {
// Before get items, ensure `Jet_Engine_Queried_Repeater_Item` class is included
$this->include_queried_repeater_item();
return parent::get_items();
}
/**
* Returns queries items
*
* @return [type] [description]
*/
public function _get_items( $count = false ) {
$this->maybe_setup_repeater_preview();
$items = array();
$args = $this->final_query;
$object_id = ! empty( $args['object_id'] ) ? $args['object_id'] : false;
$this->setup_current_object( $object_id );
$current_object_id = jet_engine()->listings->data->get_current_object_id();
switch ( $args['source'] ) {
case 'jet_engine':
$field = ! empty( $args['jet_engine_field'] ) ? $args['jet_engine_field'] : '';
$field_data = explode( '::', $field );
if ( ! empty( $field_data[1] ) ) {
$items = jet_engine()->listings->data->get_meta( $field_data[1] );
}
break;
case 'jet_engine_option':
$field = ! empty( $args['jet_engine_option_field'] ) ? $args['jet_engine_option_field'] : '';
if ( ! empty( $field ) ) {
$items = jet_engine()->listings->data->get_option( $field );
}
$current_object_id = 0;
break;
case 'custom':
$field = ! empty( $args['custom_field'] ) ? $args['custom_field'] : '';
if ( $field ) {
$items = jet_engine()->listings->data->get_meta( $field );
if ( ! empty( $items ) && ! is_array( $items ) ) {
$items = json_decode( $items );
}
$items = wp_unslash( $items );
}
$current_object_id = jet_engine()->listings->data->get_current_object_id();
break;
default:
$items = apply_filters(
'jet-engine/query-builder/types/repeater-query/items/' . $args['source'],
$items, $args, $this
);
break;
}
$this->reset_current_object( $object_id );
if ( empty( $items ) || ! is_array( $items ) ) {
$items = array();
}
$this->include_queried_repeater_item();
if ( ! empty( $args['meta_query'] ) ) {
$args['meta_query'] = $this->prepare_meta_query_args( $args );
}
$items = array_values( $items );
$items = array_filter( array_map( function( $item, $index ) use ( $args, $current_object_id ) {
$item = (object) $item;
if ( ! $this->is_item_met_requested_args( $item, $args ) ) {
return false;
}
return new \Jet_Engine_Queried_Repeater_Item( $item, $index, $current_object_id, $this->id );
}, $items, array_keys( $items ) ) );
$this->apply_sorting( $items, $args );
// Pagination
$limit = $this->get_items_per_page();
$offset = ! empty( $this->final_query['offset'] ) ? absint( $this->final_query['offset'] ) : 0;
if ( ! $count ) {
$page = $this->get_current_items_page();
$offset = $offset + ( $page - 1 ) * $limit;
}
$limit = ( ! empty( $limit ) && ! $count ) ? $limit : null;
$items = array_slice( $items, $offset, $limit );
if ( empty( $items ) ) {
$items = array();
}
return array_values( $items );
}
/**
* Apply sorting to items array
*
* This function sorts the given items array based on specified criteria.
*
* @param array &$items The array of items to be sorted.
* @return void
*/
public function apply_sorting( &$items ) {
$orderby = ! empty( $this->final_query['orderby'] ) ? $this->final_query['orderby'] : false;
if ( ! $orderby ) {
return;
}
usort( $items, function ( $a, $b ) use ( $orderby ) {
foreach ( $orderby as $order ) {
$_by = ! empty( $order['orderby'] ) ? $order['orderby'] : 'index';
$field = ! empty( $order['field_name'] ) ? $order['field_name'] : 'index';
$type = ! empty( $order['order_type'] ) ? strtolower( $order['order_type'] ) : 'numeric';
$direction = ! empty( $order['order'] ) ? strtolower( $order['order'] ) : 'asc';
// Retrieve the field values
if ( 'index' === $_by ) {
$value_a = $a->get_index();
$value_b = $b->get_index();
$type = 'numeric'; // Force numeric type for index sorting.
} else {
$value_a = $a->$field;
$value_b = $b->$field;
}
switch ( $type ) {
case 'numeric':
$value_a = floatval( $value_a );
$value_b = floatval( $value_b );
break;
case 'alphabetical':
$value_a = strtolower( $value_a );
$value_b = strtolower( $value_b );
break;
case 'dates':
$value_a = strtotime( $value_a );
$value_b = strtotime( $value_b );
break;
}
// If the values are different, decide order based on the 'order' property
if ( $value_a < $value_b ) {
return ( $direction === 'asc' ) ? -1 : 1;
} elseif ( $value_a > $value_b ) {
return ( $direction === 'asc' ) ? 1 : -1;
}
// If equal, move to the next order condition
}
// All compared fields are equal
return 0;
});
}
/**
* Check if item meets requested meta query args
*
* @param object $item Repeater item.
* @param array $args Query arguments to check the item against.
* @return array
*/
public function is_item_met_requested_args( $item, $args ) {
$result = true;
if ( empty( $args['meta_query'] ) ) {
return $result;
}
$meta_query = $args['meta_query'];
$relation = ! empty( $meta_query['relation'] ) ? strtolower( $meta_query['relation'] ) : 'and';
unset( $meta_query['relation'] );
if ( empty( $meta_query ) ) {
return $result;
}
$result = 'or' !== $relation;
foreach ( $meta_query as $clause ) {
if ( ! empty( $clause['relation'] ) ) {
$matched = $this->is_item_met_requested_args( $item,
array(
'meta_query' => $clause,
)
);
} else {
$key = ! empty( $clause['key'] ) ? $clause['key'] : '';
if ( ! $key ) {
continue;
}
$compare = ! empty( $clause['compare'] ) ? $clause['compare'] : '=';
$value = ! \Jet_Engine_Tools::is_empty( $clause, 'value' ) ? $clause['value'] : '';
$matched = false;
switch ( $compare ) {
case '=':
if ( isset( $item->$key ) ) {
$matched = $item->$key == $value;
}
break;
case '!=' :
if ( isset( $item->$key ) ) {
$matched = $item->$key != $value;
} else {
$matched = true;
}
break;
case '>':
if ( isset( $item->$key ) ) {
$matched = $item->$key > $value;
}
break;
case '>=':
if ( isset( $item->$key ) ) {
$matched = $item->$key >= $value;
}
break;
case '<':
if ( isset( $item->$key ) ) {
$matched = $item->$key < $value;
}
break;
case '<=':
if ( isset( $item->$key ) ) {
$matched = $item->$key <= $value;
}
break;
case 'LIKE':
if ( isset( $item->$key ) ) {
if ( is_array( $item->$key ) ) { // for Checkbox, Select2 ... fields
$matched = in_array( $value, $item->$key );
} else {
$matched = false !== strpos( $item->$key, $value );
}
}
break;
case 'NOT LIKE':
if ( isset( $item->$key ) ) {
if ( is_array( $item->$key ) ) { // for Checkbox, Select2 ... fields
$matched = ! in_array( $value, $item->$key );
} else {
$matched = false === strpos( $item->$key, $value );
}
} else {
$matched = true;
}
break;
case 'IN':
if ( isset( $item->$key ) ) {
$value = $this->value_to_array( $value );
$matched = in_array( $item->$key, $value );
}
break;
case 'NOT IN':
if ( isset( $item->$key ) ) {
$value = $this->value_to_array( $value );
$matched = ! in_array( $item->$key, $value );
} else {
$matched = true;
}
break;
case 'BETWEEN':
if ( isset( $item->$key ) ) {
$value = $this->value_to_array( $value );
if ( isset( $value[1] ) ) {
$matched = ( $value[0] <= $item->$key && $item->$key <= $value[1] );
}
}
break;
case 'NOT BETWEEN':
if ( isset( $item->$key ) ) {
$value = $this->value_to_array( $value );
if ( isset( $value[1] ) ) {
$matched = ( $value[0] > $item->$key || $item->$key > $value[1] );
}
} else {
$matched = true;
}
break;
case 'EXISTS':
$matched = ! empty( $item->$key );
break;
case 'NOT EXISTS':
$matched = empty( $item->$key );
break;
case 'REGEXP':
if ( isset( $item->$key ) ) {
$subject = $item->$key;
if ( is_array( $item->$key ) ) {
// Serialize item value if filtered by checkbox
if ( false !== strpos( $value, ';s:4:"true"' ) ) {
$subject = maybe_serialize( $item->$key );
} else {
$subject = json_encode( $item->$key );
}
}
$value = trim( $value, '/' );
preg_match( '/' . $value . '/', $subject, $matched );
$matched = ! empty( $matched );
}
break;
case 'NOT REGEXP':
if ( isset( $item->$key ) ) {
if ( is_array( $item->$key ) ) {
$item->$key = json_encode( $item->$key );
}
$value = trim( $value, '/' );
preg_match( '/' . $value . '/', $item->$key, $matched );
$matched = empty( $matched );
} else {
$matched = true;
}
break;
}
}
if ( 'or' === $relation && $matched ) {
$result = true;
} elseif ( 'or' !== $relation && ! $matched ) {
$result = false;
}
}
return $result;
}
public function value_to_array( $value ) {
if ( ! is_array( $value ) ) {
$value = explode( ',', $value );
$value = array_map( 'trim', $value );
}
return $value;
}
public function setup_current_object( $object ) {
if ( ! $object ) {
// Added to clear a query cache if a repeater listing is inside another listing.
$this->apply_macros( '%current_id%' );
return;
}
if ( ! is_object( $object ) ) {
$this->object_id = absint( $object );
add_filter( 'jet-engine/listing/current-object-id', array( $this, 'replace_object_id' ) );
if ( ! empty( $this->final_query['source'] )
&& 'jet_engine' === $this->final_query['source']
&& ! empty( $this->final_query['jet_engine_field'] )
) {
$field_data = explode( '::', $this->final_query['jet_engine_field'] );
if ( ! empty( $field_data[0] ) ) {
if ( taxonomy_exists( $field_data[0] ) ) {
$object = get_term( $object );
} else {
$object = get_post( $object );
}
}
if ( ! empty( $field_data['2'] ) && $field_data['2'] === 'user' ) {
$user = get_user_by( 'ID', $this->object_id );
if ( $user->ID ?? false ) {
$object = $user;
}
}
}
if ( ! is_object( $object ) ) {
$object = get_post( $object );
}
$object = apply_filters( 'jet-engine/query-builder/repeater-query/object-by-id', $object, $this );
}
/**
* If we get an array - let's assume it's what we need and just convert it to object.
* https://github.com/Crocoblock/issues-tracker/issues/11593
*/
if ( ! empty( $object ) && is_array( $object ) ) {
$object = (object) $object;
}
if ( is_object( $object ) ) {
$this->parent_object = jet_engine()->listings->data->get_current_object();
jet_engine()->listings->data->set_current_object( $object );
}
}
public function reset_current_object( $object ) {
if ( ! $object ) {
return;
}
if ( $this->object_id ) {
$this->object_id = null;
remove_filter( 'jet-engine/listing/current-object-id', array( $this, 'replace_object_id' ) );
}
if ( $this->parent_object ) {
jet_engine()->listings->data->set_current_object( $this->parent_object );
$this->parent_object = null;
}
}
public function replace_object_id( $object_id ) {
if ( $this->object_id ) {
return $this->object_id;
}
return $object_id;
}
public function get_object_id() {
return $this->object_id;
}
public function get_current_items_page() {
if ( empty( $this->final_query['page'] ) ) {
return 1;
}
return absint( $this->final_query['page'] );
}
/**
* Returns total found items count
*
* @return [type] [description]
*/
public function get_items_total_count() {
$cached = $this->get_cached_data( 'count' );
if ( false !== $cached ) {
return $cached;
}
$this->setup_query();
$items = $this->_get_items( true );
$result = count( $items );
$this->update_query_cache( $result, 'count' );
return $result;
}
/**
* Returns count of the items visible per single listing grid loop/page
* @return [type] [description]
*/
public function get_items_per_page() {
$this->setup_query();
$limit = 0;
if ( ! empty( $this->final_query['per_page'] ) ) {
$limit = absint( $this->final_query['per_page'] );
}
return $limit;
}
/**
* Returns queried items count per page
*
* @return [type] [description]
*/
public function get_items_page_count() {
$result = $this->get_items_total_count();
$per_page = $this->get_items_per_page();
if ( $per_page < $result ) {
$page = $this->get_current_items_page();
$pages = $this->get_items_pages_count();
if ( $page < $pages ) {
$result = $per_page;
} elseif ( $page == $pages ) {
$offset = ( $page - 1 ) * $per_page;
$result = $result - $offset;
}
}
return $result;
}
/**
* Returns queried items pages count
*
* @return [type] [description]
*/
public function get_items_pages_count() {
$per_page = $this->get_items_per_page();
$total = $this->get_items_total_count();
if ( ! $per_page || ! $total ) {
return 1;
}
return ceil( $total / $per_page );
}
/**
* Get fields list are available for the current instance of this query
*
* @return [type] [description]
*/
public function get_instance_fields() {
if ( ! empty( $this->_instance_fields ) ) {
return $this->_instance_fields;
}
$result = array();
$args = $this->query;
if ( empty( $args['source'] ) ) {
return $result;
}
switch ( $args['source'] ) {
case 'jet_engine':
$field = ! empty( $args['jet_engine_field'] ) ? $args['jet_engine_field'] : '';
$field_data = explode( '::', $field );
if ( ! empty( $field_data[1] ) ) {
$fields = jet_engine()->meta_boxes->get_meta_fields_for_object( $field_data[0] );
$result = $this->get_options_from_fields_data( $field_data[1], $fields );
}
break;
case 'jet_engine_option':
$field = ! empty( $args['jet_engine_option_field'] ) ? $args['jet_engine_option_field'] : '';
$field_data = explode( '::', $field );
if ( ! empty( $field_data[1] ) ) {
$page = isset( jet_engine()->options_pages->registered_pages[ $field_data[0] ] ) ? jet_engine()->options_pages->registered_pages[ $field_data[0] ] : false;
if ( $page ) {
$result = $this->get_options_from_fields_data( $field_data[1], $page->meta_box );
}
}
break;
case 'custom':
$fields_list = ! empty( $args['fields_list'] ) ? $args['fields_list'] : '';
$fields_list = explode( ',', str_replace( ', ', ',', $fields_list ) );
$result = array_combine( $fields_list, $fields_list );
break;
default:
$result = apply_filters(
'jet-engine/query-builder/types/repeater-query/fields/' . $args['source'],
$result, $args, $this
);
break;
}
$this->_instance_fields = $result;
return $result;
}
public function get_options_from_fields_data( $search_field, $all_fields ) {
$field_settings = $this->find_field( $search_field, $all_fields );
$result = array();
$fields = ! empty( $field_settings['repeater-fields'] ) ? $field_settings['repeater-fields'] : false;
$fields = ! $fields && ! empty( $field_settings['fields'] ) ? $field_settings['fields'] : $fields;
if ( ! $fields ) {
return $result;
}
foreach ( $fields as $field ) {
$label = ! empty( $field['title'] ) ? $field['title'] : false;
$label = ! $label && ! empty( $field['label'] ) ? $field['label'] : $field['name'];
$result[ $field['name'] ] = $label;
}
return $result;
}
public function find_field( $name, $fields ) {
foreach ( $fields as $field ) {
if ( isset( $field['name'] ) && $name === $field['name'] ) {
return $field;
}
}
return array();
}
/**
* Sets up the filtered properties of the repeater.
*
* @return void
*/
public function set_filtered_prop( $prop = '', $value = null ) {
switch ( $prop ) {
case '_page':
$this->final_query['page'] = $value;
break;
case '_items_per_page':
$this->final_query['per_page'] = $value;
break;
case 'meta_query':
$this->replace_meta_query_row( $value );
break;
case 'orderby':
case 'order':
case 'meta_key':
$this->set_filtered_order( $prop, $value );
break;
default:
$this->merge_default_props( $prop, $value );
break;
}
}
/**
* Set filtered order in the similar way like for the posts
*
* @param [type] $value [description]
* @return [type] [description]
*/
public function set_filtered_order( $key, $value ) {
if (
empty( $this->final_query['orderby'] )
|| ! isset( $this->final_query['orderby']['custom'] )
) {
$this->final_query['orderby'] = array( 'custom' => array( 'orderby' => 'field' ) );
}
if ( 'orderby' === $key ) {
$key = 'order_type';
$value = ( 'meta_value' === $value ) ? 'alphabetical' : 'numeric';
}
if ( 'meta_key' === $key ) {
$key = 'field_name';
}
$this->final_query['orderby']['custom'][ $key ] = $value;
}
}