Seditio Source
Root |
./othercms/xenForo 2.2.8/src/XF/Searcher/AbstractSearcher.php
<?php

namespace XF\Searcher;

use
XF\Mvc\Entity\Entity;
use
XF\Mvc\Entity\Finder;
use
XF\Mvc\Entity\Manager;
use
XF\Util\Arr;

use function
count, floatval, in_array, intval, is_array, is_int, strval;

abstract class
AbstractSearcher
{
    protected
$em;
    protected
$structure;

    protected
$rawCriteria = [];
    protected
$filteredCriteria = [];

    protected
$allowedRelations = [];
    protected
$formats = [];
    protected
$blacklist = [];
    protected
$whitelist = [];

   
// This should contain any keys that use array-values
   
protected $arrayValueKeys = [];

    protected
$orderOptions = [];

    protected
$order = [];

    public function
__construct(Manager $em, array $criteria = null)
    {
       
$this->em = $em;
       
$this->structure = $em->getEntityStructure($this->getEntityType());
       
$this->orderOptions = $this->getDefaultOrderOptions();

       
$this->init();

        if (
$criteria)
        {
           
$this->setCriteria($criteria);
        }
    }

    abstract protected function
getEntityType();

   
/**
     * Return the default allowed order options, in [column] => printable name form
     *
     * @return array
     */
   
abstract protected function getDefaultOrderOptions();

    protected function
init()
    {
    }

    public function
setCriteria(array $criteria)
    {
       
$this->rawCriteria = $criteria;
       
$this->filteredCriteria = $this->filterCriteria($criteria);
    }

    public function
getRawCriteria()
    {
        return
$this->rawCriteria;
    }

    public function
getFilteredCriteria()
    {
        return
$this->filteredCriteria;
    }

    public function
addBlacklist($entry)
    {
        if (
is_array($entry))
        {
           
$this->blacklist = Arr::mapMerge($this->blacklist, $entry);
        }
        else
        {
           
$this->blacklist[$entry] = true;
        }

        return
$this;
    }

    public function
setBlacklist(array $blacklist)
    {
       
$this->blacklist = $blacklist;
        return
$this;
    }

    public function
addWhitelist($entry)
    {
        if (
is_array($entry))
        {
           
$this->whitelist = Arr::mapMerge($this->whitelist, $entry);
        }
        else
        {
           
$this->whitelist[$entry] = true;
        }

        return
$this;
    }

    public function
setWhitelist(array $whitelist)
    {
       
$this->whitelist = $whitelist;
        return
$this;
    }

    public function
getWhitelist()
    {
        return
$this->whitelist;
    }

    public function
addOrderOptions($key, $text)
    {
       
$this->orderOptions[$key] = $text;
        return
$this;
    }

    public function
getOrderOptions()
    {
        return
$this->orderOptions;
    }

    public function
getRecommendedOrderDirection($key)
    {
        if (!isset(
$this->orderOptions[$key]))
        {
            return
'desc';
        }

       
$structure = $this->structure;
        if (!isset(
$structure->columns[$key]))
        {
            return
'desc';
        }

        if (
$structure->columns[$key]['type'] == Entity::STR)
        {
            return
'asc';
        }
        else
        {
            return
'desc';
        }
    }

    public function
setOrder($order, $direction = 'asc', $secondaryOrder = null, $secondaryDirection = 'asc')
    {
       
$newOrder = [];

        if (
$order && isset($this->orderOptions[$order]))
        {
           
$newOrder[] = [$order, strtolower($direction) == 'asc' ? 'asc' : 'desc'];
        }

        if (
$secondaryOrder && isset($this->orderOptions[$secondaryOrder]))
        {
           
$newOrder[] = [$secondaryOrder, strtolower($secondaryDirection) == 'asc' ? 'asc' : 'desc'];
        }

        if (
$newOrder)
        {
           
$this->order = $newOrder;
            return
true;
        }
        else
        {
            return
false;
        }
    }

    public function
getOrder()
    {
        return
$this->order;
    }

    protected function
filterCriteria(array $criteria, $relation = null)
    {
        if (
$relation === null)
        {
           
$structure = $this->structure;
           
$allowedRelations = array_fill_keys($this->allowedRelations, true);
           
$formats = $this->formats;
           
$blacklist = $this->blacklist;
           
$whitelist = $this->whitelist;
        }
        else
        {
           
$structure = $this->em->getEntityStructure($this->structure->relations[$relation]['entity']);
           
$allowedRelations = [];
           
$formats = $this->formats[$relation] ?? [];
           
$blacklist = $this->blacklist[$relation] ?? [];
           
$whitelist = $this->whitelist[$relation] ?? [];
        }

        foreach (
$criteria AS $key => &$value)
        {
            if (isset(
$blacklist[$key]) || ($whitelist && !isset($whitelist[$key])))
            {
                unset(
$criteria[$key]);
            }
            else if (isset(
$structure->relations[$key]))
            {
                if (isset(
$allowedRelations[$key]) && is_array($value))
                {
                   
$value = $this->filterCriteria($value, $key);
                }
                else
                {
                   
$value = false;
                }
                if (!
$value || !is_array($value))
                {
                    unset(
$criteria[$key]);
                }
            }
            else
            {
               
$column = $structure->columns[$key] ?? null;
               
$format = $formats[$key] ?? null;
                if (!
$this->validateCriteriaValue($key, $value, $column, $format, $relation))
                {
                    unset(
$criteria[$key]);
                }
            }
        }

        return
$criteria;
    }

    protected function
validateCriteriaValue($key, &$value, $column, $format, $relation)
    {
       
$validated = $this->validateSpecialCriteriaValue($key, $value, $column, $format, $relation);
        if (
$validated === true || $validated === false)
        {
            return
$validated;
        }
       
// null falls through

       
if (is_array($value) && !in_array($key, $this->arrayValueKeys, true))
        {
           
$uniqueValues = array_unique($value, SORT_REGULAR);
            if (
count($uniqueValues) == 1 && is_int(key($uniqueValues)))
            {
               
$value = reset($uniqueValues);
            }
        }

        if (
$value === '' || (is_array($value) && !count($value)))
        {
            return
false;
        }

        if (
is_array($value) && $column)
        {
            if (isset(
$column['allowedValues']))
            {
               
$allowedValues = $column['allowedValues'];
               
$matched = 0;
                foreach (
$allowedValues AS $allowed)
                {
                    if (
in_array($allowed, $value, true))
                    {
                       
$matched++;
                    }
                }
                if (
$matched == count($allowedValues))
                {
                   
// all possible values selected
                   
return false;
                }
            }

            if (
$column['type'] == Entity::BOOL && count($value) == 2)
            {
               
// both values selected
               
return false;
            }

            if (isset(
$value['start']) || isset($value['end']))
            {
                if (isset(
$column['min']))
                {
                   
$min = $column['min'];
                }
                else if (
$column['type'] == Entity::UINT)
                {
                   
$min = 0;
                }
                else
                {
                   
$min = null;
                }
                if (isset(
$column['max']))
                {
                   
$max = $column['max'];
                }
                else
                {
                   
$max = null;
                }

                if (isset(
$value['start']))
                {
                    if (
$value['start'] === ''
                       
|| ($min !== null && is_numeric($value['start']) && $value['start'] <= $min)
                        || (
$format == 'date' && !$this->validateDate($value['start']))
                    )
                    {
                        unset(
$value['start']);
                    }
                }
                if (isset(
$value['end']))
                {
                    if (
$value['end'] === ''
                       
|| ($min !== null && is_numeric($value['end']) && $value['end'] < $min)
                        || (
$max !== null && is_numeric($value['end']) && $value['end'] >= $max)
                        || (
$format == 'date' && !$this->validateDate($value['end']))
                    )
                    {
                        unset(
$value['end']);
                    }
                }

                if (!
$value)
                {
                    return
false;
                }
            }
        }

       
$validated = $this->validateSpecialCriteriaValueAfter($key, $value, $column, $format, $relation);
        if (
$validated === true || $validated === false)
        {
            return
$validated;
        }

        return
true;
    }

    protected function
validateSpecialCriteriaValue($key, &$value, $column, $format, $relation)
    {
        return
null;
    }

    protected function
validateSpecialCriteriaValueAfter($key, &$value, $column, $format, $relation)
    {
        return
null;
    }

    protected function
validateDate($value)
    {
        return (
is_int($value) || preg_match('/^\d{4}-\d{1,2}-\d{1,2}$/', $value));
    }

    public function
getFinder()
    {
       
$finder = $this->em->getFinder($this->getEntityType());
       
$this->applyDefaultFinderLimits($finder);
       
$this->applyCriteria($finder, $this->filteredCriteria);

        if (
$this->order)
        {
           
$finder->setDefaultOrder($this->order);
        }

        return
$finder;
    }

    protected function
applyDefaultFinderLimits(\XF\Mvc\Entity\Finder $finder)
    {
    }

    protected function
applyCriteria(Finder $finder, array $criteria, $relation = null)
    {
        if (
$relation === null)
        {
           
$structure = $this->structure;
           
$formats = $this->formats;
        }
        else
        {
           
$structure = $this->em->getEntityStructure($this->structure->relations[$relation]['entity']);
           
$formats = $this->formats[$relation] ?? [];
        }

        foreach (
$criteria AS $key => &$value)
        {
            if (isset(
$structure->relations[$key]))
            {
               
$this->applyCriteria($finder, $value, $key);
            }
            else
            {
               
$column = $structure->columns[$key] ?? null;
               
$format = $formats[$key] ?? null;
               
$this->applyCriteriaValue($finder, $key, $value, $column, $format, $relation);
            }
        }
    }

    protected function
applyCriteriaValue(Finder $finder, $key, $value, $column, $format, $relation)
    {
        if (
$this->applySpecialCriteriaValue($finder, $key, $value, $column, $format, $relation))
        {
            return;
        }

        if (!
$column)
        {
            return;
        }

       
$columnName = ($relation ? "$relation." : '') . $key;

        if (
$format == 'like')
        {
           
$value = $finder->escapeLike($value, '%?%');
           
$finder->where($columnName, 'LIKE', $value);
            return;
        }

        if (
$format == 'date')
        {
            if (
is_array($value))
            {
                if (isset(
$value['start']))
                {
                   
$value['start'] = $this->convertDateToInteger($value['start']);
                }
                if (isset(
$value['end']))
                {
                   
$value['end'] = $this->convertDateToInteger($value['end'], true);
                }
            }
            else
            {
               
$value = $this->convertDateToInteger($value);
            }
        }

       
$value = $this->castValueToColumnType($value, $column['type']);

        if (
is_array($value))
        {
           
$hasMin = isset($value['start']);
           
$hasMax = isset($value['end']);
            if (
$hasMin && $hasMax)
            {
               
$finder->where($columnName, 'BETWEEN', [$value['start'], $value['end']]);
                return;
            }
            else if (
$hasMin)
            {
               
$finder->where($columnName, '>=', $value['start']);
                return;
            }
            else if (
$hasMax)
            {
               
$finder->where($columnName, '<=', $value['end']);
                return;
            }
        }

       
$finder->where($columnName, '=', $value);
    }

    protected function
castValueToColumnType($value, $type)
    {
        if (
is_array($value))
        {
            foreach (
$value AS &$v)
            {
               
$v = $this->castValueToColumnType($v, $type);
            }

            return
$value;
        }

        switch (
$type)
        {
            case
Entity::BOOL:
                return (
$value ? 1 : 0);

            case
Entity::INT:
            case
Entity::UINT:
                return
intval($value);

            case
Entity::FLOAT:
                return
strval(floatval($value)) + 0;

            default:
                return
strval($value);
        }
    }

    protected function
convertDateToInteger($date, $dayEnd = false)
    {
        if (
is_int($date))
        {
            return
$date;
        }

       
$d = new \DateTime($date, \XF::language()->getTimeZone());
        if (
$dayEnd)
        {
           
$d->setTime(23, 59, 59);
        }

        return
intval($d->format('U'));
    }

    protected function
applySpecialCriteriaValue(Finder $finder, $key, $value, $column, $format, $relation)
    {
        return
false;
    }

    protected function
convertRelativeTimeToCutoff(int $value, string $unit)
    {
        if (
$value <= 0)
        {
            return \
XF::$time;
        }

       
$unit = strtolower($unit);

        switch (
$unit)
        {
            case
'year':
            case
'month':
            case
'week':
            case
'day':
            case
'hour':
                break;

            default:
               
$unit = 'hour';
        }

       
$time = strtotime("-$value $unit");
        if (!
$time || $time < 0)
        {
            return
null;
        }

        return  
$time;
    }

    public function
getFormData()
    {
        return [];
    }

    public function
getFormCriteria()
    {
        if (
$this->rawCriteria)
        {
            return
$this->rawCriteria;
        }
        else
        {
            return
$this->getFormDefaults();
        }
    }

    public function
getFormDefaults()
    {
        return [];
    }
}