<?php
/**
 * UUID generator.
 *
 * @author @jaswrks
 * @copyright WebSharks™
 */
declare(strict_types=1);
namespace WebSharks\Core\Classes\Core\Utils;

use WebSharks\Core\Classes;
use WebSharks\Core\Interfaces;
use WebSharks\Core\Traits;
#
use WebSharks\Core\Classes\Core\Error;
use WebSharks\Core\Classes\Core\Base\Exception;
#
use function assert as debug;
use function get_defined_vars as vars;
#
use Ramsey\Uuid\Uuid as UuidGen;

/**
 * UUID generator.
 *
 * @since 150424 Initial release.
 */
class Uuid extends Classes\Core\Base\Core
{
    /**
     * UUID v1 generator.
     *
     * @since 150424 Initial release.
     *
     * @param bool $optimize Optimize?
     *
     * @return string Version 1 UUID (32 bytes optimized).
     *                Or 36 bytes unoptimized; i.e., w/ dashes.
     */
    public function v1(bool $optimize = true): string
    {
        $uuid = UuidGen::uuid1()->toString();

        if ($optimize) {
            // See: <http://jas.xyz/1ODZilT>
            $uuid = substr($uuid, 14, 4).
                substr($uuid, 9, 4).
                substr($uuid, 0, 8).
                substr($uuid, 19, 4).
                substr($uuid, 24);
        }
        return $uuid; // Possibly optimized now.
    }

    /**
     * UUID v3 generator.
     *
     * @since 150424 Initial release.
     *
     * @param string $namespace  Namespace.
     * @param string $identifier Identifier.
     * @param bool   $optimize   Optimize?
     *
     * @return string Version 3 UUID (32 bytes optimized).
     *                Or 36 bytes unoptimized; i.e., w/ dashes.
     *
     * @internal {@link v5()} is a suggested alternative, as this version uses MD5.
     */
    public function v3(string $namespace, string $identifier, bool $optimize = true): string
    {
        switch ($namespace) {
            case 'dns':
                $namespace = UuidGen::NAMESPACE_DNS;
                break; // Stop here.

            case 'url':
                $namespace = UuidGen::NAMESPACE_URL;
                break; // Stop here.

            case 'oid':
                $namespace = UuidGen::NAMESPACE_OID;
                break; // Stop here.

            case 'x500':
                $namespace = UuidGen::NAMESPACE_X500;
                break; // Stop here.

            default:
                $this->c::issue('Invalid namespace.');
        }
        $uuid = UuidGen::uuid3($namespace, $identifier)->toString();

        return $optimize ? str_replace('-', '', $uuid) : $uuid;
    }

    /**
     * UUID v4 generator.
     *
     * @since 150424 Initial release.
     *
     * @param bool $optimize Optimize?
     *
     * @return string Version 4 UUID (32 bytes optimized).
     *                Or 36 bytes unoptimized; i.e., w/ dashes.
     */
    public function v4(bool $optimize = true): string
    {
        $uuid = UuidGen::uuid4()->toString();

        return $optimize ? str_replace('-', '', $uuid) : $uuid;
    }

    /**
     * UUID v4 generator x 2.
     *
     * @since 170824.30708 Initial release.
     *
     * @param bool $optimize Optimize?
     *
     * @return string Version 4 UUID x 2 (64 bytes optimized).
     *                Or 73 bytes unoptimized; i.e., w/ dashes + one extra.
     */
    public function v4x2(bool $optimize = true): string
    {
        $uuid1 = UuidGen::uuid4()->toString();
        $uuid2 = UuidGen::uuid4()->toString();

        return $optimize ? str_replace('-', '', $uuid1.$uuid2) : $uuid1.'-'.$uuid2;
    }

    /**
     * UUID v5 generator.
     *
     * @since 150424 Initial release.
     *
     * @param string $namespace  Namespace.
     * @param string $identifier Identifier.
     * @param bool   $optimize   Optimize?
     *
     * @return string Version 5 UUID (32 bytes optimized).
     *                Or 36 bytes unoptimized; i.e., w/ dashes.
     */
    public function v5(string $namespace, string $identifier, bool $optimize = true): string
    {
        switch ($namespace) {
            case 'dns':
                $namespace = UuidGen::NAMESPACE_DNS;
                break; // Stop here.

            case 'url':
                $namespace = UuidGen::NAMESPACE_URL;
                break; // Stop here.

            case 'oid':
                $namespace = UuidGen::NAMESPACE_OID;
                break; // Stop here.

            case 'x500':
                $namespace = UuidGen::NAMESPACE_X500;
                break; // Stop here.

            default:
                throw $this->c::issue('Invalid namespace.');
        }
        $uuid = UuidGen::uuid5($namespace, $identifier)->toString();

        return $optimize ? str_replace('-', '', $uuid) : $uuid;
    }
}


<?php
declare(strict_types = 1);
namespace RabbitCMS\Modules;

use ArrayIterator;
use Countable;
use Illuminate\Contracts\Support\Arrayable;
use IteratorAggregate;
use RabbitCMS\Modules\Contracts\PackageContract;
use RabbitCMS\Modules\Exceptions\ModuleNotFoundException;

/**
 * Class Repository.
 * @package RabbitCMS\Modules
 */
class Repository implements IteratorAggregate, Countable, Arrayable
{
    /**
     * @var Module[]
     */
    protected $items = [];

    /**
     * Get modules count.
     *
     * @return int
     */
    public function count(): int
    {
        return count($this->items);
    }

    /**
     * Get modules iterator.
     *
     * @return ArrayIterator
     */
    public function getIterator(): ArrayIterator
    {
        return new ArrayIterator($this->items);
    }

    /**
     * Checks if module exists.
     *
     * @param string $name
     *
     * @return bool
     */
    public function has($name): bool
    {
        return array_key_exists($name, $this->items);
    }

    /**
     * Get module by name.
     *
     * @param string $name
     *
     * @return PackageContract
     * @throws ModuleNotFoundException
     */
    public function get($name): PackageContract
    {
        if (array_key_exists($name, $this->items)) {
            return $this->items[$name];
        }
        throw new Exceptions\ModuleNotFoundException("Package '$name' not found.");
    }

    /**
     * Run function for each module.
     *
     * @param callable $callback
     */
    public function each(callable $callback)
    {
        foreach ($this->items as $name => $item) {
            $callback($item, $name);
        }
    }

    /**
     * Filter modules.
     *
     * @param callable $callback
     *
     * @return Repository
     */
    public function filter(callable $callback):Repository
    {
        $result = new Repository();
        foreach ($this->items as $name => $item) {
            if ($callback($item, $name)) {
                $result->add($item);
            }
        }

        return $result;
    }

    /**
     * Add module to repository.
     *
     * @param PackageContract[] ...$items
     */
    public function add(PackageContract ...$items)
    {
        foreach ($items as $item) {
            $this->items[$item->getName()] = $item;
        }
    }

    /**
     * @inheritdoc
     */
    public function toArray(): array
    {
        return $this->map(function (PackageContract $item) {
            return $item->toArray();
        });
    }

    /**
     * Map modules.
     *
     * @param callable $callback
     *
     * @return array
     */
    public function map(callable $callback): array
    {
        return array_map($callback, $this->items);
    }

    /**
     * Get all module.
     *
     * @return PackageContract[]
     */
    public function all(): array
    {
        return $this->items;
    }
}


<?php

/*
 * This file is part of the Ivory Google Map package.
 *
 * (c) Eric GELOEN <geloen.eric@gmail.com>
 *
 * For the full copyright and license information, please read the LICENSE
 * file that was distributed with this source code.
 */

namespace Ivory\GoogleMap\Services\Geocoding;

use Ivory\GoogleMap\Base\Bound;
use Ivory\GoogleMap\Base\Coordinate;
use Ivory\GoogleMap\Exception\GeocodingException;

/**
 * Geocoder request which describes a google map geocoder request.
 *
 * @see http://code.google.com/apis/maps/documentation/javascript/reference.html#GeocoderRequest
 * @author GeLo <geloen.eric@gmail.com>
 */
class GeocoderRequest
{
    /** @var string */
    protected $address;

    /** @var \Ivory\GoogleMap\Base\Coordinate */
    protected $coordinate;

    /** @var \Ivory\GoogleMap\Base\Bound */
    protected $bound;

    /** @var string */
    protected $region;

    /** @var string */
    protected $language;

    /** @var boolean */
    protected $sensor;

    /**
     * Creates a geocoder request.
     */
    public function __construct()
    {
        $this->sensor = false;
    }

    /**
     * Checks if the geocoder request has an address.
     *
     * @return boolean TRUE if the geocoder request has an address else FALSE.
     */
    public function hasAddress()
    {
        return $this->address !== null;
    }

    /**
     * Gets the geocoder request address.
     *
     * @return string The geocoder request address.
     */
    public function getAddress()
    {
        return $this->address;
    }

    /**
     * Sets the geocoder request address.
     *
     * @param string $address The geocoder request address.
     *
     * @throws \Ivory\GoogleMap\Exception\GeocodingException If the address is not valid.
     */
    public function setAddress($address)
    {
        if (!is_string($address) && ($address !== null)) {
            throw GeocodingException::invalidGeocoderRequestAddress();
        }

        $this->address = $address;
    }

    /**
     * Checks if the geocoder request has a coordinate.
     *
     * @return boolean TRUE if the geocoder request has a coordinate else FALSE.
     */
    public function hasCoordinate()
    {
        return $this->coordinate !== null;
    }

    /**
     * Gets the geocoder request coordinate.
     *
     * @return \Ivory\GoogleMap\Base\Coordinate The geocoder request coordinate.
     */
    public function getCoordinate()
    {
        return $this->coordinate;
    }

    /**
     * Sets the geocoder request coordinate
     *
     * Available prototypes:
     *  - function setCoordinate(\Ivory\GoogleMap\Base\Coordinate $coordinate = null)
     *  - function setCoordinate(double $latitude, double $longitude, boolean $noWrap = true)
     *
     * @throws \Ivory\GoogleMap\Exception\GeocodingException If the coordinate is not valid (prototypes).
     */
    public function setCoordinate()
    {
        $args = func_get_args();

        if (isset($args[0]) && ($args[0] instanceof Coordinate)) {
            $this->coordinate = $args[0];
        } elseif ((isset($args[0]) && is_numeric($args[0])) && (isset($args[1]) && is_numeric($args[1]))) {
            if (!$this->hasCoordinate()) {
                $this->coordinate = new Coordinate();
            }

            $this->coordinate->setLatitude($args[0]);
            $this->coordinate->setLongitude($args[1]);

            if (isset($args[2]) && is_bool($args[2])) {
                $this->coordinate->setNoWrap($args[2]);
            }
        } elseif (!isset($args[0])) {
            $this->coordinate = null;
        } else {
            throw GeocodingException::invalidGeocoderRequestCoordinate();
        }

        return $this;
    }

    /**
     * Checks if the geocoder request has a bound.
     *
     * @return boolean TRUE if the geocoder request has a bound else FALSE.
     */
    public function hasBound()
    {
        return $this->bound !== null;
    }

    /**
     * Gets the geocoder request bound.
     *
     * @return \Ivory\GoogleMap\Base\Bound The geocoder request bound.
     */
    public function getBound()
    {
        return $this->bound;
    }

    /**
     * Sets the geocoder request bound.
     *
     * Available prototypes:
     *  - function setBound(Ivory\GoogleMap\Base\Bound $bound = null)
     *  - function setBount(Ivory\GoogleMap\Base\Coordinate $southWest, Ivory\GoogleMap\Base\Coordinate $northEast)
     *  - function setBound(
     *     double $southWestLatitude,
     *     double $southWestLongitude,
     *     double $northEastLatitude,
     *     double $northEastLongitude,
     *     boolean southWestNoWrap = true,
     *     boolean $northEastNoWrap = true
     * )
     *
     * @throws \Ivory\GoogleMap\Exception\GeocodingException If the bound is not valid.
     */
    public function setBound()
    {
        $args = func_get_args();

        if (isset($args[0]) && ($args[0] instanceof Bound)) {
            $this->bound = $args[0];
        } elseif ((isset($args[0]) && ($args[0] instanceof Coordinate))
            && (isset($args[1]) && ($args[1] instanceof Coordinate))
        ) {
            if (!$this->hasBound()) {
                $this->bound = new Bound();
            }

            $this->bound->setSouthWest($args[0]);
            $this->bound->setNorthEast($args[1]);
        } elseif ((isset($args[0]) && is_numeric($args[0]))
            && (isset($args[1]) && is_numeric($args[1]))
            && (isset($args[2]) && is_numeric($args[2]))
            && (isset($args[3]) && is_numeric($args[3]))
        ) {
            if (!$this->hasBound()) {
                $this->bound = new Bound();
            }

            $this->bound->setSouthWest(new Coordinate($args[0], $args[1]));
            $this->bound->setNorthEast(new Coordinate($args[2], $args[3]));

            if (isset($args[4]) && is_bool($args[4])) {
                $this->bound->getSouthWest()->setNoWrap($args[4]);
            }

            if (isset($args[5]) && is_bool($args[5])) {
                $this->bound->getNorthEast()->setNoWrap($args[5]);
            }
        } elseif (!isset($args[0])) {
            $this->bound = null;
        } else {
            throw GeocodingException::invalidGeocoderRequestBound();
        }
    }

    /**
     * Checks if the geocoder request has a region.
     *
     * @return boolean TRUE if the geocoder request has a region else FALSE.
     */
    public function hasRegion()
    {
        return $this->region !== null;
    }

    /**
     * Gets the geocoder request region
     *
     * @return string
     */
    public function getRegion()
    {
        return $this->region;
    }

    /**
     * Sets the geocoder request region.
     *
     * @param string $region The geocoder request region.
     *
     * @throws \Ivory\GoogleMap\Exception\GeocodingException If the regin is not valid.
     */
    public function setRegion($region = null)
    {
        if ((!is_string($region) || (strlen($region) !== 2)) && ($region !== null)) {
            throw GeocodingException::invalidGeocoderRequestRegion();
        }

        $this->region = $region;
    }

    /**
     * Checks if the geocoder request has a language.
     *
     * @return boolean TRUE if the geocoder request has a language else FALSE.
     */
    public function hasLanguage()
    {
        return $this->language !== null;
    }

    /**
     * Gets the geocoder request language.
     *
     * @return string The geocoder request language.
     */
    public function getLanguage()
    {
        return $this->language;
    }

    /**
     * Sets the geocoder request language.
     *
     * @param string $language The geocoder request language.
     *
     * @throws \Ivory\GoogleMap\Exception\GeocodingException If the language is not valid.
     */
    public function setLanguage($language = null)
    {
        if ((!is_string($language) || ((strlen($language) !== 2) && (strlen($language) !== 5))) && ($language !== null)) {
            throw GeocodingException::invalidGeocoderRequestLanguage();
        }

        $this->language = $language;
    }

    /**
     * Checks if the geocoder request has a sensor.
     *
     * @return boolean TRUE if the geocoder request has a sensor else FALSE.
     */
    public function hasSensor()
    {
        return $this->sensor;
    }

    /**
     * Sets the geocoder request sensor.
     *
     * @param boolean $sensor TRUE if the geocoder request has a sensor else FALSE.
     *
     * @throws \Ivory\GoogleMap\Exception\GeocodingRequest If the sensor flag is not valid.
     */
    public function setSensor($sensor)
    {
        if (!is_bool($sensor)) {
            throw GeocodingException::invalidGeocoderRequestSensor();
        }

        $this->sensor = $sensor;
    }

    /**
     * Checks if the geocoder request is valid.
     *
     * @return boolean TRUE if the geocoder request is valid else FALSE.
     */
    public function isValid()
    {
        return $this->hasAddress() || $this->hasCoordinate();
    }
}


<?php
/**
 * Route utilities.
 *
 * @author @jaswrks
 * @copyright WebSharks™
 */
declare(strict_types=1);
namespace WebSharks\Core\Classes\Core\Utils;

use WebSharks\Core\Classes;
use WebSharks\Core\Interfaces;
use WebSharks\Core\Traits;
#
use WebSharks\Core\Classes\Core\Error;
use WebSharks\Core\Classes\Core\Base\Exception;
#
use function assert as debug;
use function get_defined_vars as vars;

/**
 * Route utilities.
 *
 * @since 161008 Route utilities.
 */
class Route extends Classes\Core\Base\Core
{
    /**
     * Loads route file.
     *
     * @since 161008 Route utilities.
     * @since 17xxxx Exit instead of return.
     *
     * @param array $args Behavioral args.
     */
    public function load(array $args = [])
    {
        $default_args = [
            'path'        => '',
            'dir'         => '',
            'vars'        => [],
            'on_resolved' => null,
        ];
        $args += $default_args;

        $args['path'] = (string) $args['path'];
        $args['dir']  = (string) $args['dir'];
        $args['vars'] = (array) $args['vars'];

        if (($resolved = $this->resolve($args['path'], $args['dir'], $args))) {
            if (isset($args['on_resolved']) && is_callable($args['on_resolved'])) {
                $args['on_resolved']($resolved);
            }
            exit($this->get($resolved)->parse($args['vars']));
        } else {
            $this->c::die(404); // Unable to resolve.
        }
    }

    /**
     * Resolves a route.
     *
     * @since 161008 Route utilities.
     *
     * @param string $path A URL path/route.
     * @param string $dir  From a specific directory?
     * @param array  $args Any additional behavioral args.
     *
     * @return array `[dir, file, ext, path, rewrite_query_vars]`.
     */
    public function resolve(string $path = '', string $dir = '', array $args = []): array
    {
        $default_args = [
            'rewrites'                => [],
            'protocol'                => 'http',
            'extension'               => 'php',
            'redirect_trailing_slash' => true,
        ];
        $args += $default_args; // Merge defaults.

        $rewrites                = (array) $args['rewrites'];
        $protocol                = (string) $args['protocol'];
        $extension               = (string) $args['extension'];
        $redirect_trailing_slash = (bool) $args['redirect_trailing_slash'];

        if (!$protocol || !$extension) {
            return []; // Missing components.
        }
        if (!isset($path[0])) { // If no specific route; use current URL path.
            $path = $this->c::currentPath(); // Use current URL path.

            if ($redirect_trailing_slash && mb_substr($path, -1) === '/' && $path !== '/') {
                $current_url         = $this->c::parseUrl($this->c::currentUrl());
                $current_url['path'] = $this->c::mbRTrim($current_url['path'], '/');
                $current_url         = $this->c::unparseUrl($current_url);

                $this->c::redirect($current_url, [
                    'cacheable' => true,
                    'status'    => 301,
                ]); // Without trailing slash.
            }
        } // Trim leading/trailing slashes.
        $path               = $this->c::mbTrim($path, '/');
        $path               = isset($path[0]) ? $path : 'index';
        $rewrite_query_vars = []; // Initialize query vars.

        foreach ($rewrites as $_rewrite_pattern => $_rewrite_path) {
            if (!preg_match($_rewrite_pattern, $path, $_m)) {
                continue; // Only if pattern matches.
            } elseif (!$_rewrite_path) {
                continue; // Must have.
            }
            $_rewrite_path_contains_rcs = mb_strpos($_rewrite_path, '$') !== false
                || mb_strpos($_rewrite_path, '%%') !== false;

            foreach ($_m as $_key => $_value) {
                if ($_rewrite_path_contains_rcs) {
                    $_rewrite_path = str_replace(['${'.$_key.'}', '%%'.$_key.'%%'], $_value, $_rewrite_path);
                    $_rewrite_path = is_int($_key) ? str_replace('$'.$_key, $_value, $_rewrite_path) : $_rewrite_path;
                } // This allows replacement codes in rewritten paths.

                if ($_key && is_string($_key)) {
                    $rewrite_query_vars[urldecode($_key)] = urldecode($_value);
                } // Only string keys w/ a value.
            } // unset($_key, $_value); // Housekeeping.

            if ($_rewrite_path_contains_rcs) {
                $_rewrite_path = preg_replace(['/\$[0-9]+/u', '/\$\{[^{}]*\}/u', '/%%[^%]*%%/u'], '', $_rewrite_path);
                $_rewrite_path = preg_replace('/\/{2,}/u', '/', $_rewrite_path);
            }
            $path = $_rewrite_path;
            $path = $this->c::mbTrim($path, '/');
            $path = isset($path[0]) ? $path : 'index';
            break; // Only one pattern can match; i.e., the first match wins.
        } // unset($_rewrite_pattern, $_rewrite_path, $_rewrite_path_contains_rcs, $_m);

        $location        = $this->locate($protocol.'/'.$path.'.'.$extension, $dir);
        return $resolved = $location ? array_merge($location, compact('path', 'rewrite_query_vars')) : [];
    }

    /**
     * Locates a route file.
     *
     * @since 161008 Route utilities.
     *
     * @param string $file Relative to routes dir.
     * @param string $dir  From a specific directory?
     *
     * @return array Route `[dir, file, ext]`.
     */
    public function locate(string $file, string $dir = ''): array
    {
        $is_file = false; // Initialize.
        $file    = $this->c::mbTrim($file, '/');
        $dir     = $this->c::mbRTrim($dir, '/');
        $dir     = $dir ?: $this->App->Config->©fs_paths['©routes_dir'];

        if ($dir === 'core') {
            if ($this->App->Parent) {
                return $this->App->Parent->Utils->©Route->locate($file, $dir);
            } else { // Use core config option.
                $dir = $this->App->Config->©fs_paths['©routes_dir'];
            }
        } elseif ($dir === 'parent' || ($dir && $file && !($is_file = is_file($dir.'/'.$file)))) {
            if ($this->App->Parent) {
                return $this->App->Parent->Utils->©Route->locate($file);
            } else { // Else go with what we have.
                $dir = $this->App->Config->©fs_paths['©routes_dir'];
            }
        }
        if ($dir && $file && !$is_file && !($is_file = is_file($dir.'/'.$file))) {
            $dir = dirname(__FILE__, 4).'/routes'; // WS core routes.
        }
        if ($dir && $file && ($is_file || ($is_file = is_file($dir.'/'.$file)))) {
            if (preg_match('/\/\.|\.\/|\.\./u', $this->c::normalizeDirPath($dir.'/'.$file))) {
                throw $this->c::issue(sprintf('Insecure route path: `%1$s`.', $dir.'/'.$file));
            }
            return ['dir' => $dir, 'file' => $file, 'ext' => $this->c::fileExt($file)];
        }
        return []; // Unable to locate.
    }

    /**
     * Gets route.
     *
     * @since 161008 Route utilities.
     *
     * @param array $args Route instance args.
     *
     * @return Classes\Core\Route Route instance.
     */
    public function get(array $args = []): Classes\Core\Route
    {
        return $this->App->Di->get(Classes\Core\Route::class, compact('args'));
    }
}


<?php
/**
 * YAML utilities.
 *
 * @author @jaswrks
 * @copyright WebSharks™
 */
declare (strict_types = 1);
namespace WebSharks\Core\Classes\Core\Utils;

use WebSharks\Core\Classes;
use WebSharks\Core\Interfaces;
use WebSharks\Core\Traits;
#
use WebSharks\Core\Classes\Core\Error;
use WebSharks\Core\Classes\Core\Base\Exception;
#
use function assert as debug;
use function get_defined_vars as vars;
#
use Symfony\Component\Yaml\Yaml as Parser;

/**
 * YAML utilities.
 *
 * @since 150424 Initial release.
 */
class Yaml extends Classes\Core\Base\Core
{
    /**
     * YAML parser.
     *
     * @since 150424 Initial release.
     *
     * @param mixed $value Any input value.
     *
     * @return array|object Converted value(s).
     */
    public function parse($value)
    {
        if (is_array($value) || is_object($value)) {
            foreach ($value as $_key => &$_value) {
                $_value = $this->parse($_value, $args);
            } // unset($_key, $_value);

            return $value;
        }
        if (!($yaml = (string) $value)) {
            return []; // Empty.
        }
        try {
            $array = Parser::parse($yaml);
        } catch (\Throwable $Throwable) {
            $array = []; // Empty.
        }
        return is_array($array) ? $array : [];
    }
}


<?php
/**
 * No-cache utils.
 *
 * @author @jaswrks
 * @copyright WebSharks™
 */
declare (strict_types = 1);
namespace WebSharks\Core\Classes\Core\Utils;

use WebSharks\Core\Classes;
use WebSharks\Core\Interfaces;
use WebSharks\Core\Traits;
#
use WebSharks\Core\Classes\Core\Error;
use WebSharks\Core\Classes\Core\Base\Exception;
#
use function assert as debug;
use function get_defined_vars as vars;

/**
 * No-cache utils.
 *
 * @since 160606 No-cache utils.
 */
class NoCache extends Classes\Core\Base\Core
{
    /**
     * Set no-cache flags.
     *
     * @since 160606 No-cache utils.
     */
    public function setFlags()
    {
        if ($this->c::isWordPress()) {
            if (!defined('DONOTCACHEPAGE')) {
                define('DONOTCACHEPAGE', true);
            }
            if (!defined('COMET_CACHE_ALLOWED')) {
                define('COMET_CACHE_ALLOWED', false);
            }
            $_SERVER['COMET_CACHE_ALLOWED'] = false;
        }
    }
}


<?php
/**
 * Array sort utilities.
 *
 * @author @jaswrks
 * @copyright WebSharks™
 */
declare (strict_types = 1);
namespace WebSharks\Core\Classes\Core\Utils;

use WebSharks\Core\Classes;
use WebSharks\Core\Interfaces;
use WebSharks\Core\Traits;
#
use WebSharks\Core\Classes\Core\Error;
use WebSharks\Core\Classes\Core\Base\Exception;
#
use function assert as debug;
use function get_defined_vars as vars;

/**
 * Array sort utilities.
 *
 * @since 150424 Initial release.
 */
class Sort extends Classes\Core\Base\Core
{
    /**
     * Sorts by key.
     *
     * @since 150424 Array utils.
     *
     * @param array $array Input array.
     * @param int   $flags Defaults to `SORT_REGULAR`.
     *
     * @return array Output array.
     */
    public function byKey(array $array, int $flags = SORT_REGULAR): array
    {
        ksort($array, $flags);

        foreach ($array as $_key => &$_value) {
            if (is_array($_value)) {
                $_value = $this->byKey($_value, $flags);
            }
        } // unset($_key, $_value); // Housekeeping.

        return $array;
    }
}


<?php
/**
 * RIJNDAEL 256 encryption utilities.
 *
 * @author @jaswrks
 * @copyright WebSharks™
 */
declare (strict_types = 1);
namespace WebSharks\Core\Classes\Core\Utils;

use WebSharks\Core\Classes;
use WebSharks\Core\Interfaces;
use WebSharks\Core\Traits;
#
use WebSharks\Core\Classes\Core\Error;
use WebSharks\Core\Classes\Core\Base\Exception;
#
use function assert as debug;
use function get_defined_vars as vars;

/**
 * RIJNDAEL 256 encryption utilities.
 *
 * @since 150424 Initial release.
 */
class Rijndael256 extends Classes\Core\Base\Core
{
    /**
     * IV size.
     *
     * @since 150424
     *
     * @var int
     */
    protected $iv_size;

    /**
     * Key size.
     *
     * @since 150424
     *
     * @var int
     */
    protected $key_size;

    /**
     * Class constructor.
     *
     * @since 150424 Initial release.
     *
     * @param Classes\App $App Instance of App.
     */
    public function __construct(Classes\App $App)
    {
        parent::__construct($App);

        $this->iv_size  = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
        $this->key_size = mcrypt_get_key_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
    }

    /**
     * RIJNDAEL 256 encryption with a URL-safe base64 wrapper.
     *
     * @since 150424 Initial release.
     *
     * @param string $string String to encrypt.
     * @param string $key    Encryption key (required always).
     * @param bool   $sha1   Defaults to `true` (recommended).
     *
     * @return string Encrypted string (URL-safe base64).
     */
    public function encrypt(string $string, string $key = '', bool $sha1 = true): string
    {
        if (!isset($string[0])) {
            return $base64 = ''; // Nothing to do.
        } elseif (!$key && !($key = $this->App->Config->©encryption['©key'])) {
            throw $this->c::issue('Missing encryption key.');
        }
        $string = '~r2|'.$string; // `RIJNDAEL 256` identifier.

        if (strlen($key = (string) substr($key, 0, $this->key_size)) < $this->key_size) {
            throw $this->c::issue(sprintf('Key too short. Minimum length: `%1$s`.', $this->key_size));
        }
        if (strlen($iv = $this->c::randomKey($this->iv_size, false)) < $this->iv_size) {
            throw $this->c::issue(sprintf('IV too short. Minimum length: `%1$s`.', $this->iv_size));
        }
        if (!is_string($e = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $string, MCRYPT_MODE_CBC, $iv)) || !isset($e[0])) {
            throw $this->c::issue('String encryption failed; `$e` is not a string; or it has no length.');
        }
        $e = '~r2:'.$iv.($sha1 ? ':'.sha1($e) : '').'|'.$e; // Pack components.

        return $base64 = $this->c::base64UrlSafeEncode($e);
    }

    /**
     * RIJNDAEL 256 decryption with a URL-safe base64 unwrapper.
     *
     * @since 150424 Initial release.
     *
     * @param string $base64 String to decrypt (URL-safe base64).
     * @param string $key    Encryption key (required always).
     *
     * @return string Decrypted string; or empty string on failure.
     */
    public function decrypt(string $base64, string $key = ''): string
    {
        if (!isset($base64[0])) {
            return $string = '';
        } elseif (!$key && !($key = $this->App->Config->©encryption['©key'])) {
            throw $this->c::issue('Missing encryption key.');
        }
        $regex = // Matches a valid encryption.

          '/^'.// Beginning of the strong.
            '~r2'.// Required RIJNDAEL 256 marker.
            '\:(?<iv>[a-zA-Z0-9]{'.$this->iv_size.'})'.// Required IV.
            '(?:\:(?<sha1>[a-zA-Z0-9]{40}))?'.// Optional checksum.
            '\|(?<e>.+)'.// Encrypted string (not empty).
          '$/s'; // End of string.

        if (!($e = $this->c::base64UrlSafeDecode($base64))) {
            return $string = ''; // Not possible.
        }
        if (!preg_match($regex, $e, $iv_sha1_e)) {
            return $string = ''; // Not possible.
        }
        if (!empty($iv_sha1_e['sha1']) && $iv_sha1_e['sha1'] !== sha1($iv_sha1_e['e'])) {
            return $string = ''; // Invalid checksum; automatic failure.
        }
        if (strlen($key = (string) substr($key, 0, $this->key_size)) < $this->key_size) {
            throw $this->c::issue(sprintf('Key too short. Minimum length: `%1$s`.', $this->key_size));
        }
        if (!is_string($string = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $iv_sha1_e['e'], MCRYPT_MODE_CBC, $iv_sha1_e['iv'])) || !isset($string[0])) {
            throw $this->c::issue('String decryption failed; `$string` is NOT a string, or it has no length.');
        }
        if (!strlen($string = preg_replace('/^~r2\|/', '', $string, 1, $r2)) || !$r2) {
            return $string = ''; // Missing packed component identifier.
        }
        return $string = $this->c::mbRTrim($string, "\0\4"); // See: <http://www.asciitable.com/>.
    }
}


<?php
/**
 * Current URL utilities.
 *
 * @author @jaswrks
 * @copyright WebSharks™
 */
declare(strict_types=1);
namespace WebSharks\Core\Classes\Core\Utils;

use WebSharks\Core\Classes;
use WebSharks\Core\Interfaces;
use WebSharks\Core\Traits;
#
use WebSharks\Core\Classes\Core\Error;
use WebSharks\Core\Classes\Core\Base\Exception;
#
use function assert as debug;
use function get_defined_vars as vars;

/**
 * Current URL utilities.
 *
 * @since 150424 Initial release.
 */
class UrlCurrent extends Classes\Core\Base\Core
{
    /**
     * Class constructor.
     *
     * @since 150424 Initial release.
     *
     * @param Classes\App $App Instance of App.
     */
    public function __construct(Classes\App $App)
    {
        parent::__construct($App);

        if ($this->c::isCli()) {
            throw $this->c::issue('Not possible in CLI mode.');
        }
    }

    /**
     * Current URL.
     *
     * @since 150424 Initial release.
     *
     * @param bool $canonical Canonical?
     *
     * @return string Current URL.
     */
    public function __invoke(bool $canonical = false): string
    {
        if (($url = &$this->cacheKey(__FUNCTION__, $canonical)) !== null) {
            return $url; // Cached this already.
        }
        return $url = $this->scheme().'://'.$this->host().$this->uri($canonical);
    }

    /**
     * Current request method.
     *
     * @since 170824.30708 Current method.
     *
     * @return string Current method.
     */
    public function method(): string
    {
        if (($method = &$this->cacheKey(__FUNCTION__)) !== null) {
            return $method; // Cached this already.
        }
        return $method = mb_strtoupper($_SERVER['REQUEST_METHOD']);
    }

    /**
     * Current scheme; lowercase.
     *
     * @since 150424 Initial release.
     *
     * @return string Current scheme; lowercase.
     */
    public function scheme(): string
    {
        if (($scheme = &$this->cacheKey(__FUNCTION__)) !== null) {
            return $scheme; // Cached this already.
        }
        return $scheme = $this->isSsl() ? 'https' : 'http';
    }

    /**
     * Current host name; lowercase.
     *
     * @since 150424 Initial release.
     *
     * @param bool $with_port With port number?
     *
     * @return string Current host name; lowercase.
     */
    public function host(bool $with_port = true): string
    {
        if (($host = &$this->cacheKey(__FUNCTION__, $with_port)) !== null) {
            return $host; // Cached this already.
        }
        $host = mb_strtolower($_SERVER['HTTP_HOST']);

        if (!$with_port) { // Strip port number?
            $host = preg_replace('/\:[0-9]+$/u', '', $host);
        }
        return $host; // With or without port number.
    }

    /**
     * Current port number (as string).
     *
     * @since 170124.74961 Enhancing support for ports.
     *
     * @return string Current port (as string).
     */
    public function port(): string
    {
        if (($port = &$this->cacheKey(__FUNCTION__)) !== null) {
            return $port; // Cached this already.
        }
        return $port = (explode(':', $this->host(), 2) + ['', ''])[1];
    }

    /**
     * Current root host name; lowercase.
     *
     * @since 151002 Adding root host support.
     *
     * @param bool $with_port With port number?
     *
     * @return string Current root host name; lowercase.
     */
    public function rootHost(bool $with_port = true): string
    {
        if (($root_host = &$this->cacheKey(__FUNCTION__, $with_port)) !== null) {
            return $root_host; // Cached this already.
        }
        $name_parts       = explode('.', $this->host(false));
        $root_name        = implode('.', array_slice($name_parts, -2));
        $port             = $this->port(); // Might be needed in next line.
        return $root_host = $root_name.(!$with_port ? '' : (isset($port[0]) ? ':'.$port : ''));
    }

    /**
     * Current root port number (as string).
     *
     * @since 170124.74961 Enhancing support for ports.
     *
     * @return string Current root port (as string).
     */
    public function rootPort(): string
    {
        if (($port = &$this->cacheKey(__FUNCTION__)) !== null) {
            return $port; // Cached this already.
        }
        return $port = (explode(':', $this->rootHost(), 2) + ['', ''])[1];
    }

    /**
     * Current URI; with a leading `/`.
     *
     * @since 150424 Initial release.
     *
     * @param bool $canonical Canonical?
     *
     * @return string Current URI; with a leading `/`.
     */
    public function uri(bool $canonical = false): string
    {
        if (($uri = &$this->cacheKey(__FUNCTION__, $canonical)) !== null) {
            return $uri; // Cached this already.
        }
        $uri = $_SERVER['REQUEST_URI'];
        $uri = '/'.$this->c::mbLTrim($uri, '/');

        if ($canonical) { // Strip query/fragment.
            $uri = preg_split('/[?#]/u', $uri, 2)[0];
        }
        return $uri; // With or without query/fragment.
    }

    /**
     * Current URI/path; with a leading `/`.
     *
     * @since 150424 Initial release.
     *
     * @return string Current URI/path; with a leading `/`.
     */
    public function path(): string
    {
        if (($path = &$this->cacheKey(__FUNCTION__)) !== null) {
            return $path; // Cached this already.
        }
        $path        = (string) parse_url($this->uri(), PHP_URL_PATH);
        return $path = '/'.$this->c::mbLTrim($path, '/');
    }

    /**
     * Current `index.php/path/info`.
     *
     * @since 150424 Initial release.
     *
     * @return string e.g., `index.php/path/info`.
     */
    public function pathInfo(): string
    {
        if (($path_info = &$this->cacheKey(__FUNCTION__)) !== null) {
            return $path_info; // Cached this already.
        }
        $path_info = ''; // Initialize.

        if (isset($_SERVER['PATH_INFO'])) {
            $path_info = $_SERVER['PATH_INFO'];
        }
        if (mb_strpos($path_info, '?') !== false) {
            list($path_info) = explode('?', $path_info, 2);
        }
        $path_info        = $this->c::mbTrim($path_info, '/');
        return $path_info = str_replace('%', '%25', $path_info);
    }

    /**
     * Is an SSL scheme?
     *
     * @since 150424 Initial release.
     *
     * @return bool Is an SSL scheme?
     */
    public function isSsl(): bool
    {
        if (($is = &$this->cacheKey(__FUNCTION__)) !== null) {
            return $is; // Cached this already.
        }
        if (!empty($_SERVER['SERVER_PORT'])) {
            if ((int) $_SERVER['SERVER_PORT'] === 443) {
                return $is = true;
            }
        }
        if (!empty($_SERVER['HTTPS'])) {
            if (filter_var($_SERVER['HTTPS'], FILTER_VALIDATE_BOOLEAN)) {
                return $is = true;
            }
        }
        if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
            if ($this->c::mbStrCaseCmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0) {
                return $is = true;
            }
        }
        return $is = false;
    }

    /**
     * Current URL is on a localhost?
     *
     * @since 150424 Initial release.
     *
     * @return bool `TRUE` if current URL is on a localhost.
     */
    public function isLocalhost(): bool
    {
        if (($is = &$this->cacheKey(__FUNCTION__)) !== null) {
            return $is; // Cached this already.
        }
        if (defined('LOCALHOST') && LOCALHOST) {
            return $is = true;
        } elseif (preg_match('/(?:\b(?:localhost|127\.0\.0\.1)\b|\.vm)$/ui', $this->host(false))) {
            return $is = true;
        }
        return $is = false;
    }
}


<?php

namespace Mingalevme\HttpBuildUrl;

class HttpBuildUrl
{
    /**
     * Build an URL
     *
     * @param string|array $url Base url or parts of the url (aliases is not supported), to build from scratch use any falsy value, e.g. [], null or ''
     * @param array $replacement [optional] Associative array like parse_url() returns
     * @param array &$newUrlParts [optional] If set, it will be filled with the parts of the composed url like parse_url() would return
     * @return string The new URL string
     */
    public static function build($url, $replacement=[], &$newUrlParts=null)
    {
        /**
         * "p" is not used to not confusing between port, path, password;
         * use fully qualified name
         */
        $aliases = [
            'u' => 'user',
            's' => 'scheme',
            'h' => 'host',
            'q' => 'query',
            'f' => 'fragment',
        ];

        /**
         * Resolve aliases
         */
        foreach ($replacement as $k => $value) {
            if (isset($aliases[strtolower($k)])) {
                $key = $aliases[strtolower($k)];
                if ($k === strtoupper($k)) {
                    $replacement[strtoupper($key)] = $value;
                } else {
                    $replacement[$key] = $value;
                }
                unset($replacement[$k]);
            }
        }

        /**
         * Parse the original URL
         */
        $parts = is_array($url)
            ? $url
            : parse_url(trim($url));

        $isSchemaless = is_string($url) && isset($parts['scheme']) === false && substr($url, 0, 2) === '//';

        foreach ($replacement as $key => $value) {

            if ($value === null) {

                unset($parts[$key]);

            } elseif ($key === strtoupper($key) || isset($parts[$key]) == false) {

                $parts[strtolower($key)] = $replacement[$key];

            } elseif ($key == 'query') {

                /**
                 * Merging query args
                 */

                $baseQuery = [];

                parse_str($parts['query'], $baseQuery);

                if (is_array($value)) {
                    $query = $value;
                } else {
                    $query = array();
                    parse_str($value, $query);
                }

                $parts['query'] = array_merge($baseQuery, $query);

            }

        }

        if (isset($parts['query']) && is_array($parts['query'])) {
            $parts['query'] = http_build_query($parts['query']);
        }

        $newUrlParts = $parts;

        return
            ((isset($parts['scheme'])) ? $parts['scheme'] . '://' : ($isSchemaless ? '//' : ''))
            .((isset($parts['user'])) ? $parts['user'] . ((isset($parts['pass'])) ? ':' . $parts['pass'] : '') .'@' : '')
            .((isset($parts['host'])) ? $parts['host'] : '')
            .((isset($parts['port'])) ? ':' . $parts['port'] : '')
            .((isset($parts['path'])) ? $parts['path'] : '')
            .((isset($parts['query']) && $parts['query']) ? '?' . $parts['query'] : '')
            .((isset($parts['fragment'])) ? '#' . $parts['fragment'] : '')
        ;
    }
}

function build_url($url, $replacement=[], &$newUrlParts=null)
{
    return HttpBuildUrl::build($url, $replacement, $newUrlParts);
}


<?php

/*
 * This file is part of the Ivory Google Map package.
 *
 * (c) Eric GELOEN <geloen.eric@gmail.com>
 *
 * For the full copyright and license information, please read the LICENSE
 * file that was distributed with this source code.
 */

namespace Ivory\GoogleMap\Services\Geocoding\Result;

use Ivory\GoogleMap\Base\Bound;
use Ivory\GoogleMap\Base\Coordinate;
use Ivory\GoogleMap\Exception\GeocodingException;

/**
 * GeocoderGeometry which describes a google map geocoder geometry.
 *
 * @see http://code.google.com/apis/maps/documentation/javascript/reference.html#GeocoderGeometry
 * @author GeLo <geloen.eric@gmail.com>
 */
class GeocoderGeometry
{
    /** @var \Ivory\GoogleMap\Base\Coordinate */
    protected $location;

    /** @var string */
    protected $locationType;

    /** @var \Ivory\GoogleMap\Base\Bound */
    protected $viewport;

    /** @var \Ivory\GoogleMap\Base\Bound */
    protected $bound;

    /**
     * Create a geocoder geometry.
     *
     * @param \Ivory\GoogleMap\Base\Coordinate $location     The geometry location.
     * @param string                           $locationType The geometry location type.
     * @param \Ivory\GoogleMap\Base\Bound      $viewport     The geometry viewport.
     * @param \Ivory\GoogleMap\Base\Bound      $bound        The geometry bound.
     */
    public function __construct(Coordinate $location, $locationType, Bound $viewport, Bound $bound = null)
    {
        $this->setLocation($location);
        $this->setLocationType($locationType);
        $this->setViewport($viewport);
        $this->setBound($bound);
    }

    /**
     * Gets the geometry location.
     *
     * @return \Ivory\GoogleMap\Base\Coordinate The geometry location.
     */
    public function getLocation()
    {
        return $this->location;
    }

    /**
     * Sets the geometry location.
     *
     * @param \Ivory\GoogleMap\Base\Coordinate $location The geometry location.
     */
    public function setLocation(Coordinate $location)
    {
        $this->location = $location;
    }

    /**
     * Gets the geometry location type.
     *
     * @return string The geometry location type.
     */
    public function getLocationType()
    {
        return $this->locationType;
    }

    /**
     * Sets the geometry location type.
     *
     * @param string $locationType The geometry location type.
     *
     * @throws \Ivory\GoogleMap\Exception\GeocodingException If the location type is not valid.
     */
    public function setLocationType($locationType)
    {
        if (!in_array($locationType, GeocoderLocationType::getGeocoderLocationTypes())) {
            throw GeocodingException::invalidGeocoderLocationType();
        }

        $this->locationType = $locationType;
    }

    /**
     * Gets the geometry viewport
     *
     * @return \Ivory\GoogleMap\Base\Bound The geometry viewport.
     */
    public function getViewport()
    {
        return $this->viewport;
    }

    /**
     * Sets the geometry viewport.
     *
     * @param \Ivory\GoogleMap\Base\Bound $viewport The geometry viewport.
     */
    public function setViewport(Bound $viewport)
    {
        $this->viewport = $viewport;
    }

    /**
     * Gets the geometry bound.
     *
     * @return \Ivory\GoogleMap\Base\Bound The geometry bound.
     */
    public function getBound()
    {
        return $this->bound;
    }

    /**
     * Sets the geometry bound.
     *
     * @param \Ivory\GoogleMap\Base\Bound $bound The geometry bound.
     */
    public function setBound(Bound $bound = null)
    {
        $this->bound = $bound;
    }
}


<?php
/**
 * Regex escape utilities.
 *
 * @author @jaswrks
 * @copyright WebSharks™
 */
declare(strict_types=1);
namespace WebSharks\Core\Classes\Core\Utils;

use WebSharks\Core\Classes;
use WebSharks\Core\Interfaces;
use WebSharks\Core\Traits;
#
use WebSharks\Core\Classes\Core\Error;
use WebSharks\Core\Classes\Core\Base\Exception;
#
use function assert as debug;
use function get_defined_vars as vars;

/**
 * Regex escape utilities.
 *
 * @since 150424 Initial release.
 */
class RegexEscape extends Classes\Core\Base\Core
{
    /**
     * Escape regex meta chars deeply.
     *
     * @since 150424 Initial release.
     *
     * @param mixed  $value     Any input value.
     * @param string $delimiter Defaults to `/`.
     *
     * @return string|array|object Quoted deeply.
     */
    public function __invoke($value, string $delimiter = '/')
    {
        if (is_array($value) || is_object($value)) {
            foreach ($value as $_key => &$_value) {
                $_value = $this->__invoke($_value, $delimiter);
            } // unset($_key, $_value);
            return $value;
        }
        if (!($string = (string) $value)) {
            return $string; // Nothing to do.
        }
        return preg_quote($string, $delimiter);

        // Multibyte safe. See: http://jas.xyz/1PvQJty
    }

    /**
     * Matches zero or more escapable chars,
     * and anything that's not vertical whitespace.
     *
     * @since 170211.63148 Markdown enhancements.
     *
     * @param string $escapable_chars Escapable chars.
     * @param bool   $ungreedy        Ungreedy matching?
     *
     * @return string Regex fragment suitable for `preg_*()`.
     */
    public function m0EscNoVws(string $escapable_chars, bool $ungreedy = false): string
    {
        $escapable_chars = $this->__invoke($escapable_chars);
        return '(?:[^\v'.$escapable_chars.'\\\\]|\\\\['.$escapable_chars.'])*'.($ungreedy ? '?' : '');
    }
}


<?php

/*
 * This file is part of the Ivory Google Map package.
 *
 * (c) Eric GELOEN <geloen.eric@gmail.com>
 *
 * For the full copyright and license information, please read the LICENSE
 * file that was distributed with this source code.
 */

namespace Ivory\GoogleMap\Services\Directions;

/**
 * A directions status which describes the google map direction status.
 *
 * @see http://code.google.com/apis/maps/documentation/javascript/reference.html#DirectionsStatus
 * @author GeLo <geloen.eric@gmail.com>
 */
class DirectionsStatus
{
    const INVALID_REQUEST = 'INVALID_REQUEST';
    const MAX_WAYPOINTS_EXCEEDED = 'MAX_WAYPOINTS_EXCEEDED';
    const NOT_FOUND = 'NOT_FOUND';
    const OK = 'OK';
    const OVER_QUERY_LIMIT = 'OVER_QUERY_LIMIT';
    const REQUEST_DENIED = 'REQUEST_DENIED';
    const UNKNOWN_ERROR = 'UNKNOWN_ERROR';
    const ZERO_RESULTS = 'ZERO_RESULTS';

    /**
     * Disabled constructor.
     *
     * @codeCoverageIgnore
     */
    final private function __construct()
    {

    }

    /**
     * Gets the available directions status.
     *
     * @return array The available directions status.
     */
    public static function getDirectionsStatus()
    {
        return array(
            self::INVALID_REQUEST,
            self::MAX_WAYPOINTS_EXCEEDED,
            self::NOT_FOUND,
            self::OK,
            self::OVER_QUERY_LIMIT,
            self::REQUEST_DENIED,
            self::UNKNOWN_ERROR,
            self::ZERO_RESULTS,
        );
    }
}


<?php
/**
 * Overload members.
 *
 * @author @jaswrks
 * @copyright WebSharks™
 */
declare(strict_types=1);
namespace WebSharks\Core\Traits;

use WebSharks\Core\Classes;
use WebSharks\Core\Interfaces;
use WebSharks\Core\Traits;
#
use WebSharks\Core\Classes\Core\Error;
use WebSharks\Core\Classes\Core\Base\Exception;
#
use function assert as debug;
use function get_defined_vars as vars;

/**
 * Overload members.
 *
 * @since 150424 Initial release.
 */
trait OverloadMembers
{
    /**
     * Overload data.
     *
     * @since 17xxxx Initial release.
     *
     * @type array Overload data.
     */
    protected $x___overload_data = [];

    /**
     * Writable overload keys.
     *
     * @since 150424 Initial release.
     *
     * @type array Writable overload keys.
     */
    protected $x___writable_overload_keys = [];

    /**
     * Serialized properties.
     *
     * @since 150424 Initial release.
     *
     * @return string Serialized data.
     */
    public function serialize(): string
    {
        return serialize((object) $this->overloadPropertyData());
    }

    /**
     * Unserialize handler.
     *
     * @since 150424 Initial release.
     *
     * @param string $serialized Serialized data.
     */
    public function unserialize($serialized)
    {
        $this->overload((object) unserialize($serialized), true);
    }

    /**
     * JSON-encode properties.
     *
     * @since 150424 Initial release.
     *
     * @return mixed Properties to JSON-encode.
     */
    public function jsonSerialize()
    {
        return (object) $this->overloadPropertyData();
    }

    /**
     * Properties to dump.
     *
     * @since 150424 Initial release.
     *
     * @return array Properties to dump.
     */
    public function __debugInfo(): array
    {
        $properties = call_user_func('get_object_vars', $this);
        unset($properties['Di'], $properties['App'], $properties['Utils']);
        return $properties; // To include in debug.
    }

    /**
     * Magic/overload checker.
     *
     * @since 150424 Initial release.
     *
     * @param string $property Property key.
     *
     * @return bool True if non-null.
     */
    public function __isset(string $property): bool
    {
        if (!isset($property[1]) && ($property === 'c' || $property === 's' || $property === 'a' || $property === 'f')) {
            // NOTE: `f` comes last here, because it improves the speed at which a match occurs.
            return true; // Public read-only access.
        } else {
            return isset($this->x___overload_data[$property]);
        }
    }

    /**
     * Magic/overload properties.
     *
     * @since 150424 Initial release.
     *
     * @param string $property Property key.
     *
     * @return mixed Property value.
     */
    public function __get(string $property)
    {
        if (!isset($property[1]) && ($property === 'c' || $property === 's' || $property === 'a' || $property === 'f')) {
            // NOTE: `f` comes last because it improves the speed at which a match occurs.
            return $this->{$property}; // Public read-only access.
            //
        } elseif (isset($this->x___overload_data[$property])) {
            return $this->x___overload_data[$property];
            //
        } elseif (array_key_exists($property, $this->x___overload_data)) {
            return $this->x___overload_data[$property];
        }
        throw $this->c::issue([], sprintf('Undefined `%1$s` property.', $property), __METHOD__);
    }

    /**
     * Magic/overload property setter.
     *
     * @since 150424 Initial release.
     *
     * @param string $property Property key.
     * @param mixed  $value    Property value.
     */
    public function __set(string $property, $value)
    {
        if (isset($this->x___writable_overload_keys[$property])) {
            if ($this->x___writable_overload_keys[$property] === 1) {
                $this->{$property} = $value;
            } else {
                $this->x___overload_data[$property] = $value;
            }
            return; // Done here.
        }
        throw $this->c::issue([], sprintf('Refused to set `%1$s` property.', $property), __METHOD__);
    }

    /**
     * Magic unset handler.
     *
     * @since 150424 Initial release.
     *
     * @param string $property Property key.
     *
     * @internal Note: This intentionally does NOT unset `$this->{$property}`,
     * as that would allow direct public write access to a read-only property.
     * ---
     * @internal If unsetting a property that was given direct public write access,
     * calling `unset()` will not trigger this method, as it unsets the real property.
     * For that reason, if unsetting an overloaded property with direct public write access,
     * you must call `unset()` twice; i.e., calling a second time unsets the overload reference.
     */
    public function __unset(string $property)
    {
        unset($this->x___overload_data[$property]);
        unset($this->x___writable_overload_keys[$property]);
    }

    /**
     * Magic call handler.
     *
     * @since 150424 Initial release.
     *
     * @param string $method Method key/name.
     * @param array  $args   Arguments to pass.
     *
     * @return mixed Overloaded method return value.
     */
    public function __call(string $method, array $args = [])
    {
        if (isset($this->x___overload_data[$method])) {
            return $this->x___overload_data[$method](...$args);
        }
        throw $this->c::issue([], sprintf('Undefined `%1$s` method.', $method), __METHOD__);
    }

    /**
     * Is class overloaded?
     *
     * @since 160515 Overload conditional.
     *
     * @return bool True if class is overloaded.
     */
    protected function isOverloaded(): bool
    {
        return !empty($this->x___overload_data);
    }

    /**
     * Current overload data.
     *
     * @since 17xxxx Overload conditional.
     *
     * @return array Current overload data.
     */
    protected function overloadData(): array
    {
        foreach ($this->x___overload_data as $_key => $_value) {
            $properties[$_key] = $_value;
        } // unset($_key, $_value);

        return $properties ?? [];
    }

    /**
     * All overload keys.
     *
     * @since 17xxxx Overload utils.
     *
     * @return array All overload keys.
     */
    protected function overloadKeys(): array
    {
        return array_keys($this->overloadData());
    }

    /**
     * Overload property data.
     *
     * @since 17xxxx Enhance overload utils.
     *
     * @return array Overload property data.
     */
    protected function overloadPropertyData(): array
    {
        foreach ($this->x___overload_data as $_key => $_value) {
            if (!is_callable($_value)) {
                $properties[$_key] = $_value;
            }
        } // unset($_key, $_value);

        return $properties ?? [];
    }

    /**
     * Writable overload keys.
     *
     * @since 17xxxx Overload utils.
     *
     * @return array Writable overload keys.
     */
    protected function overloadPropertyKeys(): array
    {
        return array_keys($this->overloadPropertyData());
    }

    /**
     * Writable overload property data.
     *
     * @since 17xxxx Enhance overload utils.
     *
     * @return array Writable overload property data.
     */
    protected function writableOverloadPropertyData(): array
    {
        foreach ($this->x___overload_data as $_key => $_value) {
            if (isset($this->x___writable_overload_keys[$_key]) && !is_callable($_value)) {
                $properties[$_key] = $_value;
            }
        } // unset($_key, $_value);

        return $properties ?? [];
    }

    /**
     * Writable overload property keys.
     *
     * @since 17xxxx Overload utils.
     *
     * @return array Writable overload property keys.
     */
    protected function writableOverloadPropertyKeys(): array
    {
        return array_keys($this->writableOverloadPropertyData());
    }

    /**
     * Setup overloads.
     *
     * @since 150424 Initial release.
     *
     * @param array|object $properties Property(s).
     *
     * @internal This will overload properties by reference.
     *   Object properties can be set as writable or not writable.
     *   -
     * @internal Property names can be overloaded to provide public or JSON access.
     *   Overloaded object property names can be set as writable or not writable.
     *
     * @param bool $writable Overloaded properties are writable?
     */
    protected function overload($properties, bool $writable = false)
    {
        if (is_object($properties)) {
            foreach ($properties as $_property => &$_value) {
                if ($writable) {
                    unset($this->{$_property});
                    unset($this->x___overload_data[$_property]);
                    $this->x___writable_overload_keys[$_property] = 1;
                    $this->{$_property}                           = null;
                    $this->{$_property}                           = &$_value;
                    $this->x___overload_data[$_property]          = &$this->{$_property};
                } else { // NOT writable.
                    unset($this->{$_property});
                    unset($this->x___overload_data[$_property]);
                    unset($this->x___writable_overload_keys[$_property]);
                    $this->x___overload_data[$_property] = &$_value;
                }
            } // unset($_property, $_value);
            return; // Stop now; all done here.
            //
        } elseif (is_array($properties)) {
            foreach ($properties as $_key => $_property) {
                if (!property_exists($this, $_property)) {
                    throw $this->c::issue([], sprintf('No `%1$s` property.', $_property), __METHOD__);
                }
                if ($writable) {
                    unset($this->x___overload_data[$_property]);
                    $this->x___writable_overload_keys[$_property] = -1;
                    $this->x___overload_data[$_property]          = &$this->{$_property};
                } else { // NOT writable.
                    unset($this->x___overload_data[$_property]);
                    unset($this->x___writable_overload_keys[$_property]);
                    $this->x___overload_data[$_property] = &$this->{$_property};
                }
            } // unset($_key, $_property);
            return; // Stop now; all done here.
        }
        throw $this->c::issue([], sprintf('`%1$s`; expecting object/array.', gettype($properties)), __METHOD__);
    }
}


<?php
//------------------------
//· 数据库操作类
//---------------------
namespace denha\db;

use denha;
use denha\Route;
use denha\Trace;
use \Exception;
use \PDO;

class BuildSql
{

    public static $dbConfig;
    public static $instance; // 单例实例化;
    public static $do; // 数据库操作符
    public $id; // 当前链接配置ID
    public $link; // 当前链接符
    public $options; // 记录参数信息
    public $bulid; // 记录构造Sql;
    public $config; //

    private function __construct($dbConfig = '')
    {
        $this->config($dbConfig);

    }

    //单例实例化 避免重复New暂用资源
    public static function getInstance($dbConfig = '')
    {
        if (is_null(self::$instance)) {
            self::$instance = new BuildSql($dbConfig);
        }

        return self::$instance;

    }

    public function config($dbConfig = [])
    {
        if ($dbConfig) {
            self::$dbConfig = $dbConfig;
        } else {
            self::$dbConfig = config('dbConfig', 'db');
        }

        foreach (self::$dbConfig as $key => $value) {
            $hash = md5(json_encode(self::$dbConfig[$key]));
            if (!isset(self::$do[$hash])) {
                self::$do[$hash] = $this->open($value);
            }

        }
    }

    public function parseDNS($config)
    {
        switch ($config['type']) {
            case 'mysql':
            case 'mysqli':
                $dns = 'mysql:host=' . $config['host'] . ';dbname=' . $config['name'];
                break;
            case 'sqlite':
                $dns = 'sqlite:' . $config['name'];
                break;
            default:
                # code...
                break;
        }

        return $dns;
    }

    /** 打开数据库链接 */
    public function open($config)
    {
        $config['user'] = isset($config['user']) ? $config['user'] : '';
        $config['pwd']  = isset($config['pwd']) ? $config['pwd'] : '';

        try {
            $do = new PDO($this->parseDNS($config), $config['user'], $config['pwd']);
        } catch (\PDOException $e) {
            $msg = $e->getMessage() . '
SQL Config:
' . PHP_EOL;
            foreach ($config as $key => $value) {
                $msg .= $key . ' : ' . $value . '
' . PHP_EOL;
            }
            throw new Exception($msg);
        }

        $do->exec('set names ' . $config['charset']);
        $do->exec('SET sql_mode =\'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION\'');

        return $do;

    }

    /** 获取最后执行SQL */
    public function getLastSql()
    {

        return $this->sqlInfo['sql'];
    }

    /** 链接 */
    public function connect($id = 0)
    {
        $this->id   = $id;
        $this->link = self::$do[md5(json_encode(self::$dbConfig[$id]))];

        if (!$this->link) {
            throw new Exception('链接信息异常');
        }

        $this->config              = self::$dbConfig[$id];
        $this->tablepre            = $this->config['prefix'];
        $this->options['tablepre'] = $this->config['prefix'];
        $this->options['database'] = $this->config['name'];

        return $this;
    }

    /** 构造Sql初始化 */
    public function init()
    {

        $this->bulid = [
            'field'     => '*',
            'sql'       => '',
            'chilidSql' => false,
            'data'      => null,
            'order'     => null,
            'group'     => null,
        ];

        $this->options = [
            'data'  => [],
            'field' => [],
            'order' => [],
        ];

        $this->connect();
    }

    /**
     * 子查询 如果开启 则直接返回sql
     * @date   2017-11-22T00:38:42+0800
     * @author ChenMingjiang
     * @param  boolean                  $value [true：开启子查询 false:关闭子查询]
     * @return [type]                          [description]
     */
    public function childSql($bool = false)
    {
        $this->bulid['chilidSql'] = $bool;

        return $this;
    }

    /**
     * 数据表
     * @date   2018-07-12T11:08:29+0800
     * @author ChenMingjiang
     * @param  [type]                   $table   [description]
     * @param  array                    $options [description]
     *                                  is_tablepre：是否使用表前缀
     *                                  link：链接数据库配置ID
     * @return [type]                            [description]
     */
    public function table($table, $options = [])
    {

        $isTablepre = isset($options['is_tablepre']) ? $options['is_tablepre'] : true;
        $link       = isset($options['link']) ? $options['link'] : '';

        // 初始化SQL参数
        $this->init();

        // 链接其他数据库
        if ($link) {
            $this->connect($link);
        }

        $this->table = parseName($table);
        if ($isTablepre && $this->options['tablepre']) {
            $this->options['table'] = [
                'name'        => $this->options['tablepre'] . parseName($table),
                'is_tablepre' => $isTablepre,
            ];
        } else {
            $this->options['table'] = [
                'name'        => parseName($table),
                'is_tablepre' => $isTablepre,
            ];
        }

        $this->bulid['table'] = $this->options['table']['name'];

        return $this;
    }

    /**
     * 获取表名称
     * @date   2017-06-10T22:56:01+0800
     * @author ChenMingjiang
     * @param  [type]                   $table [description]
     * @return [type]                          [description]
     */
    public function getTableName()
    {
        return $this->bulid['table'];
    }

    /**
     * 字段信息增加`标记
     * @date   2018-07-12T11:10:38+0800
     * @author ChenMingjiang
     * @param  [type]                   $field [description]
     */
    private function addFieldTag($field)
    {
        if (
            stripos($field, '`') === false
            && stripos($field, '_STRING') === false
            && stripos($field, '*') === false
            && stripos($field, 'CONCAT') === false
            && stripos($field, 'SUM') === false
            && stripos($field, 'AVG') === false
            && stripos($field, 'AVG') === false
            && stripos($field, 'AS') === false
            && stripos($field, 'DISTINCT') === false
            && stripos($field, 'FIELD') === false
        ) {
            $field = stripos($field, '.') !== false ? str_replace('.', '.`', $field) . '`' : '`' . $field . '`';
        }

        return $field;
    }

    /**
     * 查询条件
     * @date   2018-06-29T15:50:51+0800
     * @author ChenMingjiang
     * @param  [type]                   $where [description]
     * @param  [type]                   $value [description]
     * @param  [type]                   $exp   [description]
     * @return [type]                          [description]
     */
    public function where($where, $value = null, $exp = null)
    {

        if (!$where) {
            return $this;
        }

        // 存在三个参数 $exp => 参数值
        if ($value !== null && $exp !== null) {
            $map[$where] = [$value, $exp];
            $where       = $map;

        }
        // 存在两个参数
        elseif ($value !== null && $exp === null) {

            if (is_array($value)) {
                throw new Exception("SQL Where 参数值错误 {$where} = `数组`");
            }

            $map[$where] = $value;
            $where       = $map;
        }

        // 批量处理数组
        if (is_array($where)) {
            foreach ($where as $mapField => $v) {

                // 格式化字段
                $mapField = $this->addFieldTag($mapField);

                if (is_array($v)) {
                    list($mapExp, $mapValue) = $v;
                } elseif ($mapField == '_string') {
                    $mapExp   = '_string';
                    $mapValue = $v;
                } else {
                    $mapExp   = '=';
                    $mapValue = $v;
                }

                // 解析map
                $this->options['map'][] = $whereMap[] = $this->parseMap($mapField, $mapExp, $mapValue);
            }

            $newWhere = $this->parseWhere($whereMap);

        }
        // 单条where语句
        else {
            //记录条件参数
            $this->options['map'][] = $where;
        }

        if (!empty($newWhere)) {
            if (!isset($this->bulid['where'])) {
                $this->bulid['where'] = ' WHERE ' . substr($newWhere, 5);
            } else {
                $this->bulid['where'] .= $newWhere;
            }
        }

        return $this;
    }

    /** 解析查询条件 */
    public function parseWhere($whereMap)
    {
        if (count($whereMap) > 1) {
            // 第一个OR出现
            $orStart = true;
            foreach ($whereMap as $kk => $vv) {
                if (trim($vv[1]) == 'OR' && $orStart) {
                    $orStart = false;
                    // 存在上一条信息 则上一条增加一个 "("
                    if ($kk > 0) {
                        $whereMap[($kk - 1)][1] = $whereMap[($kk - 1)][1] . ' ( ';
                    }
                    // 否则当前信息增加一个 "("
                    else {
                        $whereMap[$kk][1] = $whereMap[$kk][1] . ' ( ';
                    }
                }

                // 最后一个OR出现
                if (trim($vv[1]) == 'AND' && !$orStart) {
                    $orStart          = true;
                    $whereMap[$kk][0] = $whereMap[$kk][0] . ' ) ';
                }
                // 最后一条数据
                elseif (!$orStart && count($whereMap) == ($kk + 1)) {
                    $orStart          = true;
                    $whereMap[$kk][0] = $whereMap[$kk][0] . ' ) ';
                }

            }
        }

        $map = '';
        foreach ($whereMap as $vv) {
            $map .= $vv[1] . $vv[0];
        }

        return $map;
    }

    /** 处理查询条件 */
    public function parseMap($mapField, $mapExp, $mapValue, $mapLink = 'AND')
    {
        $mapLink = ' ' . $mapLink . ' '; // 连接符
        $expRule = [
            ['>', '<', '>=', '<=', '!=', 'like', '<>', '='],
            ['in', 'not in', 'IN', 'NOT IN'],
            ['instr', 'INSTR'],
            ['between', 'BETWEEN'],
            ['or', 'OR'],
            ['_string', '_STRING'],
            ['find_in_set', 'FIND_IN_SET'],
        ];

        // '>', '<', '>=', '<=', '!=', 'like', '<>'
        if (in_array($mapExp, $expRule[0])) {
            $map = $mapField . '  ' . $mapExp . ' \'' . $mapValue . '\'';
        }
        // 'in', 'not in', 'IN', 'NOT IN'
        elseif (in_array($mapExp, $expRule[1])) {
            if (!$mapValue) {
                $map = $mapField . '  ' . $mapExp . ' (\'\')';
            } else {
                if (!is_array($mapValue) && stripos($mapValue, ',') !== false) {
                    $mapValue = explode(',', $mapValue);
                }
                $mapValue      = is_array($mapValue) ? $mapValue : (array) $mapValue;
                $commonInValue = '';
                foreach ($mapValue as $inValue) {
                    $commonInValue .= '\'' . $inValue . '\',';
                }
                $commonInValue = substr($commonInValue, 0, -1);
                $map           = $mapField . '  ' . $mapExp . ' (' . $commonInValue . ')';
            }
        }
        // 'instr', 'INSTR'
        elseif (in_array($mapExp, $expRule[2])) {
            $map = $mapExp . '(' . $mapField . ',\'' . $mapValue . '\')';
        }
        // 'between', 'BETWEEN'
        elseif (in_array($mapExp, $expRule[3])) {
            $map = $mapField . '  ' . $mapExp . ' \'' . $mapValue[0] . '\' AND \'' . $mapValue[1] . '\'';
        }
        // 'or', 'OR'
        elseif (in_array($mapExp, $expRule[4])) {
            if (count((array) $mapValue) == 2) {
                return $this->parseMap($mapField, $mapValue[0], $mapValue[1], strtoupper($mapExp));
            } else {
                $mapLink = ' ' . strtoupper($mapExp) . ' '; // 连接符
                $map     = $mapField . ' = \'' . $mapValue . '\'';
            }
        }
        // '_string', '_STRING'
        elseif (in_array($mapExp, $expRule[5])) {
            $map = $mapValue;
        }
        // 'find_in_set', 'FIND_IN_SET'
        elseif (in_array($mapExp, $expRule[6])) {
            $map = $mapExp . '(' . $mapValue . ', ' . $mapField . ')';
        } else {
            throw new Exception('SQL WHERE 参数错误 `' . $mapExp . '`');
        }
        return [$map, $mapLink];
    }

    /**
     * 关联查询
     * @date   2018-07-12T11:12:45+0800
     * @author ChenMingjiang
     * @param  [type]                   $table   [description]
     * @param  string                   $where   [description]
     * @param  string                   $float   [description]
     * @param  array                    $options [description]
     * @return [type]                            [description]
     */
    public function join($table, $where = '', $float = 'left')
    {

        if ($table == $this->options['table']['name']) {
            throw new Exception('表与关联表名字相同');
        }

        $where ?: $where = $this->options['table']['name'] . '.id = ' . $table . '.id';

        $this->options['join'] = ['table' => $table, 'where' => $where, 'float' => $float];

        if (!isset($this->bulid['join'])) {
            $this->bulid['join'] = ' ' . $float . ' JOIN ' . $table . ' ON ' . $where;
        } else {
            $this->bulid['join'] .= ' ' . $float . ' JOIN ' . $table . ' ON ' . $where;
        }

        return $this;
    }

    /**
     * 查询数量
     * @date   2017-03-19T16:18:13+0800
     * @author ChenMingjiang
     * @param  [type]                   $limit [description]
     * @return [type]                          [description]
     */
    public function limit($limit = 0, $pageSize = '')
    {
        $this->bulid['limit'] = ' LIMIT ' . $limit;
        if ($pageSize) {
            $this->bulid['limit'] = ' LIMIT ' . $limit . ',' . $pageSize;
        }

        $this->options['limit'] = ['limit' => $limit, 'pageSize' => $pageSize];

        return $this;
    }

    /** 查询字段 */
    public function field($field = '*')
    {

        if (!$field) {
            return $this;
        }

        $this->options['field'] = is_array($field) ? $field : explode(',', $field);

        $this->bulid['field'] = '';
        foreach ($this->options['field'] as $val) {
            $this->bulid['field'] .= $this->addFieldTag($val) . ',';
        }

        $this->bulid['field'] = substr($this->bulid['field'], 0, -1);

        return $this;
    }

    /** 编辑字段 */
    public function data($data, $exp = null, $value = null)
    {
        if ($exp !== null && $value === null) {
            $data = [$data => $exp];
        } elseif ($value !== null) {
            $data = [$data => [$exp, $value]];
        }

        $data = is_array($data) ? $data : explode(',', $data);

        $this->options['data'] = $data;
        $this->bulid['data']   = '';

        foreach ($data as $k => $v) {
            $k = $this->addFieldTag($k);
            if (is_array($v)) {

                $v[0] = strtolower($v[0]);

                if ($v[0] == 'add') {
                    $this->bulid['data'] .= $k . ' = ' . $k . ' + ' . $v[1] . ',';
                } elseif ($v[0] == 'less') {
                    $this->bulid['data'] .= $k . ' = ' . $k . ' - ' . $v[1] . ',';
                } elseif ($v[0] == 'concat') {
                    $this->bulid['data'] .= $k . ' = CONCAT(' . $k . ',\'\',\'' . str_replace('\'', '\\\'', $v[1]) . '\'),';
                } elseif ($v[0] == 'json') {
                    $this->bulid['data'] .= $k . ' = \'' . json_encode($v[1], JSON_UNESCAPED_UNICODE) . '\',';
                }
            } else {
                $v = str_replace('\\', '\\\\', $v);
                $v = str_replace('\'', '\\\'', $v);

                $this->bulid['data'] .= $k . ' = \'' . $v . '\',';
            }

        }

        $this->bulid['data'] = substr($this->bulid['data'], 0, -1);

        return $this;
    }

    /** 分组 */
    public function group($value = '')
    {

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

        $this->options['group'] = is_array($value) ? $value : explode(',', $value);

        foreach ($this->options['group'] as $val) {
            $this->bulid['group'] .= $this->addFieldTag($val) . ',';
        }

        if (!$this->bulid['order']) {
            $this->bulid['group'] = ' GROUP BY ' . substr($this->bulid['group'], 0, -1);
        } else {
            $this->bulid['group'] = ' , ' . substr($this->bulid['group'], 0, -1);
        }

        return $this;
    }

    /**
     *
     * 排序
     * @date   2018-12-19T10:53:46+0800
     * @author ChenMingjiang
     * @param  [type]                   $value [description]
     * @return [type]                   [description]
     */
    public function order($field)
    {
        if (!$field) {
            return $this;
        }

        if (!is_array($field) && stripos($field, '(') === false) {
            $parseOrder = explode(',', $field);
        } else {
            $parseOrder[] = $field;
        }

        foreach ($parseOrder as $key => $val) {

            // 包含 () 的关键字
            if (stripos($val, '(') !== false) {
                $parseOrder[$key] = ' ' . $val;
            } else {
                list($field, $exp) = explode(' ', $val);
                $parseOrder[$key]  = ' ' . $this->addFieldTag($field) . ' ' . $exp;
            }

        }

        if ($parseOrder) {

            $this->options['order'] = array_merge($parseOrder, $this->options['order']);

            if (!$this->bulid['order']) {
                $this->bulid['order'] = ' ORDER BY ' . implode(',', $parseOrder);
            } else {
                $this->bulid['order'] .= ' , ' . implode(',', $parseOrder);
            }

        }

        return $this;
    }

    /**
     * hvaing
     * @date   2017-11-22T01:18:55+0800
     * @author ChenMingjiang
     * @param  [type]                   $field [description]
     * @return [type]                          [description]
     */
    public function having($field)
    {
        $this->options['having'] = $field;
        $this->bulid['having']   = ' HAVING ' . $this->addFieldTag($field);
        return $this;
    }

    /**
     * 子查询 如果开启 则直接返回sql
     * @date   2017-11-22T00:38:42+0800
     * @author ChenMingjiang
     * @param  boolean                  $value [description]
     * @return [type]                          [description]
     */
    public function getSql($bool = true)
    {
        $this->options['childSql'] = $bool;

        if ($bool) {
            $this->bulidSql('SELECT');
            return $this->bulid['sql'];
        }

        return $this;
    }

    /**
     * 子查询table
     * @date   2017-11-22T00:45:38+0800
     * @author ChenMingjiang
     * @param  [type]                   $table [description]
     * @return [type]                          [description]
     */
    public function childSqlQuery($table)
    {
        $this->bulid['table'] = '(' . $table . ') as child';

        return $this;
    }

    /** 构建SQL语句 */
    public function bulidSql($type = 'SELECT')
    {
        $this->options['type'] = $type;

        if (!$this->bulid['table']) {
            throw new Exception('请选择数据表');
        }

        if ($type == 'SELECT') {
            $this->bulid['sql'] = 'SELECT ' . $this->bulid['field'] . ' FROM ' . $this->bulid['table'];
        } elseif ($type == 'UPDATE') {
            $this->bulid['sql'] = 'UPDATE ' . $this->bulid['table'] . ' SET ' . $this->bulid['data'];
        } elseif ($type == 'INSERT') {
            $this->bulid['sql'] = 'INSERT INTO ' . $this->bulid['table'] . ' SET ' . $this->bulid['data'];
        } elseif ($type == 'DELETE') {
            $this->bulid['sql'] = 'DELETE FROM ' . $this->bulid['table'];
        } elseif ($type == 'COUNT') {
            $this->bulid['sql'] = 'SELECT  COUNT(' . $this->bulid['field'] . ') AS  t  FROM ' . $this->bulid['table'];
        }

        empty($this->bulid['join']) ?: $this->bulid['sql'] .= $this->bulid['join'];
        empty($this->bulid['where']) ?: $this->bulid['sql'] .= $this->bulid['where'];
        empty($this->bulid['group']) ?: $this->bulid['sql'] .= $this->bulid['group'];
        empty($this->bulid['having']) ?: $this->bulid['sql'] .= $this->bulid['having'];
        empty($this->bulid['order']) ?: $this->bulid['sql'] .= $this->bulid['order'];
        empty($this->bulid['limit']) ?: $this->bulid['sql'] .= $this->bulid['limit'];

    }

    /** 查询数据表信息 */
    public function getTableStatus($field = '')
    {

        $this->field($field);
        $this->bulid['sql'] = 'SHOW TABLE STATUS WHERE NAME = \'' . $this->bulid['table'] . '\'';
        $result             = $this->query();
        $list               = $result->fetch(PDO::FETCH_ASSOC);

        if (count($this->options['field']) == 1) {
            foreach ($list as $key => $value) {
                if (!isset($list[$field])) {
                    throw new Exception('SQL ERROR : 字段信息不存在 [' . $field . ']');
                }

                $data = $list[$field];
            }
        } else {
            $data = $list;
        }

        return $data;
    }

    /** 查询表字段名 */
    public function getField($field = 'COLUMN_NAME')
    {
        $this->field($field);
        $where = ' WHERE table_name = \'' . $this->bulid['table'] . '\'';
        if ($this->bulid['group']) {
            $where .= $this->bulid['group'];
        }

        $this->bulid['sql'] = 'SELECT ' . $this->bulid['field'] . ' from information_schema.columns ' . $where;
        $result             = $this->query();
        $list               = $result->fetchAll(PDO::FETCH_ASSOC);

        if (count($this->options['field']) == 1) {
            foreach ($list as $key => $value) {
                $data[] = $value[$field];
            }
        } else {
            $data = $list;
        }

        return $data;
    }

    /**
     * 获取单个字段内容
     * @date   2018-04-06T21:35:17+0800
     * @author ChenMingjiang
     * @param  [type]                   $field [description]
     * @return [type]                          [description]
     */
    public function value($field = '')
    {

        if ($field) {
            $this->field($field);
        }

        $this->limit(1);
        $this->bulidSql('SELECT');

        $result = $this->query();

        if ($result) {
            $data = $result->fetchColumn();
        }

        return !empty($data) ? $data : '';

    }

    /**
     * 获取单个字段列表
     * @date   2018-07-12T14:41:02+0800
     * @author ChenMingjiang
     * @param  [type]                   $field [description]
     * @return [type]                          [description]
     */
    public function column($field = '')
    {

        if ($field) {
            $this->field($field);
        }

        $this->bulidSql('SELECT');

        $result = $this->query();

        while ($row = $result->fetch(PDO::FETCH_NUM)) {
            if (count($this->options['field']) == 2) {
                $data[$row[1]] = $row[0];
            } else {
                $data[] = $row[0];
            }
        }

        return isset($data) ? $data : [];
    }

    /**
     * 单条查询
     * @date   2018-07-13T11:01:05+0800
     * @author ChenMingjiang
     * @return [type]                   [description]
     */
    public function find($field = '')
    {

        if ($field) {
            $this->field($field);
        }

        $this->limit(1);
        $this->bulidSql('SELECT');

        $result = $this->query();

        $data = $result->fetch(PDO::FETCH_ASSOC);

        return $data;

    }

    /** 数组索引 */
    public function lists($field = '*')
    {
        if ($field) {
            $this->field($field);
        }

        $this->limit(1);
        $this->bulidSql('SELECT');

        $result = $this->query();

        $data = $result->fetch(PDO::FETCH_NUM);
        if ($data == false) {
            foreach ($this->options['field'] as $value) {
                $data[$value] = '';
            }
        }

        return $data;
    }

    /**
     * 多条查询
     * @date   2018-07-13T11:01:13+0800
     * @author ChenMingjiang
     * @return [type]                   [description]
     */
    public function select()
    {

        $this->bulidSql('SELECT');

        $result = $this->query();

        $data = $result->fetchAll(PDO::FETCH_ASSOC);

        return $data;
    }

    /**
     * 统计总条数
     * @date   2018-07-13T11:01:24+0800
     * @author ChenMingjiang
     * @param  [type]                   $value [description]
     * @return [type]                          [description]
     */
    public function count($value = null)
    {
        if ($value) {
            $this->field($value);
        }

        $this->bulidSql('COUNT');

        $result = $this->query();
        $data   = $result->fetchColumn();

        return (int) $data;

    }

    /** 添加 */
    public function add($data = '', $exp = null, $value = null)
    {

        $this->data($data, $exp, $value);

        $this->bulidSql('INSERT');
        $result = $this->query();
        $id     = $this->link->lastInsertId();

        return $id;
    }

    /**
     * 添加多条信息
     * @date   2017-09-19T15:45:40+0800
     * @author ChenMingjiang
     */
    public function addAll($data = [])
    {

        foreach ($data as $item) {
            $result = $this->add($item);
        }

        return $result;
    }

    /** 修改保存 */
    public function save($data = '', $exp = null, $value = null)
    {
        $this->data($data, $exp, $value);

        $this->bulidSql('UPDATE');
        $result = $this->query();
        $num    = $result->rowCount();

        return $num;
    }

    /** 删除 */
    public function delete()
    {

        if (empty($this->bulid['where'])) {
            throw new Exception('SQL ERROR : 禁止全表删除');
        }

        $this->bulidSql('DELETE');
        $result = $this->query();
        $num    = $result->rowCount();

        return $num;
    }

    // 开启事务
    public function startTrans()
    {
        $this->link->beginTransaction();
        return true;
    }

    // 回滚事务
    public function rollback()
    {
        $this->link->rollBack();
        return true;
    }

    // 提交事务
    public function commit()
    {
        $this->link->commit();
        return true;
    }

    /**
     * 执行
     * @date   2017-03-19T16:20:36+0800
     * @author ChenMingjiang
     * @param  [type]                   $sql [description]
     * @return [type]                        [description]
     */
    public function query($sql = '')
    {

        // 存入Sql Explain信息
        if (!empty($this->config['sql_explain'])) {
            $res = $this->link->query('explain ' . $this->bulid['sql']);
            if ($res) {
                $this->sqlInfo['explain'] = $res->fetchAll(PDO::FETCH_ASSOC);
            }
        }

        $_beginTime = microtime(true);
        $result     = $this->link->query($this->bulid['sql']);
        $_endTime   = microtime(true);

        $this->sqlInfo['time'] = $_endTime - $_beginTime; // 获取执行时间
        $this->sqlInfo['sql']  = $this->bulid['sql'];

        // 执行成功
        if ($result) {

            // 存入调试信息中
            Trace::addSqlInfo($this->sqlInfo);
            // 存入文件中
            if ($this->config['save_log']) {
                $this->addSqlLog();
            }

            return $result;
        } else {
            // 存入文件
            if ($this->config['error_log']) {
                $this->addErrorSqlLog();
            }

            if (config('debug')) {
                throw new Exception('SQL ERROR : ' . $this->bulid['sql']);
            }

            if (config('trace')) {
                Trace::addErrorInfo('[SQL ERROR] ' . $this->bulid['sql']);
            }

            return false;

        }

    }

    /** 保存错误SQL记录 */
    public function addErrorSqlLog()
    {

        //如果没有写入权限尝试修改权限 如果修改后还是失败 则跳过
        if (isWritable(DATA_PATH)) {
            $path = DATA_SQL_PATH . $this->options['database'] . DS;
            is_dir($path) ? '' : mkdir($path, 0755, true);

            $path .= 'error_' . date('Y_m_d_H', TIME) . '.text';

            $info = '------ ' . $this->sqlInfo['time'];
            $info .= ' | ' . date('Y-m-d H:i:s', TIME);
            $info .= ' | ip:' . getIP();
            $info .= ' | Url:' . URL . '/' . Route::$uri;
            $info .= PHP_EOL;

            $content = $this->sqlInfo['sql'] . ';' . PHP_EOL . '来源：' . $_SERVER['HTTP_USER_AGENT'] . PHP_EOL . '--------------' . PHP_EOL;

            error_log($content . $info, 3, $path);

        }
    }

    /**
     * 保存sql记录
     * @date   2017-10-18T13:45:16+0800
     * @author ChenMingjiang
     * @return [type]                   [description]
     */
    public function addSqlLog()
    {
        // 如果没有写入权限尝试修改权限 如果修改后还是失败 则跳过
        if (!isWritable(DATA_PATH)) {
            return false;
        }

        //创建文件夹
        is_dir(DATA_SQL_PATH) ? '' : mkdir(DATA_SQL_PATH, 0755, true);

        $info = '------ ' . $this->sqlInfo['time'];
        $info .= ' | ' . date('Y-m-d H:i:s', TIME);
        $info .= ' | ip:' . getIP();
        $info .= ' | Url:' . URL . '/' . Route::$uri;
        $info .= PHP_EOL . PHP_EOL;

        // 如果存在sql信息 并且开启日志记录
        if ($this->sqlInfo && $this->config['save_log']) {
            $basePath = DATA_SQL_PATH . $this->options['database'] . DS;
            is_dir($basePath) ? '' : mkdir($basePath, 0755, true);

            $content = $this->sqlInfo['sql'] . PHP_EOL;

            // 记录explain
            if ($this->config['sql_explain'] && isset($this->sqlInfo['explain'])) {
                foreach ($this->sqlInfo['explain'] as $explain) {
                    $content .= json_encode($explain) . PHP_EOL;
                }
            }

            $path = $basePath . (isset($this->options['type']) ? strtolower($this->options['type']) : 'other');
            $path .= '_' . date('Y_m_d_H', TIME) . '.text';

            // 记录慢sql
            if ($this->config['slow_log']) {
                if ($this->sqlInfo['time'] > $this->config['slow_time']) {
                    $path = $basePath . 'slow_' . date('Y_m_d_H', TIME) . '.text';
                }
            }

            error_log($content . $info, 3, $path);
        }
    }

}


<?php
/**
 * CLI OS-based utilities.
 *
 * @author @jaswrks
 * @copyright WebSharks™
 */
declare (strict_types = 1);
namespace WebSharks\Core\Classes\Core\Utils;

use WebSharks\Core\Classes;
use WebSharks\Core\Interfaces;
use WebSharks\Core\Traits;
#
use WebSharks\Core\Classes\Core\Error;
use WebSharks\Core\Classes\Core\Base\Exception;
#
use function assert as debug;
use function get_defined_vars as vars;

/**
 * CLI OS-based utilities.
 *
 * @since 150424 Initial release.
 */
class CliOs extends Classes\Core\Base\Core
{
    /**
     * Class constructor.
     *
     * @since 150424 Initial release.
     *
     * @param Classes\App $App Instance of App.
     */
    public function __construct(Classes\App $App)
    {
        parent::__construct($App);

        if (!$this->c::isCli()) {
            throw $this->c::issue('Requires CLI mode.');
        }
    }

    /**
     * Open a URL from the command line.
     *
     * @since 150424 Initial release.
     *
     * @param string $url The URL to open.
     */
    public function openUrl(string $url)
    {
        if (!($url = $this->c::mbTrim($url))) {
            return; // Not possible.
        }
        $url_arg = escapeshellarg($url);

        if ($this->c::isMac()) {
            `open $url_arg`;
        } elseif ($this->c::isLinux()) {
            `xdg-open $url_arg`;
        } elseif ($this->c::isWindows()) {
            `start $url_arg`;
        } else {
            throw $this->c::issue('Unable to open <'.$url.'>. Unsupported operating system.');
        }
    }
}


<?php

namespace webignition\InternetMediaType\Parser;

use webignition\StringParser\StringParser;

/**
 * Parses out the subtype from an internet media type string
 *
 */
class SubtypeParser extends StringParser
{
    const TYPE_SUBTYPE_SEPARATOR = '/';
    const TYPE_PARAMETER_SEPARATOR = ';';

    const STATE_IN_TYPE = 1;
    const STATE_IN_SUBTYPE = 2;
    const STATE_LEFT_SUBTYPE = 3;
    const STATE_INVALID_INTERNAL_CHARACTER = 4;

    /**
     * Collection of characters not valid in a subtype
     *
     * @var string[]
     */
    private $invalidCharacters = array(
        ' ',
        '"',
        '\\'
    );

    /**
     * @var bool
     */
    private $hasAttemptedToFixAttributeInvalidInternalCharacter = false;

    /**
     * @var Configuration
     */
    private $configuration;

    public function __construct()
    {
        $this->configuration = new Configuration();
    }

    public function setConfiguration(Configuration $configuration)
    {
        $this->configuration = $configuration;
    }

    public function getConfiguration(): Configuration
    {
        return $this->configuration;
    }

    /**
     * @param string $inputString
     *
     * @return string
     *
     * @throws SubtypeParserException
     */
    public function parse($inputString): string
    {
        return parent::parse(trim($inputString));
    }

    /**
     * @return string
     *
     * @throws SubtypeParserException
     */
    protected function parseCurrentCharacter(): ?string
    {
        switch ($this->getCurrentState()) {
            case self::STATE_UNKNOWN:
                $this->setCurrentState(self::STATE_IN_TYPE);
                break;

            case self::STATE_IN_TYPE:
                if ($this->isCurrentCharacterTypeSubtypeSeparator()) {
                    $this->setCurrentState(self::STATE_IN_SUBTYPE);
                }

                $this->incrementCurrentCharacterPointer();

                break;

            case self::STATE_IN_SUBTYPE:
                if ($this->isCurrentCharacterInvalid()) {
                    $this->setCurrentState(self::STATE_INVALID_INTERNAL_CHARACTER);
                } elseif ($this->isCurrentCharacterTypeParameterSeparator()) {
                    $this->setCurrentState(self::STATE_LEFT_SUBTYPE);
                } else {
                    $this->appendOutputString();
                    $this->incrementCurrentCharacterPointer();
                }

                break;

            case self::STATE_LEFT_SUBTYPE:
                $this->stop();
                break;

            case self::STATE_INVALID_INTERNAL_CHARACTER:
                if ($this->shouldAttemptToFixInvalidInternalCharacter()) {
                    $this->hasAttemptedToFixAttributeInvalidInternalCharacter = true;

                    $fixer = new TypeFixer();
                    $fixer->setInputString($this->getInputString());
                    $fixer->setPosition($this->getCurrentCharacterPointer());
                    $fixedType = $fixer->fix();

                    return $this->parse($fixedType);
                }

                throw new SubtypeParserException(
                    'Invalid internal character after at position '.$this->getCurrentCharacterPointer(),
                    SubtypeParserException::INTERNAL_INVALID_CHARACTER_CODE,
                    $this->getCurrentCharacterPointer()
                );
        }

        return null;
    }

    private function shouldAttemptToFixInvalidInternalCharacter(): bool
    {
        return $this->getConfiguration()->attemptToRecoverFromInvalidInternalCharacter()
            && !$this->hasAttemptedToFixAttributeInvalidInternalCharacter;
    }

    private function isCurrentCharacterInvalid(): bool
    {
        return in_array($this->getCurrentCharacter(), $this->invalidCharacters);
    }

    private function isCurrentCharacterTypeSubtypeSeparator(): bool
    {
        return $this->getCurrentCharacter() == self::TYPE_SUBTYPE_SEPARATOR;
    }

    private function isCurrentCharacterTypeParameterSeparator(): bool
    {
        return $this->getCurrentCharacter() == self::TYPE_PARAMETER_SEPARATOR;
    }
}