<?php
/**
 * Change recursive.
 *
 * @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;

/**
 * Change recursive.
 *
 * @since 17xxxx Swap recursive.
 */
class ArrayChangeRecursive extends Classes\Core\Base\Core
{
    /**
     * Change recursive.
     *
     * @since 17xxxx Change recursive.
     *
     * @param array $array    Base array.
     * @param array ...$merge Array to merge.
     *
     * @note The same as `array_replace_recursive()`, except numerically-indexed arrays
     * are emptied out entirely before, and only if, they are going to be replaced by a merge.
     */
    public function __invoke(array $array, array ...$merge): array
    {
        $array        = $this->maybeEmptyNumericArrays($array, ...$merge);
        return $array = array_replace_recursive($array, ...$merge);
    }

    /**
     * Empty numeric arrays.
     *
     * @since 17xxxx Change recursive.
     *
     * @param array $array    Base array.
     * @param array ...$merge Array to merge.
     *
     * @return array w/ possibly-empty numeric arrays.
     */
    protected function maybeEmptyNumericArrays(array $array, array ...$merge): array
    {
        if (!$merge) { // Save time. Merge is empty?
            return $base; // Nothing to do here.
        }
        foreach ($array as $_key => &$_value) {
            if (is_array($_value)) {
                foreach ($merge as $_merge) {
                    if (array_key_exists($_key, $_merge)) {
                        if (!$_value || $_value === array_values($_value)) {
                            $_value = []; // Empty.
                        } elseif (is_array($_merge[$_key])) {
                            $_value = $this->maybeEmptyNumericArrays($_value, $_merge[$_key]);
                        }
                    }
                } // unset($_merge); // Housekeeping.
            }
        } // unset($_key, $_value); // Housekeeping.

        return $array;
    }
}


<?php
namespace Perry\Representation\Eve\v1;

use \Perry\Representation\Reference as Reference;
use \Perry\Representation\Uri as Uri;
use \Perry\Representation\Base as Base;

class Killmail extends Base
{
    public $solarSystem;

    public $killID;

    public $killTime;

    public $moon;

    public $attackers = [];

    public $attackerCount;

    public $victim;

    public $war;

    // by Warringer\Types\Reference
    public function setSolarSystem($solarSystem)
    {
        $this->solarSystem = new Reference($solarSystem);
    }

    // by Warringer\Types\Long
    public function setKillID($killID)
    {
        $this->killID = $killID;
    }

    // by Warringer\Types\String
    public function setKillTime($killTime)
    {
        $this->killTime = $killTime;
    }

    // by Warringer\Types\Reference
    public function setMoon($moon)
    {
        $this->moon = new Reference($moon);
    }

    // by Warringer\Types\ArrayType
    public function setAttackers($attackers)
    {
        // by Warringer\Types\Base
        $converters = [];
        $converters['alliance'] = function ($value) { return new Reference($value); };
        $converters['shipType'] = function ($value) { return new Reference($value); };
        $converters['faction'] = function ($value) { return new Reference($value); };
        $converters['corporation'] = function ($value) { return new Reference($value); };
        $converters['character'] = function ($value) { return new Reference($value); };
        $converters['weaponType'] = function ($value) { return new Reference($value); };
        $converters['finalBlow'] = function ($value) { return $value; };
        $converters['securityStatus'] = function ($value) { return $value; };
        $converters['damageDone'] = function ($value) { return $value; };

        $func = function ($value) use($converters) {
            $return = new \ArrayObject($value, \ArrayObject::ARRAY_AS_PROPS);
            $return['alliance'] = isset($value->{'alliance'}) ? $converters['alliance']($value->{'alliance'}) : null;
            $return['shipType'] = isset($value->{'shipType'}) ? $converters['shipType']($value->{'shipType'}) : null;
            $return['faction'] = isset($value->{'faction'}) ? $converters['faction']($value->{'faction'}) : null;
            $return['corporation'] = isset($value->{'corporation'}) ? $converters['corporation']($value->{'corporation'}) : null;
            $return['character'] = isset($value->{'character'}) ? $converters['character']($value->{'character'}) : null;
            $return['weaponType'] = isset($value->{'weaponType'}) ? $converters['weaponType']($value->{'weaponType'}) : null;
            $return['finalBlow'] = isset($value->{'finalBlow'}) ? $converters['finalBlow']($value->{'finalBlow'}) : null;
            $return['securityStatus'] = isset($value->{'securityStatus'}) ? $converters['securityStatus']($value->{'securityStatus'}) : null;
            $return['damageDone'] = isset($value->{'damageDone'}) ? $converters['damageDone']($value->{'damageDone'}) : null;
            return $return;
        };

        foreach ($attackers as $key => $value) {
            $this->attackers[$key] = $func($value);
        }
    }

    // by Warringer\Types\Long
    public function setAttackerCount($attackerCount)
    {
        $this->attackerCount = $attackerCount;
    }

    // by Warringer\Types\Dict
    public function setVictim($victim)
    {
        // by Warringer\Types\Dict
        $converters = [];
        $converters['alliance'] = function ($value) { return new Reference($value); };
        $converters['faction'] = function ($value) { return new Reference($value); };
        $converters['corporation'] = function ($value) { return new Reference($value); };
        $converters['damageTaken'] = function ($value) { return $value; };
        $converters['character'] = function ($value) { return new Reference($value); };
        $converters['shipType'] = function ($value) { return new Reference($value); };
        $converters['items'] = function ($values) {
        // by Warringer\Types\Base
        $converters = [];
        $converters['singleton'] = function ($value) { return $value; };
        $converters['itemType'] = function ($value) { return new Reference($value); };
        $converters['items'] = function ($values) {
        // by Warringer\Types\Dict
        $converters = [];
        $converters['quantityDropped'] = function ($value) { return $value; };
        $converters['singleton'] = function ($value) { return $value; };
        $converters['quantityDestroyed'] = function ($value) { return $value; };
        $converters['flag'] = function ($value) { return $value; };
        $converters['itemType'] = function ($value) { return new Reference($value); };

        $func = function ($value) use($converters) {
            $return = new \ArrayObject($value, \ArrayObject::ARRAY_AS_PROPS);
            $return['quantityDropped'] = isset($value->{'quantityDropped'}) ? $converters['quantityDropped']($value->{'quantityDropped'}) : null;
            $return['singleton'] = isset($value->{'singleton'}) ? $converters['singleton']($value->{'singleton'}) : null;
            $return['quantityDestroyed'] = isset($value->{'quantityDestroyed'}) ? $converters['quantityDestroyed']($value->{'quantityDestroyed'}) : null;
            $return['flag'] = isset($value->{'flag'}) ? $converters['flag']($value->{'flag'}) : null;
            $return['itemType'] = isset($value->{'itemType'}) ? $converters['itemType']($value->{'itemType'}) : null;
            return $return;
        };

            foreach ($values as $key => $value) {
                 $values[$key] = $func($value);
            }
           return $values;
        };

        $converters['flag'] = function ($value) { return $value; };
        $converters['quantityDropped'] = function ($value) { return $value; };
        $converters['quantityDestroyed'] = function ($value) { return $value; };

        $func = function ($value) use($converters) {
            $return = new \ArrayObject($value, \ArrayObject::ARRAY_AS_PROPS);
            $return['singleton'] = isset($value->{'singleton'}) ? $converters['singleton']($value->{'singleton'}) : null;
            $return['itemType'] = isset($value->{'itemType'}) ? $converters['itemType']($value->{'itemType'}) : null;
            $return['items'] = isset($value->{'items'}) ? $converters['items']($value->{'items'}) : null;
            $return['flag'] = isset($value->{'flag'}) ? $converters['flag']($value->{'flag'}) : null;
            $return['quantityDropped'] = isset($value->{'quantityDropped'}) ? $converters['quantityDropped']($value->{'quantityDropped'}) : null;
            $return['quantityDestroyed'] = isset($value->{'quantityDestroyed'}) ? $converters['quantityDestroyed']($value->{'quantityDestroyed'}) : null;
            return $return;
        };

            foreach ($values as $key => $value) {
                 $values[$key] = $func($value);
            }
           return $values;
        };

        $func = function ($value) use($converters) {
            $return = new \ArrayObject($value, \ArrayObject::ARRAY_AS_PROPS);
            $return['alliance'] = isset($value->{'alliance'}) ? $converters['alliance']($value->{'alliance'}) : null;
            $return['faction'] = isset($value->{'faction'}) ? $converters['faction']($value->{'faction'}) : null;
            $return['corporation'] = isset($value->{'corporation'}) ? $converters['corporation']($value->{'corporation'}) : null;
            $return['damageTaken'] = isset($value->{'damageTaken'}) ? $converters['damageTaken']($value->{'damageTaken'}) : null;
            $return['character'] = isset($value->{'character'}) ? $converters['character']($value->{'character'}) : null;
            $return['shipType'] = isset($value->{'shipType'}) ? $converters['shipType']($value->{'shipType'}) : null;
            $return['items'] = isset($value->{'items'}) ? $converters['items']($value->{'items'}) : null;
            return $return;
        };
        $this->victim = $func($victim);
    }

    // by Warringer\Types\Reference
    public function setWar($war)
    {
        $this->war = new Reference($war);
    }

}


<?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\Helper\Overlays;

use Ivory\GoogleMap\Helper\AbstractHelper;
use Ivory\GoogleMap\Map;
use Ivory\GoogleMap\Overlays\Rectangle;

/**
 * Rectangle helper.
 *
 * @author GeLo <geloen.eric@gmail.com>
 */
class RectangleHelper extends AbstractHelper
{
    /**
     * Renders a rectangle.
     *
     * @param \Ivory\GoogleMap\Overlays\Rectangle $rectangle The rectangle.
     * @param \Ivory\GoogleMap\Map                $map       The map.
     *
     * @return string The JS output.
     */
    public function render(Rectangle $rectangle, Map $map)
    {
        $this->jsonBuilder
            ->reset()
            ->setValue('[map]', $map->getJavascriptVariable(), false)
            ->setValue('[bounds]', $rectangle->getBound()->getJavascriptVariable(), false)
            ->setValues($rectangle->getOptions());

        return sprintf(
            '%s = new google.maps.Rectangle(%s);'.PHP_EOL,
            $rectangle->getJavascriptVariable(),
            $this->jsonBuilder->build()
        );
    }
}


<?php
namespace Perry\Representation\Eve\v1;

use \Perry\Representation\Reference as Reference;
use \Perry\Representation\Uri as Uri;
use \Perry\Representation\Base as Base;

class Tournament extends Base
{
    public $series;

    public $membershipCutoff;

    public $type;

    public $name;

    public $entries = [];

    // by Warringer\Types\Reference
    public function setSeries($series)
    {
        $this->series = new Reference($series);
    }

    // by Warringer\Types\String
    public function setMembershipCutoff($membershipCutoff)
    {
        $this->membershipCutoff = $membershipCutoff;
    }

    // by Warringer\Types\String
    public function setType($type)
    {
        $this->type = $type;
    }

    // by Warringer\Types\String
    public function setName($name)
    {
        $this->name = $name;
    }

    // by Warringer\Types\ArrayType
    public function setEntries($entries)
    {
        // by Warringer\Types\Reference
        $func = function ($value) { return new Reference($value); };

        foreach ($entries as $key => $value) {
            $this->entries[$key] = $func($value);
        }
    }

}


<?php
/**
 * XML conversion 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;

/**
 * XML conversion utils.
 *
 * @since 160829.74007 XML conversion utils.
 */
class Array2Xml extends Classes\Core\Base\Core
{
    /**
     * Array to XML markup.
     *
     * @since 160829.74007 XML conversion utils.
     *
     * @param string $parent_element_name Parent element name.
     * @param array  $array               Input array to convert.
     * @param array  $args                Any additional behavioral args.
     *
     * @return string XML or HTML (with or w/o a DOCTYPE tag).
     *
     * @note `<!DOCTYPE html>` is an HTML DOCTYPE tag.
     * @note `<?xml version="1.0" encoding="utf-8"?>` is an XML DOCTYPE tag.
     */
    public function __invoke(string $parent_element_name, array $array, array $args = []): string
    {
        $default_args = [
            'type'            => 'xml',
            'version'         => '1.0',
            'encoding'        => 'utf-8',
            'include_doctype' => true,
            'format'          => true,
        ];
        $args += $default_args; // Merge w/ defaults.

        $args['type']            = (string) $args['type'];
        $args['version']         = (string) $args['version'];
        $args['encoding']        = (string) $args['encoding'];
        $args['include_doctype'] = (bool) $args['include_doctype'];
        $args['format']          = (bool) $args['format'];

        if ($args['type'] === 'html') {
            $DOMImplementation = new \DOMImplementation();
            $DOMDocumentType   = $DOMImplementation->createDocumentType($args['type']);
            $DOMDocument       = $DOMImplementation->createDocument('', '', $DOMDocumentType);
        } else {
            $DOMDocument = new \DOMDocument($args['version']);
        }
        $DOMDocument->encoding     = $args['encoding'];
        $DOMDocument->formatOutput = $args['format']; // Indentation.
        $save                      = $args['type'] === 'html' ? 'saveHTML' : 'saveXML';

        $ParentDOMElement = $DOMDocument->createElement($parent_element_name);
        $DOMDocument->appendChild($ParentDOMElement);

        $this->convert($DOMDocument, $ParentDOMElement, $array);

        if (!$args['include_doctype']) {
            return (string) $DOMDocument->{$save}($DOMDocument->documentElement);
        } else {
            return (string) $DOMDocument->{$save}(); // With doctype.
        }
    }

    /**
     * Array to HTML markup.
     *
     * @since 160829.74007 XML conversion utils.
     *
     * @param string $parent_element_name Parent element name.
     * @param array  $array               Input array to convert.
     * @param array  $args                Any additional behavioral args.
     *
     * @return string HTML (with or w/o a DOCTYPE tag).
     */
    public function toHtml(string $parent_element_name, array $array, array $args = []): string
    {
        return $this->__invoke($parent_element_name, $array, array_merge($args, ['type' => 'html']));
    }

    /**
     * Array to XML handler.
     *
     * @since 160829.74007 XML conversion utils.
     *
     * @param \DOMDocument $DOMDocument      Class instance.
     * @param \DOMElement  $ParentDOMElement Class instance.
     * @param array        $array            Input array to convert.
     */
    protected function convert(\DOMDocument $DOMDocument, \DOMElement $ParentDOMElement, array $array)
    {
        foreach ($array as $_child_key => $_child_value) {
            if (is_string($_child_key)) { // Allows duplicate string keys.
                $_child_key = preg_replace('/^[0-9]+_/u', '', $_child_key);
            }
            if (is_array($_child_value)) { // Nested tag.
                $_ChildDOMElement = $DOMDocument->createElement($_child_key);
                $ParentDOMElement->appendChild($_ChildDOMElement);
                $this->convert($DOMDocument, $_ChildDOMElement, $_child_value);
                //
            } elseif (is_scalar($_child_value)) { // Must be scalar.
                // String keys become attributes; numeric keys text nodes.
                $_child_value = (string) $_child_value; // Force string.

                if (is_string($_child_key)) {
                    $_ChildDOMAttr = $DOMDocument->createAttribute($_child_key);
                    $ParentDOMElement->appendChild($_ChildDOMAttr);
                    $_ChildDOMAttr->value = $this->c::escAttr($_child_value);
                } else {
                    $_ChildDOMText = $DOMDocument->createTextNode($_child_value);
                    $ParentDOMElement->appendChild($_ChildDOMText);
                }
            } else { // Fail on unexpected values.
                throw $this->c::issue('Unexpected non-scalar child value.');
            }
        } // unset($_child_key, $_child_value, $_ChildDOMElement, $_ChildDOMAttr, $_ChildDOMText);
    }
}


<?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\Helper\Overlays\MarkerCluster;

use Ivory\GoogleMap\Events\Event;
use Ivory\GoogleMap\Helper\Overlays\InfoWindowHelper;
use Ivory\GoogleMap\Helper\Overlays\MarkerHelper;
use Ivory\GoogleMap\Map;
use Ivory\GoogleMap\Overlays\Marker;
use Ivory\GoogleMap\Overlays\MarkerCluster;

/**
 * Default marker cluster helper.
 *
 * @author GeLo <geloen.eric@gmail.com>
 */
class DefaultMarkerClusterHelper extends AbstractMarkerClusterHelper
{
    /** @var \Ivory\GoogleMap\Helper\Overlays\InfoWindowHelper */
    protected $infoWindowHelper;

    /**
     * Creates a default marker cluster helper.
     *
     * @param \Ivory\GoogleMap\Helper\Overlays\MarkerCluster\MarkerHelper $markerHelper     The marker helper.
     * @param \Ivory\GoogleMap\Helper\Overlays\InfoWindowHelper           $infoWindowHelper The info window helper.
     */
    public function __construct(MarkerHelper $markerHelper = null, InfoWindowHelper $infoWindowHelper = null)
    {
        parent::__construct($markerHelper);

        if ($infoWindowHelper === null) {
            $infoWindowHelper = new InfoWindowHelper();
        }

        $this->setInfoWindowHelper($infoWindowHelper);
    }

    /**
     * Gets the info window helper.
     *
     * @return \Ivory\GoogleMap\Helper\Overlays\InfoWindowHelper The info window helper.
     */
    public function getInfoWindowHelper()
    {
        return $this->infoWindowHelper;
    }

    /**
     * Sets the info window helper.
     *
     * @param \Ivory\GoogleMap\Helper\Overlays\InfoWindowHelper $infoWindowHelper The info window helper.
     */
    public function setInfoWindowHelper(InfoWindowHelper $infoWindowHelper)
    {
        $this->infoWindowHelper = $infoWindowHelper;
    }

    /**
     * {@inheritdoc}
     */
    public function render(MarkerCluster $markerCluster, Map $map)
    {

    }

    /**
     * {@inheritdoc}
     */
    public function renderLibraries(MarkerCluster $markerCluster, Map $map)
    {

    }

    /**
     * {@inheritdoc}
     */
    public function renderMarkers(MarkerCluster $markerCluster, Map $map)
    {
        $output = array();

        foreach ($markerCluster->getMarkers() as $marker) {
            $output[] = $this->renderMarker($marker, $map);

            if ($marker->hasInfoWindow() && $marker->getInfoWindow()->isAutoOpen()) {
                $this->registerInfoWindowEvent($marker, $map);
            }
        }

        return implode('', $output);
    }

    /**
     * Renders a marker with the js map container.
     *
     * @param \Ivory\GoogleMap\Overlays\Marker $marker The marker.
     * @param \Ivory\GoogleMap\Map             $map    The map.
     *
     * @return string The JS output.
     */
    protected function renderMarker(Marker $marker, Map $map)
    {
        return sprintf(
            '%s.markers.%s = %s',
            $this->getJsContainerName($map),
            $marker->getJavascriptVariable(),
            $this->markerHelper->render($marker, $map)
        );
    }

    /**
     * Registers the info window event (auto open).
     *
     * @param \Ivory\GoogleMap\Overlays\Marker $marker The marker.
     * @param \Ivory\GoogleMap\Map             $map    The map.
     */
    protected function registerInfoWindowEvent(Marker $marker, Map $map)
    {
        $closableInfoWindows = sprintf('%s.closable_info_windows', $this->getJsContainerName($map));

        $handle = <<<EOF
function () {
    for (var info_window in {$closableInfoWindows}) {
        {$closableInfoWindows}[info_window].close();
    }
    {$this->infoWindowHelper->renderOpen($marker->getInfoWindow(), $map, $marker)}
}
EOF;

        $event = new Event();
        $event->setJavascriptVariable(sprintf('%s_%s', $marker->getJavascriptVariable(), 'info_window_event'));
        $event->setInstance($marker->getJavascriptVariable());
        $event->setEventName($marker->getInfoWindow()->getOpenEvent());
        $event->setHandle($handle);

        $map->getEventManager()->addEvent($event);
    }
}


<?php
/**
 * Key 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;

/**
 * Key generator.
 *
 * @since 170824.30708 Key generator.
 */
class Keygen extends Classes\Core\Base\Core
{
    /**
     * License key.
     *
     * @since 170824.30708 Key generator.
     *
     * @return string UUID w/ 4 dashed parts (35 uppercase bytes).
     *
     * @example `CAF0F02F-DFC248C7-B4EF607C-F85C154A`
     */
    public function license(): string
    {
        $key        = mb_strtoupper($this->c::uuidV4());
        return $key = implode('-', str_split($key, 8));
    }

    /**
     * Public API key.
     *
     * @since 170824.30708 Key generator.
     *
     * @return string UUID w/ `pub_` prefix (64 lowercase bytes).
     *
     * @example `pub_104abe60992841aa920c764d5a37c33e20467398bb3a43288a1e554c607f`
     */
    public function publicApi(): string
    {
        return 'pub_'.mb_substr($this->c::uuidV4x2(), 0, -4);
    }

    /**
     * Secret API key.
     *
     * @since 170824.30708 Key generator.
     *
     * @return string UUID w/ `sec_` prefix (64 lowercase bytes).
     *
     * @example `sec_8f7d4f577838478498f0ca193f87980c529f20c9526c47ecbb5f3dcdcf48`
     */
    public function secretApi(): string
    {
        return 'sec_'.mb_substr($this->c::uuidV4x2(), 0, -4);
    }

    /**
     * Secret sig key.
     *
     * @since 170824.30708 Key generator.
     *
     * @return string UUID w/ `sig_` prefix (64 lowercase bytes).
     *
     * @example `sig_2759592dc1fc4574938e5f78e74c90abb5caad9a17d942e7857747af4b0a`
     */
    public function secretSig(): string
    {
        return 'sig_'.mb_substr($this->c::uuidV4x2(), 0, -4);
    }
}


<?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\Helper\Extension;

use Ivory\GoogleMap\Helper\ApiHelper;
use Ivory\GoogleMap\Helper\Overlays\MarkerCluster\MarkerClusterHelper;
use Ivory\GoogleMap\Map;

/**
 * Core extension helper.
 *
 * @author GeLo <geloen.eric@gmail.com>
 */
class CoreExtensionHelper implements ExtensionHelperInterface
{
    /** @var \Ivory\GoogleMap\Helper\ApiHelper */
    protected $apiHelper;

    /** @var \Ivory\GoogleMap\Helper\Overlays\MarkerCluster\MarkerClusterHelper */
    protected $markerClusterHelper;

    /**
     * Creates a core extension helper.
     *
     * @param \Ivory\GoogleMap\Helper\ApiHelper                                  $apiHelper           The api helper.
     * @param \Ivory\GoogleMap\Helper\Overlays\MarkerCluster\MarkerClusterHelper $markerClusterHelper The marker cluster helper.
     */
    public function __construct(ApiHelper $apiHelper = null, MarkerClusterHelper $markerClusterHelper = null)
    {
        if ($apiHelper === null) {
            $apiHelper = new ApiHelper();
        }

        if ($markerClusterHelper === null) {
            $markerClusterHelper = new MarkerClusterHelper();
        }

        $this->setApiHelper($apiHelper);
        $this->setMarkerClusterHelper($markerClusterHelper);
    }

    /**
     * Gets the api helper.
     *
     * @return \Ivory\GoogleMap\Helper\ApiHelper The api helper.
     */
    public function getApiHelper()
    {
        return $this->apiHelper;
    }

    /**
     * Sets the api helper.
     *
     * @param \Ivory\GoogleMap\Helper\ApiHelper $apiHelper The api helper.
     */
    public function setApiHelper(ApiHelper $apiHelper)
    {
        $this->apiHelper = $apiHelper;
    }

    /**
     * Gets the marker cluster helper.
     *
     * @return \Ivory\GoogleMap\Helper\Overlays\MarkerCluster\MarkerClusterHelper The marker cluster helper.
     */
    public function getMarkerClusterHelper()
    {
        return $this->markerClusterHelper;
    }

    /**
     * Sets the marker cluster helper.
     *
     * @param \Ivory\GoogleMap\Helper\Overlays\MarkerCluster\MarkerClusterHelper $markerClusterHelper The marker cluster helper.
     */
    public function setMarkerClusterHelper(MarkerClusterHelper $markerClusterHelper)
    {
        $this->markerClusterHelper = $markerClusterHelper;
    }

    /**
     * {@inheritdoc}
     */
    public function renderLibraries(Map $map)
    {
        if ($this->apiHelper->isLoaded()) {
            return;
        }

        $callback = null;

        if ($map->isAsync()) {
            $callback = 'load_ivory_google_map';
        }

        $output = array();

        $output[] = $this->apiHelper->render($map->getLanguage(), $this->getLibraries($map), $callback, $map->getApiKey());
        $output[] = $this->markerClusterHelper->renderLibraries($map->getMarkerCluster(), $map);

        return implode('', $output);
    }

    /**
     * {@inheritdoc}
     */
    public function renderBefore(Map $map)
    {
        if ($map->isAsync()) {
            return 'function load_ivory_google_map() {'.PHP_EOL;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function renderAfter(Map $map)
    {
        if ($map->isAsync()) {
            return '}'.PHP_EOL;
        }
    }

    /**
     * Gets the libraries needed for the map.
     *
     * @param \Ivory\GoogleMap\Map $map The map.
     *
     * @return array The map libraries.
     */
    protected function getLibraries(Map $map)
    {
        $libraries = $map->getLibraries();

        $encodedPolylines = $map->getEncodedPolylines();
        if (!empty($encodedPolylines)) {
            $libraries[] = 'geometry';
        }

        return array_unique($libraries);
    }
}


<?php
/**
 * SQL 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;

/**
 * SQL utilities.
 *
 * @since 160422 SQL utilities.
 */
class Sql extends Classes\Core\Base\Core
{
    /**
     * Exists?
     *
     * @since 17xxxx
     *
     * @type bool
     */
    protected $pdo_exists;

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

        $this->pdo_exists = class_exists('PDO');
    }

    /**
     * Quotes/escapes SQL value.
     *
     * @since 160422 SQL utils.
     *
     * @param int|float|string $value Value.
     *
     * @return string Quoted/escaped value (as a string).
     */
    public function quote($value): string
    {
        if ($this->pdo_exists && $this->c::currentPdo()) {
            return $this->c::pdoQuote($value);
        }
        switch (gettype($value)) {
            case 'int':
            case 'integer':
                return (string) $value;

            case 'float':
            case 'double':
                return (string) $value;

            case 'str':
            case 'string':
                if ($this->c::isWordPress()) {
                    return "'".esc_sql($value)."'";
                }
            // Else fall through to exception.
            // no break
            default: // Unexpected data type.
                throw $this->c::issue('No SQL escape handler.');
        }
    }

    /**
     * Quotes/escapes IN values.
     *
     * @since 160422 SQL utils.
     *
     * @param array $values Values.
     *
     * @return string Quoted/escaped IN values.
     */
    public function quoteIn(array $values): string
    {
        return implode(',', array_map([$this, 'quote'], $values));
    }

    /**
     * Quotes/escapes table.column.
     *
     * @since 160422 SQL utils.
     *
     * @param string $column Column.
     * @param string $table  Table name.
     *
     * @return string Quotes/escapes table.column.
     */
    public function quoteColumn(string $column, string $table = ''): string
    {
        $quoted = ''; // Initialize.

        if ($table) { // Table prefix?
            $quoted .= '`'.$this->escapeName($table).'`.';
        } // If there's a table add ``. prefix.

        return $quoted .= '`'.$this->escapeName($column).'`';
    }

    /**
     * Quotes/escapes table.columns.
     *
     * @since 160422 SQL utils.
     *
     * @param array  $columns Columns.
     * @param string $table   Table name.
     *
     * @return string Quoted/escaped table.columns.
     */
    public function quoteColumns(array $columns, string $table = ''): string
    {
        $quoted = ''; // Initialize.

        foreach ($columns as $_column) {
            if ($_column && is_string($_column)) {
                $quoted .= ','.$this->quoteColumn($_column, $table);
            }
        } // unset($_column); // Housekeeping.

        return $this->c::mbTrim($quoted, ' ,');
    }

    /**
     * Quotes/escapes values.
     *
     * @since 17xxxx SQL utils.
     *
     * @param array $values Values.
     *
     * @return string Quoted/escaped values.
     */
    public function quoteValues(array $values): string
    {
        return implode(',', array_map([$this, 'quote'], $values));
    }

    /**
     * Quotes/escapes (table.column) (sets).
     *
     * @since 17xxxx SQL utils.
     *
     * @param array  $sets  Sets.
     * @param string $table Table name.
     *
     * @return string Quoted/escaped (table.column) (sets).
     */
    public function quoteCvSets(array $sets, string $table = ''): string
    {
        $quoted = ''; // Initialize.

        foreach ($sets as $_set) {
            if (!is_array($_set)) {
                continue; // Not possible.
            }
            if (!$quoted) { // Establish columns.
                $quoted .= '('.$this->quoteColumns(array_keys($_set), $table).')';
                $quoted .= 'VALUES('.$this->quoteValues($_set).')';
            } else {
                $quoted .= ',('.$this->quoteValues($_set).')';
            }
        } // unset($_set); // Housekeeping.

        return $this->c::mbTrim($quoted, ' ,');
    }

    /**
     * Quotes/escapes table.column=value pairs.
     *
     * @since 17xxxx SQL utils.
     *
     * @param array  $pairs Pairs.
     * @param string $table Table name.
     *
     * @return string Quoted/escaped table.column=value pairs.
     */
    public function quoteCvPairs(array $pairs, string $table = ''): string
    {
        $quoted = ''; // Initialize.

        foreach ($pairs as $_column => $_value) {
            if ($_column && is_string($_column)) {
                $quoted .= ','.$this->quoteColumn($_column, $table);
                $quoted .= '='.$this->quote($_value);
            }
        } // unset($_key, $_column); // Housekeeping.

        return $this->c::mbTrim($quoted, ' ,');
    }

    /**
     * Escapes an SQL name.
     *
     * @since 160422 SQL utils.
     *
     * @param string $name SQL name.
     *
     * @return string Escaped SQL name.
     */
    public function escapeName(string $name): string
    {
        return preg_replace('/[^a-z0-9_]+/ui', '', $name);
    }

    /**
     * Escapes order direction.
     *
     * @since 160422 SQL utils.
     *
     * @param string $order Order direction.
     *
     * @return string Escaped order direction.
     */
    public function escapeOrder(string $order): string
    {
        $string = mb_strtoupper($order);
        return in_array($order, ['ASC', 'DESC'], true) ? $order : 'ASC';
    }

    /**
     * Quotes/escapes order-bys.
     *
     * @since 160422 SQL utils.
     *
     * @param array  $order_bys Order-bys.
     * @param string $table     Table name.
     *
     * @return string Quoted/escaped order-bys.
     */
    public function quoteOrderBys(array $order_bys, string $table = ''): string
    {
        $quoted = ''; // Initialize.

        foreach ($order_bys as $_order_by => $_order) {
            if ($_order_by && is_string($_order_by)) {
                $quoted .= ','; // Always.

                if ($table) { // Table prefix?
                    $quoted .= '`'.$this->escapeName($table).'`.';
                } // If table name, add a ``. prefix.

                $quoted .= '`'.$this->escapeName($_order_by).'` '.$this->escapeOrder($_order);
            }
        } // unset($_order_by, $_order);

        return $this->c::mbTrim($quoted, ', ');
    }
}


<?php
/**
 * Serializer.
 *
 * @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 SuperClosure\Serializer as ClosureSerializer;
use SuperClosure\Analyzer\AstAnalyzer as ClosureAstAnalyzer;
use SuperClosure\Analyzer\TokenAnalyzer as ClosureTokenAnalyzer;

/**
 * Serializer.
 *
 * @since 150424 Serializer.
 */
class Serializer extends Classes\Core\Base\Core
{
    /**
     * Closure serializer.
     *
     * @since 160712
     *
     * @type ClosureSerializer
     */
    protected $ClosureAstSerializer;

    /**
     * Closure serializer (faster).
     *
     * @since 160712
     *
     * @type ClosureSerializer
     */
    protected $ClosureTokenSerializer;

    /**
     * Marker (for closures).
     *
     * @since 160712 Serializer.
     *
     * @type string `⌗🆂🅲⫶` Four UTF-8 chars.
     */
    const CLOSURE = "\u{2317}\u{1F182}\u{1F172}\u{2AF6}";

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

        $this->ClosureAstSerializer   = new ClosureSerializer(new ClosureAstAnalyzer());
        $this->ClosureTokenSerializer = new ClosureSerializer(new ClosureTokenAnalyzer());
        // See: <https://github.com/jeremeamia/super_closure>
    }

    /**
     * Serialize value.
     *
     * @since 160712 Serializer.
     *
     * @param mixed $value Value to serialize.
     *
     * @return string A string (serialized).
     */
    public function __invoke($value): string
    {
        if ($value instanceof \Closure) {
            $string = $this::CLOSURE;
            return $string .= $this->serializeClosure($value);
        } elseif (!is_resource($value)) {
            return $string = serialize($value);
        } else { // Cannot serialize.
            throw $this->c::issue('Cannot serialize.');
        }
    }

    /**
     * Serialize closure.
     *
     * @since 160712 Closure utils.
     *
     * @param \Closure $Closure A closure.
     * @param bool     $faster  Use faster serializer?
     *
     * @return string Serialized closure.
     */
    public function serializeClosure(\Closure $Closure, bool $faster = false): string
    {
        // NOTE: No marker; i.e., so this can be called stand-alone if necessary.
        return $this->{$faster ? 'ClosureTokenSerializer' : 'ClosureAstSerializer'}->serialize($Closure);
    }

    /**
     * Unserialize closure.
     *
     * @since 160712 Closure utils.
     *
     * @param string $string A serialized closure.
     * @param bool   $faster Used faster serializer?
     *
     * @return \Closure Unserialized closure.
     */
    public function unserializeClosure(string $string, bool $faster = false): \Closure
    {
        if (mb_strpos($string, $this::CLOSURE) === 0) {
            $string = mb_substr($string, mb_strlen($this::CLOSURE));
        }
        return $this->{$faster ? 'ClosureTokenSerializer' : 'ClosureAstSerializer'}->unserialize($string);
    }

    /**
     * Maybe serialize value.
     *
     * @since 150424 Serializer.
     * @since 170824.30708 `$strict` param (= true).
     *
     * @param mixed $value  Value to serialize.
     * @param bool  $strict Enable strict types?
     *
     * @return string A string (possibly serialized).
     */
    public function maybeSerialize($value, bool $strict = true): string
    {
        if (!$strict) {
            if (is_string($value)) {
                return $string = $value;
            } elseif (is_bool($value)) {
                return $string = (string) (int) $value;
            } elseif (is_int($value) || is_float($value)) {
                return $string = (string) $value;
            } else { // Serialize.
                return $string = $this->__invoke($value);
            }
        } else { // Default.
            if (is_string($value)) {
                return $string = $value;
            } else { // Serialize.
                return $string = $this->__invoke($value);
            }
        }
    }

    /**
     * Maybe unserialize value.
     *
     * @since 150424 Serializer.
     *
     * @param mixed $value Value.
     *
     * @return mixed The unserialized value.
     */
    public function maybeUnserialize($value)
    {
        if ($value && $this->isSerialized($value)) {
            if (mb_strpos($value, $this::CLOSURE) === 0) {
                return $this->unserializeClosure($value);
            } else {
                return unserialize($value);
            }
        } else {
            return $value; // Not applicable.
        }
    }

    /**
     * Is serialized?
     *
     * @since 160712 Serializer.
     *
     * @param mixed $value Value.
     *
     * @return bool True if serialized.
     */
    public function isSerialized($value): bool
    {
        if (!$value) {
            return false;
        } elseif (!is_string($value)) {
            return false;
        }
        $string = $value; // It is a string.

        if ('N;' === $string) {
            return true; // `null`.
        } elseif ('b:0;' === $string) {
            return true; // (bool)`false`.
        } elseif ('b:1;' === $string) {
            return true; // (bool)`true`.
        }
        if (mb_strpos($string, $this::CLOSURE) === 0) {
            $string = mb_substr($string, mb_strlen($this::CLOSURE));
        }
        if (!isset($string[3])) {
            return false;
        } elseif ($string[1] !== ':') {
            return false;
        } elseif (!in_array($string[0], [/* 'N', 'b', */ 'i', 'd', 's', 'a', 'O', 'C'], true)) {
            return false; // NOTE: Null and bool already tested above.
        } elseif (!in_array(mb_substr($string, -1), [';', '}'], true)) {
            return false;
        }
        switch ($string[0]) {
            case 'N': // Null.
            case 'b': // Boolean.
                return false; // For clarity.
                // NOTE: Null and bool already tested above.

            case 'i': // Integer.
                return (bool) preg_match('/^i\:\-?[0-9]+;$/u', $string);

            case 'd': // Float (decimal).
                return (bool) preg_match('/^d\:\-?[0-9.Ee+\-]+;$/u', $string);

            case 's': // String.
                return (bool) preg_match('/^s\:[0-9]+\:".*?";$/us', $string);

            case 'a': // An array.
                return (bool) preg_match('/^a\:[0-9]+\:\{.*?\}$/us', $string);

            case 'O': // An object.
            case 'C': // A serializable class.
                return (bool) preg_match('/^[OC]\:[0-9]+\:".*?"\:[0-9]+\:\{.*?\}$/us', $string);

            default: // Default case handler.
                return false; // Nope.
        }
    }

    /**
     * Check/set expected type.
     *
     * @since 150424 Serializer.
     *
     * @param mixed  $value         Value.
     * @param string $expected_type Data type.
     *
     * @return mixed|null The typified value; else `null`.
     */
    public function checkSetType($value, string $expected_type)
    {
        switch ($expected_type) {
            case 'bool':
            case 'boolean':
                $expected_type = 'bool';
                if ($value === '0' || $value === '1') {
                    $value = (bool) $value;
                }
                break; // Stop here.

            case 'int':
            case 'integer':
            case 'long':
                $expected_type = 'int';
                if (is_numeric($value) && mb_strpos($value, '.') === false) {
                    $value = (int) $value;
                }
                break; // Stop here.

            case 'float':
            case 'double':
            case 'real':
                $expected_type = 'float';
                if (is_numeric($value)) {
                    $value = (float) $value;
                }
                break; // Stop here.

            case 'string':
            case 'array':
            case 'object':
            case 'null':
                break; // Stop here.

            default: // Catch invalid type checks here.
                throw $this->c::issue(sprintf('Unexpected type: `%1$s`.', $expected_type));
        }
        $is = 'is_'.$expected_type; // See: <http://php.net/manual/en/function.gettype.php>

        return $is($value) ? $value : null;
    }
}


<?php
/**
 * OEmbed 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 Embedly\Embedly;

/**
 * OEmbed utilities.
 *
 * @since 151125 OEmbed utilities.
 */
class OEmbed extends Classes\Core\Base\Core implements Interfaces\UrlConstants
{
    /**
     * Cache directory.
     *
     * @since 150424
     *
     * @var string
     */
    protected $cache_dir;

    /**
     * Class constructor.
     *
     * @since 151125 OEmbed utilities.
     *
     * @param Classes\App $App Instance of App.
     */
    public function __construct(Classes\App $App)
    {
        parent::__construct($App);

        if (!$this->App->Config->©fs_paths['©cache_dir']) {
            throw $this->c::issue('Missing cache directory.');
        }
        $this->cache_dir = $this->App->Config->©fs_paths['©cache_dir'].'/oembed';
    }

    /**
     * OEmbed parser.
     *
     * @since 151125 OEmbed utilities.
     *
     * @param mixed $value Any input value.
     * @param array $args  Any additional behavioral args.
     *
     * @return string|array|object Html markup value(s).
     */
    public function __invoke($value, array $args = [])
    {
        if (is_array($value) || is_object($value)) {
            foreach ($value as $_key => &$_value) {
                $_value = $this->__invoke($_value, $args);
            } //unset($_key, $_value);
            return $value;
        }
        if (!($string = (string) $value)) {
            return $string; // Nothing to do.
        }
        if (mb_strpos($string, '://') === false) {
            return $string; // Nothing to do.
        }
        $default_args = ['via' => ''];
        $args         = array_merge($default_args, $args);
        $args         = array_intersect_key($args, $default_args);

        $via = (string) $args['via']; // Force string.

        $Tokenizer = $this->c::tokenize(
            $string,
            [
                'shortcodes',
                'pre',
                'code',
                'samp',
                'anchors',
                'md-fences',
                'md-links',
            ]
        ); // Now get string by reference.
        $string = &$Tokenizer->getString();

        switch ($via) {
            case 'embedly':
                $string = $this->viaEmbedly($string);
                break; // All done here.

            case 'wordpress':
                $string = $this->viaWordPress($string);
                break; // All done here.

            default: // Defaults.
                if ($this->c::isWordPress()) {
                    $string = $this->viaWordPress($string);
                } else { // Default outside WP.
                    $string = $this->viaEmbedly($string);
                } // See {@link viaEmbedly()} routines below.
        }
        return $string = $Tokenizer->restoreGetString();
    }

    /**
     * Via Embedly.
     *
     * @since 151125 OEmbed utilities.
     *
     * @param string $string String to parse.
     */
    protected function viaEmbedly(string $string): string
    {
        # Require a valid API key for Embedly.

        if (!$this->App->Config->©embedly['©api_key']) {
            throw $this->c::issue('Missing Embedly API key.');
        }
        # Initialize several variables.

        $tokens  = $uncached_urls  = $new_embeds  = [];
        $marker  = str_replace('.', '', uniqid('', true));
        $Embedly = new Embedly(['key' => $this->App->Config->©embedly['©api_key']]);

        # Tokenize all URLs on a line of their own.

        $string = preg_replace_callback($this::URL_REGEX_VALID.'m', function ($m) use (&$tokens, &$uncached_urls, $marker) {
            $token = '%#%oembed-token-'.$marker.'-'.(count($tokens) - 1).'%#%';
            $tokens[$token] = $uncached_urls[$m[0]] = $m[0];
            return $token; // Replaced w/ unique token.
        }, $string); // Tokenizes all possible embed URLs.

        # Replace and flag URLs already cached.

        foreach ($tokens as $_token => $_url) {
            if (!($_embed = $this->getEmbedlyCache($_url))) {
                continue; // Not cached yet.
            }
            unset($uncached_urls[$_url]); // Cached already.

            if ($_embed->type === 'error') {
                continue; // Not possible.
            } elseif (!empty($_embed->error_code)) {
                continue; // Not possible.
            }
            if (($_markup = $this->embedlyMarkup($_url, $_embed))) {
                $string = str_replace($_token, $_markup, $string);
            }
        } // unset($_token, $_url, $_embed, $_markup); // Housekeeping.

        # Retrieve embed objects for any uncached URLs now.

        for ($_i = 1; $_i <= count($uncached_urls); $_i = $_i + 10) {
            $_urls = array_slice($uncached_urls, $_i - 1, 10);
            $_urls = array_values($_urls);

            $_args = [ // Batches of 10.
                'urls'     => $_urls,
                'secure'   => true,
                'width'    => 800,
                'maxwidth' => 800,
                'wmode'    => 'transparent',
            ]; // See: <http://embed.ly/docs/api/embed/arguments>

            foreach ($Embedly->oembed($_args) as $_key => $_embed) {
                $new_embeds[$_urls[$_key]] = $_embed;
            } // unset($_key, $_embed); // Housekeeping.
        } // unset($_i, $_urls, $_args); // Housekeeping.

        # Cache each of the new embeds now, and replace any
        # tokens associated w/ new embeds obtained above.

        foreach ($new_embeds as $_url => $_embed) {
            $this->setEmbedlyCache($_url, $_embed);

            if ($_embed->type === 'error') {
                continue; // Not possible.
            } elseif (!empty($_embed->error_code)) {
                continue; // Not possible.
            }
            if (($_markup = $this->embedlyMarkup($_url, $_embed))) {
                foreach (array_keys($tokens, $_url, true) as $_token) {
                    $string = str_replace($_token, $_markup, $string);
                } // unset($_token); // Housekeeping.
            }
        } // unset($_url, $_embed, $_markup); // Housekeeping.

        # Restore URLs that didn't trigger embeds & return.

        foreach (array_reverse($tokens, true) as $_token => $_url) {
            $string = str_replace($_token, $_url, $string);
        } // unset($_token, $_url); // Housekeeping.

        return $string;
    }

    /**
     * Get Embedly markup.
     *
     * @since 151125 OEmbed utilities.
     *
     * @param string    $url   URL the embed is for.
     * @param \StdClass $embed The embed response object.
     *
     * @return string The Embedly HTML markup.
     */
    protected function embedlyMarkup(string $url, \StdClass $embed): string
    {
        $markup = ''; // Initialize HTML markup.

        if (!$url || empty($embed->type) || empty($embed->provider_name)) {
            return $markup; // Not possible; i.e., empty string.
        }
        $provider_slug = $this->c::nameToSlug($embed->provider_name);
        $classes       = '_-embedly _-'.$embed->type.' _-'.$provider_slug;

        switch ($embed->type) { // See: <http://jas.xyz/1O7ymwT>
            case 'rich':
            case 'video':
                if (!empty($embed->html)) {
                    $markup = '
c::escAttr($classes).'">'.
                        $embed->html.
                    '
';
                }
                break;

            case 'photo':
                if (!empty($embed->url) && isset($embed->width, $embed->height)) {
                    $markup = '
c::escAttr($classes).'">'.
                        'c::escUrl($url).'" title="'.$this->c::escAttr($embed->title ?? '').'">'.
                            'c::escUrl($embed->url).'" width="'.$this->c::escAttr((string) $embed->width).'" height="'.$this->c::escAttr((string) $embed->height).'" alt="" />'.
                        ''.
                    '
';
                }
                break;

            case 'link':
                if (!empty($embed->thumbnail_url) && isset($embed->thumbnail_width, $embed->thumbnail_height)) {
                    $markup = '
c::escAttr($classes).'">'.
                        'c::escUrl($url).'" title="'.$this->c::escAttr($embed->title ?? '').'">'.
                            'c::escUrl($embed->thumbnail_url).'" width="'.$this->c::escAttr((string) $embed->thumbnail_width).'" height="'.$this->c::escAttr((string) $embed->thumbnail_height).'" alt="" />'.
                        ''.
                    '
';
                }
                break;
        }
        return $markup;
    }

    /**
     * Get Embedly cache.
     *
     * @since 151125 OEmbed utilities.
     *
     * @param string $url URL to check the cache for.
     *
     * @return \StdClass|null Cached embed code, if possible.
     */
    protected function getEmbedlyCache(string $url)
    {
        $cache_dir = $this->cache_dir.'/embedly';
        $cache_dir .= '/'.$this->c::sha1ModShardId($url);
        $cache_file = $cache_dir.'/'.sha1($url);

        if (is_file($cache_file)) {
            $embed = file_get_contents($cache_file);
            $embed = unserialize($embed);
            if ($embed instanceof \StdClass) {
                return $embed;
            }
        }
    }

    /**
     * Set Embedly cache.
     *
     * @since 151125 OEmbed utilities.
     *
     * @param string    $url   URL the embed is for.
     * @param \StdClass $embed The embed response object.
     */
    protected function setEmbedlyCache(string $url, \StdClass $embed)
    {
        $url_sha1              = sha1($url);
        $cache_dir             = $this->cache_dir.'/embedly/'.$this->c::sha1ModShardId($url_sha1, true);
        $cache_dir_permissions = $this->App->Config->©fs_permissions['©transient_dirs'];
        $cache_file            = $cache_dir.'/'.$url_sha1;

        if (!is_dir($cache_dir)) {
            mkdir($cache_dir, $cache_dir_permissions, true);
        }
        file_put_contents($cache_file, serialize($embed));
    }

    /**
     * Via WordPress.
     *
     * @since 151125 OEmbed utilities.
     *
     * @param string $string String to parse.
     */
    protected function viaWordPress(string $string): string
    {
        if (!$this->c::isWordPress()
            || !$this->c::canCallFunc('wp_oembed_get')
            || !$this->c::canCallFunc('wp_embed_defaults')) {
            throw $this->c::issue('Unable to oEmbed via WordPress.');
        }
        $oembed_args = array_merge(wp_embed_defaults(), ['discover' => false]);
        $string      = preg_replace_callback('/^\s*(https?:\/\/[^\s"]+)\s*$/uim', function ($m) use ($oembed_args) {
            $oembed = wp_oembed_get($m[1], $oembed_args);
            return $oembed ? $oembed : $m[0];
        }, $string);

        return $string;
    }
}


<?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\Helper\Overlays;

use Ivory\GoogleMap\Helper\AbstractHelper;
use Ivory\GoogleMap\Map;
use Ivory\GoogleMap\Overlays\EncodedPolyline;
use Ivory\GoogleMap\Helper\Geometry\EncodingHelper;

/**
 * Encoded polyline helper.
 *
 * @author GeLo <geloen.eric@gmail.com>
 */
class EncodedPolylineHelper extends AbstractHelper
{
    /** @var \Ivory\GoogleMap\Helper\Geometry\EncodingHelper */
    protected $encodingHelper;

    /**
     * Creates an encoded polyline helper.
     *
     * @param \Ivory\GoogleMap\Helper\Geometry\EncodingHelper $encodingHelper The encoding helper.
     */
    public function __construct(EncodingHelper $encodingHelper = null)
    {
        parent::__construct();

        if ($encodingHelper === null) {
            $encodingHelper = new EncodingHelper();
        }

        $this->setEncodingHelper($encodingHelper);
    }

    /**
     * Gets the encoding helper.
     *
     * @return \Ivory\GoogleMap\Helper\Geometry\EncodingHelper The encoding helper.
     */
    public function getEncodingHelper()
    {
        return $this->encodingHelper;
    }

    /**
     * Sets the encoding helper.
     *
     * @param \Ivory\GoogleMap\Helper\Geometry\EncodingHelper $encodingHelper The encoding helper.
     */
    public function setEncodingHelper(EncodingHelper $encodingHelper)
    {
        $this->encodingHelper = $encodingHelper;
    }

    /**
     * Renders an encoded polyline.
     *
     * @param \Ivory\GoogleMap\Overlays\EncodedPolyline $encodedPolyline The encoded polyline.
     * @param \Ivory\GoogleMap\Map                      $map             The map.
     *
     * @return string The JS output.
     */
    public function render(EncodedPolyline $encodedPolyline, Map $map)
    {
        $this->jsonBuilder
            ->reset()
            ->setValue('[map]', $map->getJavascriptVariable(), false)
            ->setValue('[path]', $this->encodingHelper->renderDecodePath($encodedPolyline->getValue()), false)
            ->setValues($encodedPolyline->getOptions());

        return sprintf(
            '%s = new google.maps.Polyline(%s);'.PHP_EOL,
            $encodedPolyline->getJavascriptVariable(),
            $this->jsonBuilder->build()
        );
    }
}


<?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\Base;

use Ivory\GoogleMap\Assets\AbstractJavascriptVariableAsset;
use Ivory\GoogleMap\Exception\BaseException;
use Ivory\GoogleMap\Overlays\ExtendableInterface;

/**
 * Bound wich describes a google map bound.
 *
 * @see http://code.google.com/apis/maps/documentation/javascript/reference.html#LatLngBounds
 * @author GeLo <geloen.eric@gmail.com>
 */
class Bound extends AbstractJavascriptVariableAsset
{
    /** @var \Ivory\GoogleMap\Base\Coordinate */
    protected $southWest;

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

    /** @var array */
    protected $extends;

    /**
     * Creates a bound.
     */
    public function __construct(Coordinate $southWest = null, Coordinate $northEast = null, array $extends = array())
    {
        $this->setPrefixJavascriptVariable('bound_');

        $this->setSouthWest($southWest);
        $this->setNorthEast($northEast);
        $this->setExtends($extends);
    }

    /**
     * Checks if the bound has coordinates.
     *
     * @return boolean TRUE if the bound has coordinates else FALSE.
     */
    public function hasCoordinates()
    {
        return ($this->southWest !== null) && ($this->northEast !== null);
    }

    /**
     * Gets the south west coordinate.
     *
     * @return \Ivory\GoogleMap\Base\Coordinate The south west coordinate.
     */
    public function getSouthWest()
    {
        return $this->southWest;
    }

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

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

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

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

    /**
     * Gets the north east coordinate.
     *
     * @return \Ivory\GoogleMap\Base\Coordinate The northh east coordinate.
     */
    public function getNorthEast()
    {
        return $this->northEast;
    }

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

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

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

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

    /**
     * Checks if the bound extends something.
     *
     * @return boolean TRUE if the bound extends somethind else FALSE.
     */
    public function hasExtends()
    {
        return !empty($this->extends);
    }

    /**
     * Gets the google map objects that the bound extends.
     *
     * @return array The objects that the bound extends.
     */
    public function getExtends()
    {
        return $this->extends;
    }

    /**
     * Sets the google map objects that the bound extends.
     *
     * @param array $extends The objects that the bound extends.
     */
    public function setExtends($extends)
    {
        $this->extends = array();

        foreach ($extends as $extend) {
            $this->extend($extend);
        }
    }

    /**
     * Adds an object that the bound extends.
     *
     * @param \Ivory\GoogleMap\Overlays\ExtendableInterface $extend The object that the bound extends.
     */
    public function extend(ExtendableInterface $extend)
    {
        $this->extends[] = $extend;
    }

    /**
     * Gets the center of the bound.
     *
     * @return \Ivory\GoogleMap\Base\Coordinate The bound center.
     */
    public function getCenter()
    {
        $centerLatitude = ($this->getSouthWest()->getLatitude() + $this->getNorthEast()->getLatitude()) / 2;
        $centerLongitude = ($this->getSouthWest()->getLongitude() + $this->getNorthEast()->getLongitude()) / 2;

        return new Coordinate($centerLatitude, $centerLongitude);
    }
}


<?php
/**
 * File size 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;

/**
 * File size utilities.
 *
 * @since 150424 Initial release.
 */
class FileSize extends Classes\Core\Base\Core
{
    /**
     * Abbreviated byte notation.
     *
     * @since 150424 Initial release.
     *
     * @param string $file Absolute file path.
     *
     * @return string Abbreviated byte notation.
     */
    public function abbr(string $file): string
    {
        if (!$file) {
            return ''; // Empty.
        } elseif (!is_file($file)) {
            return ''; // Not possible.
        } elseif (!is_readable($file)) {
            return ''; // Not possible.
        }
        return $this->bytesAbbr(filesize($file));
    }

    /**
     * Abbreviated byte notation.
     *
     * @since 150424 Initial release.
     *
     * @param int|float $bytes File size in bytes.
     * @note This accepts `float` for back compat. only.
     *
     * @param int $precision Number of decimals to use.
     *
     * @return string Abbreviated byte notation.
     */
    public function bytesAbbr(float $bytes, int $precision = 2): string
    {
        $bytes      = max(0, (int) $bytes);
        $precision  = max(0, $precision);

        $units      = ['bytes', 'kbs', 'MB', 'GB', 'TB'];
        $power      = floor(($bytes ? log($bytes) : 0) / log(1024));
        $abbr_bytes = round($bytes / pow(1024, $power), $precision);
        $abbr       = $units[min($power, count($units) - 1)];

        if ($abbr_bytes === (float) 1 && $abbr === 'bytes') {
            $abbr = 'byte'; // Quick fix.
        } elseif ($abbr_bytes === (float) 1 && $abbr === 'kbs') {
            $abbr = 'kb'; // Quick fix.
        }
        return $abbr_bytes.' '.$abbr;
    }

    /**
     * Bytes represented by notation.
     *
     * @since 150424 Initial release.
     * @since 170824.30708 Returns `int` instead of `float`.
     *
     * @param string $string A string byte notation.
     *
     * @return int Bytes represented by notation.
     */
    public function abbrBytes(string $string): int
    {
        if (!preg_match('/^(?<value>[0-9\.]+)\s*(?<modifier>bytes|byte|kbs|kb|k|mb|m|gb|g|tb|t)$/ui', $string, $_m)) {
            return 0; // Default value of `0`, failure.
        }
        $value    = (float) $_m['value'];
        $modifier = mb_strtolower($_m['modifier']);
        // unset($_m); // Housekeeping.

        switch ($modifier) {
            case 't':
            case 'tb':
                $value *= 1024;
                // Fall through.
                // no break
            case 'g':
            case 'gb':
                $value *= 1024;
                // Fall through.
                // no break
            case 'm':
            case 'mb':
                $value *= 1024;
                // Fall through.
                // no break
            case 'k':
            case 'kb':
            case 'kbs':
                $value *= 1024;
        }
        return (int) $value;
    }

    /**
     * Determines remote file size.
     *
     * @since 170824.30708 Remote file size.
     *
     * @param string $url            URL to check size of.
     * @param int    $expires_after  Cache expires after (in seconds).
     * @param bool   $report_failure If true, return `-1` on failure.
     *
     * @return int Remote file size, in bytes.
     * @note If `$report_failure` is true, `-1` indicates failure.
     * For example, if file does not exist, or it has no `content-length` header.
     */
    public function remoteBytes(string $url, bool $report_failure = false, int $expires_after = 86400): int
    {
        if (!$url) { // If no URL, no file size.
            return $report_failure ? -1 : 0;
        }
        if (($bytes = $this->c::dirCacheGet(__METHOD__, $url)) !== null) {
            return $bytes; // Cached this already.
        } // Get from an existing cache if at all possible.

        $r = $this->c::remoteResponse('HEAD::'.$url, [
            'max_con_secs' => 5, 'max_stream_secs' => 5,
        ]);
        if ($r->code === 200 && isset($r->headers['content-length'])) {
            $bytes = (int) $r->headers['content-length'];
        } else {
            $bytes = $report_failure ? -1 : 0; // Not possible.
        }
        $this->c::dirCacheSet(__METHOD__, $url, $bytes, $expires_after);
        return $bytes; // Cached for future reference.
    }
}


<?php
/**
 * URL parse 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;

/**
 * URL parse utilities.
 *
 * @since 150424 Initial release.
 */
class UrlParse extends Classes\Core\Base\Core
{
    /**
     * Parses a URL.
     *
     * @since 150424 Initial release.
     * @since 170124.74961 Adding `uri_no_fragment` key.
     * @since 170124.74961 Adding `$include_extra_parts` param.
     *
     * @param string $url_uri_qsl         Input URL, URI, or query string w/ a leading `?`.
     * @param int    $component           Optional component to retrieve a value for.
     * @param bool   $include_extra_parts N/A when `$component` is > `-1`.
     *
     * @return array|string|int|null Array, else `string|int|null` component value.
     *
     * Additional parts (i.e., `$include_extra_parts`) includes:
     *
     * - `full` Full original URL before parsing.
     * - `canonical` Full original URL minus `?query#fragment` parts.
     *
     * - `uri` Unparsed URL w/ only the `path`, `query`, and `fragment` parts.
     *    If the `path` part is not empty it will always start w/ a leading `/`.
     * - `uri_no_fragment` Unparsed URL w/ only the `path` and `query` parts.
     */
    public function __invoke(string $url_uri_qsl, int $component = -1, bool $include_extra_parts = true)
    {
        // Note: We must allow for `0` here.
        // It will parse as `[path => '0']`, which is valid.

        ${'//'} = mb_strpos($url_uri_qsl, '//') === 0;

        if ($component > -1) {
            if (${'//'} && $component === PHP_URL_SCHEME) {
                return $part = '//'; // Supports cross-protocol scheme.
            } else {
                return $part = parse_url($url_uri_qsl, $component);
            }
        } else { // Parse all parts.
            $parts = parse_url($url_uri_qsl);

            if (!is_array($parts)) {
                return $parts = [];
            }
            if (${'//'}) {
                $parts['scheme'] = '//';
            }
            if ($include_extra_parts) {
                $parts['full']            = $url_uri_qsl;
                $parts['canonical']       = preg_split('/[?#]/u', $url_uri_qsl, 2)[0];

                $parts['uri']             = $this->un(array_intersect_key($parts, ['path' => null, 'query' => null, 'fragment' => null]));
                $parts['uri_no_fragment'] = $parts['uri'] ? preg_split('/#/u', $parts['uri'], 2)[0] : $parts['uri'];
            }
            return $parts;
        }
    }

    /**
     * Unparses a URL.
     *
     * @since 150424 Initial release.
     *
     * @param array $parts Input URL parts.
     *
     * @return string Unparsed URL in string format.
     */
    public function un(array $parts): string
    {
        $scheme   = '';
        $host     = $port     = '';
        $user     = $pass     = '';
        $path     = $query     = $fragment = '';

        $parts = array_map('strval', $parts);

        if (isset($parts['scheme'][0])) {
            if ($parts['scheme'] === '//') {
                $scheme = $parts['scheme'];
            } else {
                $scheme = $parts['scheme'].'://';
            }
        } // Supports cross-protocol scheme.

        if (isset($parts['host'][0])) {
            $host = $parts['host'];
        }
        if (isset($parts['port'][0])) {
            $port = ':'.$parts['port'];
        }
        if (isset($parts['user'][0])) {
            $user = $parts['user'];
        }
        if (isset($parts['pass'][0])) {
            $pass = ':'.$parts['pass'];
        }
        if (isset($user[0]) || isset($pass[0])) {
            $pass .= '@'; // `user@`, `:pass@`, or `user:pass@`.
        }
        if (isset($parts['path'][0])) {
            $path = '/'.$this->c::mbLTrim($parts['path'], '/');
        }
        if (isset($parts['query'][0])) {
            $query = '?'.$parts['query'];
        }
        if (isset($parts['fragment'][0])) {
            $fragment = '#'.$parts['fragment'];
        }
        return $scheme.$user.$pass.$host.$port.$path.$query.$fragment;
    }
}


<?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\Helper\Base;

use Ivory\GoogleMap\Base\Coordinate;

/**
 * Coordinate templating helper.
 *
 * @author GeLo <geloen.eric@gmail.com>
 */
class CoordinateHelper
{
    /**
     * Renders a coordinate.
     *
     * @param \Ivory\GoogleMap\Base\Coordinate $coordinate The coordinate.
     *
     * @return string The JS output.
     */
    public function render(Coordinate $coordinate)
    {
        return sprintf(
            '%s = new google.maps.LatLng(%s, %s, %s);'.PHP_EOL,
            $coordinate->getJavascriptVariable(),
            $coordinate->getLatitude(),
            $coordinate->getLongitude(),
            json_encode($coordinate->isNoWrap())
        );
    }
}


<?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\Helper\Overlays;

use Ivory\GoogleMap\Overlays\MarkerImage;

/**
 * Marker image helper.
 *
 * @author GeLo <geloen.eric@gmail.com>
 */
class MarkerImageHelper
{
    /**
     * Renders a marker image.
     *
     * @param \Ivory\GoogleMap\Overlays\MarkerImage $markerImage The marker image.
     *
     * @return string The JS output.
     */
    public function render(MarkerImage $markerImage)
    {
        return sprintf(
            '%s = new google.maps.MarkerImage("%s", %s, %s, %s, %s);'.PHP_EOL,
            $markerImage->getJavascriptVariable(),
            $markerImage->getUrl(),
            $markerImage->hasSize() ? $markerImage->getSize()->getJavascriptVariable() : 'null',
            $markerImage->hasOrigin() ? $markerImage->getOrigin()->getJavascriptVariable() : 'null',
            $markerImage->hasAnchor() ? $markerImage->getAnchor()->getJavascriptVariable() : 'null',
            $markerImage->hasScaledSize() ? $markerImage->getScaledSize()->getJavascriptVariable() : 'null'
        );
    }
}


<?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\Utils;

/**
 * Xml parser.
 *
 * @author GeLo <geloen.eric@gmail.com>
 */
class XmlParser
{
    /**
     * Parses xml according to pluralization rules.
     *
     * @param string  $xml                The xml.
     * @param array   $pluralizationRules The pluralization rules.
     *
     * @return \stdClass The parsed & pluralized xml.
     */
    public function parse($xml, array $pluralizationRules = array())
    {
        $parsedXml = json_decode(json_encode(new \SimpleXMLElement($xml)), true);

        return $this->pluralize($parsedXml, $pluralizationRules);
    }

    /**
     * Pluralizes xml.
     *
     * @param array $xml   The xml.
     * @param array $rules The pluralization rules.
     *
     * @return \stdClass The pluralized xml.
     */
    protected function pluralize(array $xml, array $rules)
    {
        foreach ($xml as $attribute => $value) {
            if (isset($rules[$attribute])) {
                $xml[$rules[$attribute]] = $value;
                unset($xml[$attribute]);

                $attribute = $rules[$attribute];

                if (!is_array($value) || is_string(key($value))) {
                    $xml[$attribute] = array($value);
                }
            }

            if (is_array($xml[$attribute])) {
                $xml[$attribute] = $this->pluralize($xml[$attribute], $rules);
            }
        }

        return $this->normalize($xml);
    }

    /**
     * Normalizes xml.
     *
     * @param array $xml The xml.
     *
     * @return array|\stdClass The normalized xml.
     */
    protected function normalize(array $xml)
    {
        if (is_string(key($xml))) {
            return (object) $xml;
        }

        return $xml;
    }
}


<?php
/**
 * Throwable 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;

/**
 * Throwable utilities.
 *
 * @since 160711 Throwables.
 */
class Throwables extends Classes\Core\Base\Core
{
    /**
     * Setup handler.
     *
     * @since 160711 Throwables.
     */
    public function handle()
    {
        set_exception_handler([$this, 'handler']);
    }

    /**
     * Throwable handler.
     *
     * @since 160711 Throwables.
     *
     * @param \Throwable $Throwable Error/Exception.
     */
    public function handler(\Throwable $Throwable)
    {
        if ($this->c::isCli()) {
            // Set `STDERR` so that it can be used in CLI feedback.
            // If debugging, `STDERR` should include a full stack trace.
            // If it's not an interactive terminal session, try to log the error.
            // The exit status should always be `1` to indicate an error.

            try { // Catch throwables.

                $this->c::noCacheFlags();
                $this->c::sessionWriteClose();
                $this->c::obEndCleanAll();

                if ($this->App->Config->©debug['©enable']) {
                    $this->c::writeStderr($Throwable->__toString());
                } else {
                    $this->c::writeStderr($Throwable->getMessage());
                }
                if (!$this->c::isCliInteractive()) {
                    error_log(str_replace("\0", '', $Throwable->__toString()));
                }
                exit(1); // Exit status code.
                //
            } catch (\Throwable $inner_Throwable) { // Edge case.
                exit(1); // Simply exit in this edge case.
            }
        } elseif (!headers_sent()) { // Send a 500 error response code.
            // If there is a throwable template, use the throwable template.
            // In either case, rethrow; i.e., allow PHP to log as an error.
            // It's also IMPORTANT to rethrow so that execution stops!

            try { // Catch throwables.

                $this->c::noCacheFlags();
                $this->c::sessionWriteClose();
                $this->c::obEndCleanAll();

                $this->c::statusHeader(500);
                $this->c::noCacheHeaders();
                header('content-type: text/html; charset=utf-8');

                echo $this->c::getTemplate('http/html/status/500.php')
                    ->parse(['Throwable' => $Throwable]);
                //
            } catch (\Throwable $inner_Throwable) {
                echo 'Unexpected error. Please try again.'."\n"; // Edge case.
            }
            throw $Throwable; // Rethrow. i.e., allow PHP to log as an error.
            // ↑ NOTE: Assumes throwables will not be handled here when `display_errors=yes`.
            // Therefore, when the above template is displayed, that's all you'll see in most cases.
            // i.e., Under most conditions, the display of this PHP error should not be seen. Only logged.
        } else {
            // Should be avoided. It's always better to buffer output so that an error
            // can be shown instead of what would have been output to a browser otherwise.
            // Our own template handler uses output buffering so this is not an issue with core.
            throw $Throwable; // Rethrow. i.e., log and/or display if debugging.
        }
    }
}


<?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\Exception;

use Ivory\GoogleMap\Services\DistanceMatrix\DistanceMatrixStatus;
use Ivory\GoogleMap\Services\DistanceMatrix\DistanceMatrixElementStatus;
use Ivory\GoogleMap\Services\Base\TravelMode;
use Ivory\GoogleMap\Services\Base\UnitSystem;

/**
 * DistanceMatrix exception.
 *
 * @author GeLo <geloen.eric@gmail.com>
 * @author Tyler Sommer <sommertm@gmail.com>
 */
class DistanceMatrixException extends ServiceException
{
    /**
     * Gets the "INVALID DISTANCE MATRIX REQUEST" exception.
     *
     * @return \Ivory\GoogleMap\Exception\DistanceMatrixException The "INVALID DISTANCE MATRIX REQUEST" exception.
     */
    public static function invalidDistanceMatrixRequest()
    {
        return new static('The directions request is not valid. It needs at least one origin and one destination.');
    }

    /**
     * Gets the "INVALID DISTANCE MATRIX REQUEST PARAMETERS" exception.
     *
     * @return \Ivory\GoogleMap\Exception\DistanceMatrixException The "INVALID DISTANCE MATRIX REQUEST PARAMETERS" exception.
     */
    public static function invalidDistanceMatrixRequestParameters()
    {
        return new static(sprintf(
            '%s'.PHP_EOL.'%s'.PHP_EOL.'%s'.PHP_EOL.'%s',
            'The process arguments are invalid.',
            'The available prototypes are:',
            '- function process(array $origins, array $destinations)',
            '- function process(Ivory\GoogleMap\Services\DistanceMatrix\DistanceMatrixRequest $request)'
        ));
    }

    /**
     * Gets the "INVALID DISTANCE MATRIX REQUEST DESTINATION" exception.
     *
     * @return \Ivory\GoogleMap\Exception\DistanceMatrixException The "INVALID DISTANCE MATRIX REQUEST DESTINATION" exception.
     */
    public static function invalidDistanceMatrixRequestDestination()
    {
        return new static(sprintf(
            '%s'.PHP_EOL.'%s'.PHP_EOL.'%s'.PHP_EOL.'%s'.PHP_EOL.'%s',
            'The destination adder arguments are invalid.',
            'The available prototypes are :',
            ' - function addDestination(string $destination)',
            ' - function addDestination(Ivory\GoogleMap\Base\Coordinate $destination)',
            ' - function addDestination(double $latitude, double $longitude, boolean $noWrap)'
        ));
    }

    /**
     * Gets the "INVALID DISTANCE MATRIX REQUEST ORIGIN" exception.
     *
     * @return \Ivory\GoogleMap\Exception\DistanceMatrixException The "INVALID DISTANCE MATRIX REQUEST ORIGIN" exception.
     */
    public static function invalidDistanceMatrixRequestOrigin()
    {
        return new static(sprintf(
            '%s'.PHP_EOL.'%s'.PHP_EOL.'%s'.PHP_EOL.'%s'.PHP_EOL.'%s',
            'The origin adder arguments are invalid.',
            'The available prototypes are :',
            ' - function addOrigin(string $origin)',
            ' - function addOrigin(Ivory\GoogleMap\Base\Coordinate $origin)',
            ' - function addOrigin(double $latitude, double $longitude, boolean $noWrap)'
        ));
    }

    /**
     * Gets the "INVALID DISTANCE MATRIX REQUEST REGION" exception.
     *
     * @return \Ivory\GoogleMap\Exception\DistanceMatrixException The "INVALID DISTANCE MATRIX REQUEST REGION" exception.
     */
    public static function invalidDistanceMatrixRequestRegion()
    {
        return new static('The distance matrix request region must be a string with two characters.');
    }

    /**
     * Gets the "INVALID DISTANCE MATRIX REQUEST LANGUAGE" exception.
     *
     * @return \Ivory\GoogleMap\Exception\DistanceMatrixException The "INVALID DISTANCE MATRIX REQUEST LANGUAGE" exception.
     */
    public static function invalidDistanceMatrixRequestLanguage()
    {
        return new static('The distance matrix request language must be a string with two or five characters.');
    }

    /**
     * Gets the "INVALID DISTANCE MATRIX REQUEST SENSOR" exception.
     *
     * @return \Ivory\GoogleMap\Exception\DistanceMatrixException The "INVALID DISTANCE MATRIX REQUEST SENSOR" exception.
     */
    public static function invalidDistanceMatrixRequestSensor()
    {
        return new static('The distance matrix request sensor flag must be a boolean value.');
    }

    /**
     * Gets the "INVALID DISTANCE MATRIX REQUEST TRAVEL MODE" exception.
     *
     * @return \Ivory\GoogleMap\Exception\DistanceMatrixException The "INVALID DISTANCE MATRIX REQUEST TRAVEL MODE" exception.
     */
    public static function invalidDistanceMatrixRequestTravelMode()
    {
        $travelModes = array_diff(TravelMode::getTravelModes(), array(TravelMode::TRANSIT));

        return new static(sprintf(
            'The distance matrix request travel mode can only be : %s.',
            implode(', ', $travelModes)
        ));
    }

    /**
     * Gets the "INVALID DISTANCE MATRIX REQUEST UNIT SYSTEM" exception.
     *
     * @return \Ivory\GoogleMap\Exception\DistanceMatrixException The "INVALID DISTANCE MATRIX REQUEST UNIT SYSTEM" exception.
     */
    public static function invalidDistanceMatrixRequestUnitSystem()
    {
        return new static(sprintf(
            'The distance matrix request unit system can only be : %s.',
            implode(', ', UnitSystem::getUnitSystems())
        ));
    }

    /**
     * Gets the "INVALID DISTANCE MATRIX REQUEST AVOID HIGHWAYS" exception.
     *
     * @return \Ivory\GoogleMap\Exception\DistanceMatrixException The "INVALID DISTANCE MATRIX REQUEST AVOID HIGHWAYS" exception.
     */
    public static function invalidDistanceMatrixRequestAvoidHighways()
    {
        return new static('The distance matrix request avoid hightways flag must be a boolean value.');
    }

    /**
     * Gets the "INVALID DISTANCE MATRIX REQUEST AVOID TOLLS" exception.
     *
     * @return \Ivory\GoogleMap\Exception\DistanceMatrixException The "INVALID DISTANCE MATRIX REQUEST AVOID TOLLS" exception.
     */
    public static function invalidDistanceMatrixRequestAvoidTolls()
    {
        return new static('The distance matrix request avoid tolls flag must be a boolean value.');
    }

    /**
     * Gets the "INVALID DISTANCE MATRIX RESPONSE STATUS" exception.
     *
     * @return \Ivory\GoogleMap\Exception\DistanceMatrixException The "INVALID DISTANCE MATRIX RESPONSE STATUS" exception.
     */
    public static function invalidDistanceMatrixResponseStatus()
    {
        return new static(sprintf(
            'The distance matrix response status can only be : %s.',
            implode(', ', DistanceMatrixStatus::getDistanceMatrixStatus())
        ));
    }

    /**
     * Gets the "INVALID DISTANCE MATRIX RESPONSE ELEMENT STATUS" exception.
     *
     * @return \Ivory\GoogleMap\Exception\DistanceMatrixException The "INVALID DISTANCE MATRIX RESPONSE ELEMENT STATUS" exception.
     */
    public static function invalidDistanceMatrixResponseElementStatus()
    {
        return new static(sprintf(
            'The distance matrix response element status can only be : %s.',
            implode(', ', DistanceMatrixElementStatus::getDistanceMatrixElementStatus())
        ));
    }
}


<?php
/**
 * URL host 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;

/**
 * URL host utilities.
 *
 * @since 151002 Adding host parser.
 */
class UrlHost extends Classes\Core\Base\Core
{
    /**
     * Host parser.
     *
     * @since 151002 Adding host parser.
     * @since 170124.74961 Adding `port` key.
     * @since 170124.74961 Adding `root_name` key.
     * @since 170124.74961 Adding `root_port` key.
     * @since 170124.74961 Removing `root_as_name` key.
     * @since 170124.74961 The `name` key now contains only the name.
     * @since 170124.74961 The `root` key now contains a full root host (including port).
     * @since 170124.74961 Bug fix. Hosts with ports in their name are now exploded properly.
     *
     * @param string $host The input host to parse.
     *
     * @return array `[name, port, sub, subs, root, root_name, root_port, root_basename, tld]`.
     *
     * @internal This allows an empty `$host` so that a caller can get the array elements even if empty.
     */
    public function parse(string $host, bool $no_port = false): array
    {
        $host = mb_strtolower($host); // `abc.xyz.example.com:80`.

        $name = preg_replace('/\:[0-9]+$/u', '', $host); // `abc.xyz.example.com`.
        $port = (explode(':', $host, 2) + ['', ''])[1]; // `80`; i.e., port number.

        $name_parts = explode('.', $name); // `[abc,xyz,example,com]`.

        $subs = array_slice($name_parts, 0, -2); // `[abc,xyz]`.
        $sub  = implode('.', $subs); // `abc.xyz`.

        $root_name     = implode('.', array_slice($name_parts, -2)); // `example.com`.
        $root_port     = $port; // This is nothing more than a copy of the `$port`; `80`.
        $root          = $root_name.(isset($root_port[0]) ? ':'.$root_port : ''); // `example.com:80`.
        $root_basename = implode('.', array_slice($name_parts, -2, 1)); // `example`.

        $tld = implode('.', array_slice($name_parts, -1)); // `com`, `net`, `org`, etc.

        return compact('name', 'port', 'sub', 'subs', 'root', 'root_name', 'root_port', 'root_basename', 'tld');
    }

    /**
     * Unparses a host.
     *
     * @since 151002 Adding host parser.
     * @since 170124.74961 Collecting both `name` and `port` now.
     *
     * @param array $parts Input host parts.
     *
     * @return string Unparsed host string.
     */
    public function unParse(array $parts): string
    {
        $name = $port= '';

        if (isset($parts['name'][0])) {
            $name = $parts['name'];
        }
        if (isset($parts['port'][0])) {
            $port = ':'.$parts['port'];
        }
        return $name.$port;
    }
}


<?php

namespace webignition\InternetMediaType\Parameter\Parser;

use webignition\InternetMediaType\Parser\Configuration;
use webignition\StringParser\StringParser;

/**
 * Parses out the attribute name from an internet media type parameter string
 */
class AttributeParser extends StringParser
{
    const ATTRIBUTE_VALUE_SEPARATOR = '=';
    const STATE_IN_ATTRIBUTE_NAME = 1;
    const STATE_INVALID_INTERNAL_CHARACTER = 2;
    const STATE_LEFT_ATTRIBUTE_NAME = 3;

    /**
     * Collection of characters not valid in an attribute name
     *
     * @var array
     */
    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 AttributeParserException
     */
    public function parse($inputString): string
    {
        return parent::parse(trim($inputString));
    }

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

            case self::STATE_IN_ATTRIBUTE_NAME:
                if ($this->isCurrentCharacterInvalid()) {
                    if ($this->shouldIgnoreInvalidCharacter()) {
                        $this->incrementCurrentCharacterPointer();
                        $this->setCurrentState(self::STATE_LEFT_ATTRIBUTE_NAME);
                        $this->clearOutputString();
                    } else {
                        $this->setCurrentState(self::STATE_INVALID_INTERNAL_CHARACTER);
                    }
                } elseif ($this->isCurrentCharacterAttributeValueSeparator()) {
                    $this->setCurrentState(self::STATE_LEFT_ATTRIBUTE_NAME);
                } else {
                    $this->appendOutputString();
                    $this->incrementCurrentCharacterPointer();
                }

                break;

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

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

                    $attributeFixer = new AttributeFixer();
                    $attributeFixer->setInputString($this->getInputString());
                    $fixedInputString = $attributeFixer->fix();

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

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

        return null;
    }

    private function shouldIgnoreInvalidCharacter(): bool
    {
        if (false === $this->getConfiguration()->ignoreInvalidAttributes()) {
            return false;
        }

        if (false === $this->getConfiguration()->attemptToRecoverFromInvalidInternalCharacter()) {
            return true;
        }

        if ($this->hasAttemptedToFixAttributeInvalidInternalCharacter) {
            return true;
        }

        return false;
    }

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

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

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


<?php
/**
 * HTML balance 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;

/**
 * HTML balance utilities.
 *
 * @since 150424 Initial release.
 */
class HtmlBalance extends Classes\Core\Base\Core
{
    /**
     * Balance HTML markup/tags.
     *
     * @since 150424 Initial release.
     *
     * @param mixed $value Any input value.
     *
     * @throws Exception If missing DOM extension.
     *
     * @return string|array|object Balanced value (i.e., HTML markup).
     *
     * @internal This works with HTML fragments only. No on a full document.
     * i.e., If the input contains `</html>` or `</body>` it is left as-is.
     */
    public function tags($value)
    {
        if (is_array($value) || is_object($value)) {
            foreach ($value as $_key => &$_value) {
                $_value = $this->tags($_value);
            } // unset($_key, $_value);
            return $value;
        }
        if (!($string = (string) $value)) {
            return $string; // Nothing to do.
        } elseif (mb_stripos($string, '') !== false) {
            return $string; // Not a fragment; leave as-is.
        } elseif (mb_stripos($string, '') !== false) {
            return $string; // Not a fragment; leave as-is.
        }
        $html = // UTF-8 encoding.
            ''.
            '   '.
            '       <meta charset="utf-8">'.
            '       <meta http-equiv="content-type" content="text/html; charset=utf-8">'.
            '   '.
            '   '.$string.''.
            '';
        $Dom = new \DOMDocument();
        $Dom->loadHTML($html);

        $string = $Dom->saveHTML($Dom->getElementsByTagName('body')->item(0));
        $string = preg_replace(['/\/ui', '/\<\/body\>/ui'], '', $string);
        $string = $this->c::htmlTrim($string);

        return $string;
    }
}