shell bypass 403
GrazzMean Shell
: /home/nayff91c5tsx/www/wp-content/plugins/fluentformpro/src/Components/DynamicField/ [ drwx---r-x ]
<?php
namespace FluentFormPro\Components\DynamicField;
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly.
}
use FluentForm\App\App;
use FluentForm\App\Helpers\Helper;
use FluentForm\App\Modules\Form\FormFieldsParser;
use FluentForm\Framework\Database\Query\WPDBConnection;
use FluentForm\Framework\Helpers\ArrayHelper as Arr;
use FluentForm\Framework\Database\Query\Builder;
abstract class DynamicBase
{
protected $config;
/**
* Populate Source Name
* @var string
*/
protected $source;
/**
* Global $wpdb
* @var \wpdb
*/
protected $wpdb;
/**
* The model associated with this object.
*
* @var Builder
*/
protected $model;
/**
* The prefix used for filter keys.
*
* @var string
*/
protected $filterPrefix;
/**
* The result of filters.
*
* @var array
*/
protected $result = [];
/**
* Tables, maybe join.
*
* @var bool
*/
protected $joinTables = [];
/**
* Constructor for the class.
*
* @param string $source The populate key.
* @param string $tableName The table name for model.
* @param array $config The configuration.
* @return void
*/
public function __construct($source, $tableName = '', $joins = [], $config = [])
{
$this->source = $source;
$this->joinTables = $joins;
$this->config = $config;
/**
* Database instance
* @var $db WPDBConnection
*/
$db = App::getInstance('db');
$this->wpdb = $db->getWPDB();
if ($tableName) {
$this->model = $db->table($tableName);
}
$this->filterPrefix = "fluentform/dynamic_field_filter_{$this->source}";
}
/**
* Get the selectable columns for the query.
*
* @return array
*/
public abstract function selectableColumns();
/**
* Get the supported columns for the query.
*
* @return array
*/
public abstract function getSupportedColumns();
/**
* Retrieve the default configuration settings.
*
* @return array
*/
public abstract function getDefaultConfig();
/**
* Retrieve the value options.
*
* @return array
*/
public abstract function getValueOptions();
/**
* Set the configuration.
*
* @param mixed $config The configuration to set.
* @return void
*/
public function setConfig($config)
{
$this->config = $config;
}
/**
* Magic method to access object properties dynamically.
*
* @param string $name
* @return mixed|null The value of the property if it exists, otherwise null.
*/
public function __get($name)
{
if (property_exists($this, $name)) {
return $this->{$name};
}
return null;
}
/**
* Retrieves the result data including result counts and valid options.
*
* @return array The result data including total counts, valid counts, valid options, and all options.
*/
public function getResult()
{
$this->setResult();
$validOptions = $this->getAdvanceOptions();
return [
'result_counts' => [
'total' => count($this->result),
'valid' => count($validOptions),
],
'valid_options' => $validOptions,
'all_options' => $this->result,
];
}
/**
* Sets the result based on the configured filters.
*
* @return void
*/
public function setResult()
{
// Prepare filter groups
$filters = $this->formatAndSanitizeFilters();
// Clone the model to ensure a fresh instance
$query = clone $this->model;
$select = $this->selectableColumns();
// Adjust select and join tables if join tables filter present
foreach ($this->joinTables as $table) {
if (Arr::isTrue($table, 'enable') && $joinInfo = Arr::get($table, 'join')) {
$select = array_merge($select, Arr::get($table, 'columns'));
list ($table, $one, $operator, $two) = $joinInfo;
$query->join($table, $one, $operator, $two);
}
}
// Apply filter groups
$this->applyFiltersQuery($filters, $query);
// Apply sorting, ordering, and limit
$this->applySortingOrderAndLimit($query);
// Execute the query
$result = $query->get($select);
$this->result = $result ?: [];
}
/**
* Apply the filters to the query.
*
* This method applies the given filters to the query using dynamic WHERE clauses.
*
* @param array $filters The filters to apply.
* @return void
*/
protected function applyFiltersQuery($filters, $query)
{
$query->where(function ($query) use ($filters) {
foreach ($filters as $groups) {
$query->orWhere(function ($query) use ($groups) {
foreach ($groups as $group) {
list ($column, $operator, $value) = $group;
if (is_array($value)) {
if ('IN' === $operator) {
$query->whereIn($column, $value);
} elseif ('NOT IN' === $operator) {
$query->whereNotIn($column, $value);
} elseif ('BETWEEN' === $operator) {
$query->whereBetween($column, $value);
} elseif ('NOT BETWEEN' === $operator) {
$query->whereNotBetween($column, $value);
}
} else {
$query->where($column, $operator, $value);
}
}
});
}
});
}
/**
* Apply sorting, ordering, and limit clauses to the query based on the configuration.
*
* @return void
*/
protected function applySortingOrderAndLimit($query)
{
$defaultConfig = $this->getDefaultConfig();
$defaultSortBy = Arr::get($defaultConfig, 'sort_by');
// Ensure unique results by ID or term_id if enable
$uniqueResult = Arr::get($this->config, 'unique_result');
if ('yes' === $uniqueResult && $defaultSortBy) {
$query->groupBy($defaultSortBy);
}
// Sort by column
$sortBy = Arr::get($this->config, 'sort_by', $defaultSortBy);
if (!$this->isSupportedColumn($sortBy)) {
$sortBy = $defaultSortBy;
}
// If sorting by a join table column and there is no join table filter, use the default sort by column
foreach ($this->joinTables as $table) {
if (!Arr::isTrue($table, 'enable') && in_array($sortBy, Arr::get($table, 'columns'))) {
$sortBy = $defaultSortBy;
break;
}
}
// Order by
$defaultOrderBy = Arr::get($defaultConfig, 'order_by');
$orderBy = Arr::get($this->config, 'order_by', $defaultOrderBy);
if (!in_array($orderBy, ['ASC', 'DESC'])) {
$orderBy = $defaultOrderBy;
}
// Result Limit
$limit = intval(Arr::get($this->config, 'result_limit', 0));
if (!$limit) {
$limit = $this->getResultLimit();
}
$query->orderBy($sortBy, $orderBy)->limit($limit);
}
/**
* Format and sanitize the filters based on the configuration.
*
* This method prepares the filter groups, sanitizes them, and update tables join.
*
* @return array The formatted and sanitized filters.
*/
protected function formatAndSanitizeFilters()
{
if ('basic' === Arr::get($this->config, 'query_type')) {
$filters = apply_filters("fluentform/dynamic_field_basic_filters_{$this->source}", [], Arr::get($this->config, 'basic_query', []));
} else {
$filters = Arr::get($this->config, 'filters', []);
}
// Use default filters if not has a valid filter
if (empty($filters)) {
$filters = Arr::get($this->getDefaultConfig(), 'filters', []);
}
$formattedFilters = [];
foreach ($filters as $groupsIndex => $groups) {
if (!is_array($groups)) {
continue;
}
foreach ($groups as $group) {
if ($group = $this->sanitizeFilterGroup($group)) {
list ($column) = $group;
$this->maybeEnableJoinTablesByColumn($column);
$formattedFilters[$groupsIndex][] = $group;
}
}
}
return $formattedFilters;
}
/**
* Maybe Enable join tables base on filter group column.
*
* @param string $column The filter group column.
*/
protected function maybeEnableJoinTablesByColumn($column)
{
foreach ($this->joinTables as &$join) {
if (!Arr::isTrue($join, 'enable') && in_array($column, Arr::get($join, 'columns'))) {
$join['enable'] = true;
}
}
}
/**
* Sanitizes the provided filter group.
*
* @param array $group The filter group to sanitize.
* @return array|false The sanitized filter group, or false if the group is not valid or cannot be sanitized.
*/
protected function sanitizeFilterGroup($group)
{
if (!$this->isValidGroup($group)) {
return false;
}
$column = Arr::get($group, 'column');
$operator = Arr::get($group, 'operator');
$value = Arr::get($group, 'value');
$value = $this->sanitizeValueByColumn($value, $column, $operator);
if (null === $value || (is_array($value) && empty($value))) {
return false;
}
return $this->sanitizeFilterGroupForEluquentModel($column, $operator, $value);
}
/**
* Checks if the provided group is valid.
*
* @param array $group The group to check.
* @return bool True if the group is valid, false otherwise.
*/
protected function isValidGroup($group)
{
if (!$this->isSupportedColumn(Arr::get($group, 'column'))) {
return false;
}
if (!$this->isValidOperator(Arr::get($group, 'operator'))) {
return false;
}
return true;
}
/**
* Checks if the provided column is supported.
*
* @param string $column
* @return bool
*/
protected function isSupportedColumn($column)
{
if (!$column) {
return false;
}
return Arr::exists($this->getSupportedColumns(), $column);
}
/**
* Checks if the provided operator is valid.
*
* @param string $operator The operator to check.
* @return bool True if the operator is valid, false otherwise.
*/
protected function isValidOperator($operator)
{
if (!$operator) {
return false;
}
return Arr::exists(DynamicFieldHelper::getOperators(), $operator);
}
/**
* Sanitizes a value based on the column and operator.
*
* This method determines the appropriate sanitization callback function based on the column,
* and then sanitizes the value using the provided callback function or the WordPress database prepare method.
*
* @param mixed $value The value to sanitize.
* @param string $column The column name.
* @param string $operator The operator used for sanitization.
* @return mixed The sanitized value.
*/
protected function sanitizeValueByColumn($value, $column, $operator)
{
$callback = null;
if (in_array($column, DynamicFieldHelper::numericColumns())) {
$callback = 'absint';
} elseif (in_array($column, DynamicFieldHelper::dateColumns())) {
$value = $this->sanitizeDate($value);
} elseif ('is_favourite' == $column) {
$value = (int)Arr::isTrue(['is_favorites' => $value],'is_favorites');
}
return $this->sanitizeValue($value, $callback, $operator);
}
protected function sanitizeDate($date)
{
if (is_array($date)) {
foreach ($date as $i => &$d) {
$d = date('Y-m-d H:i:s', strtotime($d));
if (0 !== $i) {
$d = str_replace('00:00:00', '23:59:59', $d);
}
}
} else {
$date = date('Y-m-d H:i:s', strtotime($date));
}
return $date;
}
/**
* Sanitizes a value based on a callback function or the WordPress database prepare method.
*
* @param mixed $value The value to sanitize.
* @param callable|null $callback The callback function to apply to the value.
* @param string $operator The operator used for sanitization.
* @return mixed The sanitized value or null if the input value is null.
*/
protected function sanitizeValue($value, $callback, $operator)
{
if (null === $value) {
return null;
}
$value = $this->resolveValueForSanitize($value, $operator);
if (is_array($value)) {
if (is_callable($callback)) {
$value = array_filter(array_map($callback, $value));
} else {
$value = array_filter(array_map(function ($v) {
return $this->sanitizeWithWPDB($v);
}, $value));
}
} elseif (is_callable($callback)) {
$value = $callback($value);
} else {
$value = $this->sanitizeWithWPDB($value);
}
return $value;
}
/**
* Resolves the value for sanitization based on the provided operator.
*
* @param mixed $value The value to resolve for sanitization.
* @param string $operator The operator to determine how the value should be resolved.
* @return mixed The resolved value for sanitization.
*/
protected function resolveValueForSanitize($value, $operator)
{
switch ($operator) {
case 'IN':
case 'NOT IN':
if (is_string($value)) {
$value = explode(',', $value);
}
$value = array_map('trim', $value);
break;
default:
break;
}
return $value;
}
/**
* Sanitizes a value using the WordPress database prepare method.
* It also trims leading and trailing quotes from the sanitized value.
*
* @param mixed $value The value to sanitize.
* @return string|null The sanitized value or null if the input value is null.
*/
protected function sanitizeWithWPDB($value)
{
$value = $this->wpdb->prepare('%s', $value);
if ($value) {
// Trim leading and trailing quotes
$value = trim($value, "'\"");
}
return $value;
}
/**
* Sanitizes a filter group for an Eloquent model query.
*
* @param string $column The column to filter on.
* @param string $operator The comparison operator.
* @param mixed $value The value to compare against.
* @return array An array containing the sanitized column, operator, and value.
*/
protected function sanitizeFilterGroupForEluquentModel($column, $operator, $value)
{
switch ($operator) {
case 'contains':
case 'doNotContains':
case 'startsWith':
case 'endsWith':
if (is_array($value)) {
$value = join('', $value);
}
$value = $this->wpdb->esc_like($value);
if ('startsWith' === $operator) {
$value = "%" . $value;
} elseif ('endsWith' === $operator) {
$value = $value . "%";
} else {
$value = "%" . $value . "%";
}
$operator = 'doNotContains' === $operator ? 'NOT LIKE' : 'LIKE';
break;
default:
break;
}
return [$column, $operator, $value];
}
/**
* Get advance options based on template mapping.
*
* @return array
*/
public function getAdvanceOptions()
{
$value = sanitize_text_field(Arr::get($this->config, 'template_value.value', ''));
$label = sanitize_text_field(Arr::get($this->config, 'template_label.value', ''));
if (!$value || !$this->result) {
return [];
}
$validOptions = [];
$uniqueValueSet = [];
foreach ($this->result as $index => $result) {
$response = [];
if (isset($result->response) && Helper::isJson($result->response)) {
$response = \json_decode($result->response, true);
unset($result->response);
}
// Replace placeholders in value
$valueResult = $this->replacePlaceholders($value, $result, $response);
if (!is_string($value)) {
continue;
}
$valueResult = esc_attr($valueResult);
if (!$valueResult) {
continue;
}
// Replace placeholders in label
$labelResult = $this->replacePlaceholders($label, $result, $response);
// Use value result as label if label result is not a string or empty
if (!is_string($labelResult)) {
$labelResult = $valueResult;
}
$labelResult = esc_html($labelResult);
if (!$labelResult) {
$labelResult = $valueResult;
}
// Check for duplicate values and skip if found
if (in_array($valueResult, $uniqueValueSet)) {
continue;
}
$validOptions[] = ['id' => $index, 'label' => $labelResult, 'value' => $valueResult];
$uniqueValueSet[] = $valueResult;
}
return $validOptions;
}
/**
* Replaces placeholders in a string with corresponding values from a object.
*
* @param string $string The string containing placeholders.
* @param object $obj The object containing values to replace placeholders.
* @param array $response The submission response array.
* @return string The string with placeholders replaced by corresponding values.
*/
protected function replacePlaceholders($string, $obj, $response = [])
{
static $advanceOptions = null;
if (!$advanceOptions && false !== strpos($string, 'option_label')) {
$formId = Arr::get($this->config, 'basic_query.form_id');
$fieldName = Arr::get($this->config, 'basic_query.form_field');
if ($form = Helper::getForm($formId)) {
$inputs = FormFieldsParser::getField($form, ['input_radio', 'input_checkbox', 'multi_payment_component', 'select'], $fieldName, ['options']);
if ($inputs) {
$advanceOptions = Arr::get($inputs, $fieldName . '.options');
}
}
}
return preg_replace_callback('/\{([\w.]+)\}/', function ($matches) use ($obj, $response, $advanceOptions) {
$value = isset($obj->{$matches[1]}) ? trim($obj->{$matches[1]}) : '';
// resolve {option_label} value by response
if ('option_label' == $matches[1] && isset($obj->field_value)) {
if ($advanceOptions && $label = Arr::get($advanceOptions, $obj->field_value)) {
$value = $label;
}
}
// resolve {inputs.field_name} value by response
if (false !== strpos($matches[1], 'inputs.')) {
$name = substr($matches[1], strlen('inputs.'));
$name = isset($obj->{$name}) ? trim($obj->{$name}) : $name;
if ($name && isset($response[$name])) {
if (is_array($response[$name])) {
$value = fluentImplodeRecursive(' ', $response[$name]);
} elseif (is_string($response[$name])) {
$value = $response[$name];
}
}
}
return $value;
}, $string);
}
/**
* Gets the maximum number of editor value options allowed.
*
* @return int The maximum number of editor value options allowed.
*/
protected function getEditorValueOptionsLimit()
{
return apply_filters("{$this->filterPrefix}_editor_value_options_limit", 200);
}
/**
* Gets the maximum number of result allowed in the query.
*
* @return int The maximum number of result allowed in the query, default 500.
*/
protected function getResultLimit()
{
return apply_filters("{$this->filterPrefix}_record_limit", 500);
}
}