File: /var/www/html/wp-content/plugins/jet-engine/framework/macros/macros-handler.php
<?php
/**
* Macros handler module
*
* Version: 2.0.0
*/
namespace Crocoblock;
// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
die;
}
if ( ! class_exists( '\Crocoblock\Macros_Handler' ) ) {
class Macros_Handler {
private $macros_context = null;
private $fallback = null;
private $before = null;
private $after = null;
private $filter = null;
private $macros_list = null;
private $escaped_macros_list = null;
private $namespace = null;
/**
* Setup namespace
*
* @param string $namespace Namespace of the current instance,
* which allows to call unique custom registration hooks
*/
public function __construct( $namespace = null ) {
$this->namespace = $namespace;
}
/**
* Bulk register macros list
*
* Macros list format:
*
* [
* 'macros_tag' => [
* 'label' => 'Macros Name',
* 'cb' => 'callback_function_name',
* 'args' => [
* 'arg_1' => [
* 'label' => __( 'Field', 'jet-engine' ),
* 'type' => 'select',
* 'options' => $options, // This could be an array or callable function to retrieve options list only for UI
* ]
* 'arg_2' => [
* 'label' => __( 'Return', 'jet-engine' ),
* 'type' => 'text',
* ],
* ],
* ],
* ]
*
* Triggers hook $this->namespace . '/register-macros' to register custom macros for you handler instance
* On hook you need to register macros by using Macros_Handler::register_macros();
*
* @param array $macros_list array of macros list to register
* @return void
*/
public function register_macros_list( $macros_list = [] ) {
if ( $this->namespace ) {
/**
* Custom callback to register current instance macros based on namespace
*/
do_action( $this->namespace . '/register-macros', $this );
}
if ( null === $this->macros_list ) {
$this->macros_list = $macros_list;
} else {
$this->macros_list = array_merge( $this->macros_list, $macros_list );
}
uasort( $this->macros_list, function( $a, $b ) {
$name_a = ( is_array( $a ) && isset( $a['label'] ) ) ? $a['label'] : $this->to_string( $a );
$name_b = ( is_array( $b ) && isset( $b['label'] ) ) ? $b['label'] : $this->to_string( $b );
if ( $name_a == $name_b ) {
return 0;
}
return ( $name_a < $name_b ) ? -1 : 1;
} );
}
/**
* Register single macros into the current handler instance.
* This Method should be used to adding any new macros for your instance after bul registration is done
*
* @param object $macros_object Macros object
* @return [type] [description]
*/
public function register_macros( $macros_object ) {
$macros_data = array(
'label' => $macros_object->macros_name(),
'cb' => [ $macros_object, '_macros_callback' ],
);
$args = $macros_object->get_macros_args();
if ( ! empty( $args ) ) {
$macros_data['args'] = $args;
}
$this->macros_list[ $macros_object->macros_tag() ] = $macros_data;
}
/**
* Returns plain $this->macros_list.
*
* If you added any arguments where options is set as callback function -
* this options will return not preapred to use in UI.
*
* This method should be used anywhere where you need to get macros list without macros insertion UI
*
* @return array
*/
public function get_raw_list() {
return $this->macros_list;
}
/**
* Returns macros list where arguments options already prepared to use in UI.
*
* This method requires more resources, so should be used only in cases when you need macros list
* with prepared arguments to use in some macros insertion UI
*
* @return array
*/
public function get_escaped_list() {
$macros_list = $this->get_raw_list();
if ( empty( $macros_list ) ) {
return [];
}
if ( null === $this->escaped_macros_list ) {
foreach ( $macros_list as $key => $macros ) {
if ( ! empty( $macros['args'] ) ) {
foreach ( $macros['args'] as $arg => $data ) {
if ( ! empty( $data['options'] ) && is_callable( $data['options'] ) ) {
$data['options'] = call_user_func( $data['options'] );
$macros['args'][ $arg ] = $data;
$macros_list[ $key ] = $macros;
}
if ( ! empty( $data['groups'] ) && is_callable( $data['groups'] ) ) {
$data['groups'] = call_user_func( $data['groups'] );
$macros['args'][ $arg ] = $data;
$macros_list[ $key ] = $macros;
}
}
}
}
$this->escaped_macros_list = $macros_list;
}
return $this->escaped_macros_list;
}
/**
* Returns macros list prepared to use as JS options list
*
* @return array
*/
public function get_macros_for_js() {
$res = array();
foreach ( $this->get_escaped_list() as $macros_id => $data ) {
$macros_data = array(
'id' => $macros_id,
);
if ( ! is_array( $data ) || empty( $data['label'] ) ) {
$macros_data['name'] = $macros_id;
} elseif ( ! empty( $data['label'] ) ) {
$macros_data['name'] = $data['label'];
}
if ( is_array( $data ) && ! empty( $data['args'] ) ) {
$macros_data['controls'] = $data['args'];
}
$res[] = $macros_data;
}
return $res;
}
/**
* Set context to get current macros data in.
* This method is usable in combination with jet_engine plugin,
* which could process custom contexts by string name
*
* Without jet_engine, when using this method you need to pass $context as object to use it later
*
* @param string|object $context [description]
*/
public function set_macros_context( $context = null ) {
$this->macros_context = $context;
}
/**
* Returns macros context saved in the current instance.
*
* @return string|object $context Context object or a context name for JetEngine
*/
public function get_macros_context() {
return $this->macros_context;
}
/**
* Set fallback value for macros. Fallback will be returned if macros itself return nothing
*
* @param string $fallback Value to return if macros return an empty value
*/
public function set_fallback( $fallback = null ) {
$this->fallback = $fallback;
}
/**
* Return fallback value stored in the current instance
*
* @return string $fallback Value to return if macros value is empty
*/
public function get_fallback() {
return $this->fallback;
}
/**
* Set text to add before raw macros value when returning macros result
*
* @param string $before Text to add
*/
public function set_before( $before = null ) {
$this->before = $before;
}
/**
* Return text to add before macros value stored in the current handler instance
*
* @return string
*/
public function get_before() {
return $this->before;
}
/**
* Set text to add after raw macros value when returning macros result
*
* @param string $after Text to add
*/
public function set_after( $after = null ) {
$this->after = $after;
}
/**
* Return text to add after macros value stored in the current handler instance
*
* @return string
*/
public function get_after() {
return $this->after;
}
/**
* Get filter callback and parameters
*
* @param string $filter Filter to apply
*/
public function set_filter( $filter = null ) {
$this->filter = $filter;
}
/**
* Get filter callback and parameters
*
* @param string Filter to apply
*/
public function get_filter() {
return $this->filter;
}
/**
* Is $str is array - returns 0, in other cases returns $str
*
* @param mixed $str
* @return mixed
*/
public function to_string( $str ) {
if ( is_array( $str ) ) {
return 0;
} else {
return $str;
}
}
/**
* Get macros list for options in raw 'macros' => 'label' format
*
* @return array
*/
public function get_macros_list_for_options() {
$all = $this->get_raw_list();
$result = array();
if ( empty( $all ) ) {
return $result;
}
foreach ( $all as $key => $data ) {
if ( is_array( $data ) ) {
$result[ $key ] = ! empty( $data['label'] ) ? $data['label'] : $key;
} else {
$result[ $key ] = $key;
}
}
return $result;
}
/**
* Return verbosed macros list
*
* @return string
*/
public function verbose_macros_list() {
$macros = $this->get_raw_list();
$result = '';
$sep = '';
foreach ( $macros as $key => $data ) {
$result .= $sep . '%' . $key . '%';
$sep = ', ';
}
return $result;
}
/**
* Return current macros object based on the stored context
*
* @return object|null
*/
public function get_macros_object() {
$context = $this->get_macros_context();
if ( is_object( $context ) ) {
return $context;
}
if ( function_exists( 'jet_engine' ) ) {
if ( ! $context || 'default_object' === $context ) {
$object = jet_engine()->listings->data->get_current_object();
} else {
$object = jet_engine()->listings->data->get_object_by_context( $this->macros_context );
}
} else {
$object = get_queried_object();
}
return $object;
}
/**
* Call macros callback by macros name and args array
*
* @param [type] $macros [description]
* @param array $args [description]
* @return [type] [description]
*/
public function call_macros_func( $macros, $args = array() ) {
$all_macros = $this->get_raw_list();
if ( empty( $all_macros[ $macros ] ) ) {
return;
}
$macros_data = $all_macros[ $macros ];
$prepared_args = array( false );
$custom_args = array();
if ( is_callable( $macros_data ) ) {
return call_user_func_array( $macros_data, $prepared_args );
}
if ( ! empty( $macros_data['args'] ) ) {
foreach ( array_keys( $macros_data['args'] ) as $arg ) {
$custom_args[] = isset( $args[ $arg ] ) ? $args[ $arg ] : null;
}
}
$prepared_args[] = implode( '|', $custom_args );
return call_user_func_array( $macros_data['cb'], $prepared_args );
}
/**
* Check if given value is empty
*
* @param [type] $source [description]
* @param [type] $key [description]
* @return boolean [description]
*/
public function is_empty( $source = null, $key = null ) {
if ( class_exists( '\Jet_Engine_Tools' ) ) {
return \Jet_Engine_Tools::is_empty( $source, $key );
} elseif ( is_array( $source ) && $key ) {
if ( ! isset( $source[ $key ] ) ) {
return true;
}
$source = $source[ $key ];
}
return empty( $source ) && '0' !== $source;
}
/**
* Works in similar way like ltrim() but remove only first appearance of required char.
* @see https://github.com/Crocoblock/suggestions/issues/7902
* @return string
*/
public function ltrim( $string = '', $char = '' ) {
if ( $string && $char && $char === $string[0] ) {
return substr( $string, 1 );
}
return $string;
}
/**
* Apply filtered function to value
*
* @param mixed $value Value to filter
* @param mixed Filtered value
*/
public function get_filtered_value( $value, $filter ) {
if ( $filter && function_exists( 'jet_engine' ) ) {
$value = jet_engine()->listings->filters->apply_filters( $value, $filter );
} elseif ( is_callable( $filter ) ) {
$value = call_user_func( $filter, $value );
}
return $value;
}
/**
* Find and replace macros inside string
*
* @param [type] $string [description]
* @param [type] $field_value [description]
* @return [type] [description]
*/
public function do_macros( $string = '', $field_value = null ) {
if ( empty( $string ) ) {
return $string;
}
$macros = $this->get_raw_list();
return preg_replace_callback(
'/%([a-z_-]+)(\|(?:\[.*?\]|[a-zA-Z0-9_\-\,\.\+\:\/\s\(\)|\[\]\'\"=\{\}&\p{Sc}]+))?%(\{.*?\})?/u',
function( $matches ) use ( $macros, $field_value ) {
$found = $matches[1];
if ( ! isset( $macros[ $found ] ) ) {
return $matches[0];
}
$cb = $macros[ $found ];
if ( is_array( $cb ) && isset( $cb['cb'] ) ) {
$cb = ! empty( $cb['cb'] ) ? $cb['cb'] : false;
if ( ! $cb ) {
return $matches[0];
}
}
if ( ! is_callable( $cb ) ) {
return $matches[0];
}
$args = isset( $matches[2] ) ? $this->ltrim( $matches[2], '|' ) : false;
$config = isset( $matches[3] ) ? json_decode( $matches[3], true ) : false;
// Store the initial configs
$initial_fallback = $this->get_fallback();
$initial_context = $this->get_macros_context();
$initial_before = $this->get_before();
$initial_after = $this->get_after();
$initial_filter = $this->get_filter();
// Reset the configs except macros context.
$this->set_fallback( null );
$this->set_before( null );
$this->set_after( null );
$this->set_filter( null );
// Set the config of current macro
if ( $config ) {
if ( ! empty( $config['context'] ) ) {
$this->set_macros_context( $config['context'] );
}
if ( ! $this->is_empty( $config, 'fallback' ) ) {
$this->set_fallback( $config['fallback'] );
}
if ( ! $this->is_empty( $config, 'before' ) ) {
$this->set_before( $config['before'] );
}
if ( ! $this->is_empty( $config, 'after' ) ) {
$this->set_after( $config['after'] );
}
if ( ! $this->is_empty( $config, 'filter' ) ) {
$this->set_filter( $config['filter'] );
}
}
$result = call_user_func( $cb, $field_value, $args );
$fallback = $this->get_fallback();
$before = $this->get_before();
$after = $this->get_after();
$filter = $this->get_filter();
$result = $this->get_filtered_value( $result, $filter );
$is_empty_result = empty( $result );
/*
* If the result of macro is `not-found` or array( 'not-found' )
* and the fallback value is not empty, then the fallback value is returned.
*
* See: https://github.com/Crocoblock/issues-tracker/issues/3243
*/
if ( ! $is_empty_result ) {
$is_not_found_result = ( ( is_array( $result ) && in_array( 'not-found', $result ) ) || ( ! is_array( $result ) && 'not-found' === $result ) );
if ( $is_not_found_result && ! $this->is_empty( $fallback ) ) {
$is_empty_result = true;
}
}
if ( ! $is_empty_result ) {
if ( is_array( $result ) ) {
$result = implode( ',', $result );
}
if ( $before ) {
$result = $before . $result;
}
if ( $after ) {
$result .= $after;
}
} elseif ( ! $this->is_empty( $fallback ) ) {
$result = $fallback;
}
// Set the initial configs
$this->set_fallback( $initial_fallback );
$this->set_macros_context( $initial_context );
$this->set_before( $initial_before );
$this->set_after( $initial_after );
$this->set_filter( $initial_filter );
return apply_filters( 'jet-engine/macros/macros-result', $result, $cb );
}, $string
);
}
}
}