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

use Ivory\GoogleMap\Controls\StreetViewControl;
use Ivory\GoogleMap\Helper\AbstractHelper;

/**
 * Street view control helper.
 *
 * @author GeLo <geloen.eric@gmail.com>
 */
class StreetViewControlHelper extends AbstractHelper
{
    /** @var \Ivory\GoogleMap\Helper\Controls\ControlPositionHelper */
    protected $controlPositionHelper;

    /**
     * Creates a street view control helper.
     *
     * @param \Ivory\GoogleMap\Helper\Controls\ControlPositionHelper $controlPositionHelper The control position helper.
     */
    public function __construct(ControlPositionHelper $controlPositionHelper = null)
    {
        parent::__construct();

        if ($controlPositionHelper === null) {
            $controlPositionHelper = new ControlPositionHelper();
        }

        $this->setControlPositionHelper($controlPositionHelper);
    }

    /**
     * Gets the control position helper.
     *
     * @return \Ivory\GoogleMap\Helper\Controls\ControlPositionHelper The control position helper.
     */
    public function getControlPositionHelper()
    {
        return $this->controlPositionHelper;
    }

    /**
     * Sets the control position helper.
     *
     * @param \Ivory\GoogleMap\Helper\Controls\ControlPositionHelper $controlPositionHelper The control position helper.
     */
    public function setControlPositionHelper(ControlPositionHelper $controlPositionHelper)
    {
        $this->controlPositionHelper = $controlPositionHelper;
    }

    /**
     * Renders the street view control
     *
     * @param Ivory\GoogleMapBundle\Model\Controls\StreetViewControl $streetViewControl
     * @return string HTML output
     */
    public function render(StreetViewControl $streetViewControl)
    {
        return $this->jsonBuilder
            ->reset()
            ->setValue(
                '[position]',
                $this->controlPositionHelper->render($streetViewControl->getControlPosition()),
                false
            )
            ->build();
    }
}


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

/**
 * EOL utilities.
 *
 * @since 150424 Initial release.
 */
class Eols extends Classes\Core\Base\Core
{
    /**
     * Normalizes end of line chars deeply.
     *
     * @since 150424 Initial release.
     *
     * @param mixed $value    Any input value.
     * @param bool  $compress Compress `\n{3,}` into `\n\n`?
     *
     * @return string|array|object With normalized end of line chars deeply.
     */
    public function normalize($value, bool $compress = false)
    {
        if (is_array($value) || is_object($value)) {
            foreach ($value as $_key => &$_value) {
                $_value = $this->normalize($_value, $compress);
            } // unset($_key, $_value);
            return $value;
        }
        if (!($string = (string) $value)) {
            return $string; // Nothing to do.
        }
        $string = str_replace(["\r\n", "\r"], "\n", $string);
        $string = $compress ? preg_replace('/'."\n".'{3,}/u', "\n\n", $string) : $string;

        return $string;
    }
}


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

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

/**
 * Regex pattern utilities.
 *
 * @since 150424 Initial release.
 */
class RegexPattern extends Classes\Core\Base\Core
{
    /**
     * Match a regex pattern against other values.
     *
     * @since 150424 Initial release.
     *
     * @param string $regex             A regular expression.
     * @param mixed  $value             Any value can be converted into a string before comparison.
     *                                  Actually, objects can't, but this recurses into objects.
     * @param bool   $collect_key_props Collect array keys and/or object properties?
     *
     * @return bool|array Will return `true` if regex pattern finds a match.
     *                    If `$collect_key_props` is `true`; returns array.
     */
    public function in(string $regex, $value, bool $collect_key_props = false)
    {
        if (!$regex) { // Empty regex?
            return $collect_key_props ? [] : false;
        }
        $matching_key_props = []; // Initialize.

        if (is_array($value) || is_object($value)) {
            foreach ($value as $_key_prop => $_value) {
                if (is_array($_value) || is_object($_value)) {
                    if (($_matching_key_props = $this->in($regex, $_value, $collect_key_props))) {
                        if ($collect_key_props) {
                            $matching_key_props[] = [$_key_prop => $_matching_key_props];
                        } else {
                            return true;
                        }
                    }
                } else {
                    $_value = (string) $_value;
                    if (preg_match($regex, $_value)) {
                        if ($collect_key_props) {
                            $matching_key_props[] = $_key_prop;
                        } else {
                            return true;
                        }
                    }
                }
            } // unset($_key_prop, $_value, $_matching_key_props);
        } elseif (!$collect_key_props) {
            $value = (string) $value;
            if (preg_match($regex, $value)) {
                return true;
            }
        }
        return $collect_key_props ? $matching_key_props : false;
    }
}


<?php
/**
 * HTML whitespace 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 whitespace utilities.
 *
 * @since 150424 Initial release.
 */
class HtmlWhitespace extends Classes\Core\Base\Core implements Interfaces\HtmlConstants
{
    /**
     * Normalizes HTML whitespace deeply.
     *
     * @since 150424 Initial release.
     *
     * @param mixed $value    Any input value.
     * @param bool  $compress Compress `\n{3,}` into 2?
     *
     * @return string|array|object With normalized HTML whitespace deeply.
     */
    public function normalize($value, bool $compress = false)
    {
        if (is_array($value) || is_object($value)) {
            foreach ($value as $_key => &$_value) {
                $_value = $this->normalize($_value, $compress);
            } // unset($_key, $_value);
            return $this->c::normalizeEols($value, $compress);
        }
        if (!($string = (string) $value)) {
            return $string; // Nothing to do.
        }
        if (($whitespace = &$this->cacheKey(__FUNCTION__.'_whitespace')) === null) {
            $whitespace = implode('|', $this::HTML_WHITESPACE);
        }
        if ($compress) { // NOTE: This is the only task at the moment. It could change in the future.
            $string = preg_replace('/('.$whitespace.')('.$whitespace.')('.$whitespace.')+/u', '${1}${2}', $string);
        }
        return $this->c::normalizeEols($string, $compress);
    }
}


<?php
/**
 * Watered-down regex.
 *
 * @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;

/**
 * Watered-down regex.
 *
 * @since 150424 Watered-down regex.
 */
class WRegx extends Classes\Core\Base\Core
{
    /**
     * Convert watered-down regex to real regex.
     *
     * @since 150424 Watered-down regex.
     *
     * - `*` Matches zero or more characters that are not a `/`
     * - `**` Matches zero or more characters of any kind.
     * - `?` Matches exactly one character that is not a `/`
     * - `??` Matches exactly one character of any kind.
     * - `[abc]` Matches exactly one character: `a`, `b`, or `c`.
     * - `[a-z0-9]` Matches exactly one character: `a` thru `z` or `0` thru `9`.
     * - `[!abc]` A leading `!` inside `[]` negates; i.e., anything that is not: `a`, `b`, or `c`.
     * - `{abc,def}` Matches the fragment `abc` or `def` (one or the other).
     * - `{abc,def,}` Matches `abc`, `def` or nothing; i.e., an optional match.
     * - `{/**,}` Matches a `/` followed by zero or more characters. Or nothing.
     * - `[*?[]!{},^$]` Matches a literal special character. One of: `*?[]!{},^$` explicitly.
     *
     * - `^` = beginning of the string (but can be turned off depending on the use case).
     * - `$` = end of the string (but can be turned off depending on the use case).
     *
     * @param array|string $patterns        Watered-down regex patterns; array or line-delimited.
     * @param string       $exclusion_chars The behavior of `*` & `?`. Defaults to excluding `/`.
     * @param bool         $force_match_all Force `^$` into play & don't treat `^$` as special chars.
     * @param bool         $capture         Capture the matches, or use the default `(?:)` syntax?
     *
     * @internal `$exclusion_chars` may not contain: `⁅`, `⒯`, `⁆`, `?` or `*`
     *
     * @return string A real regex pattern; ready for {@link preg_match()}.
     */
    public function __invoke($patterns, string $exclusion_chars = '/', bool $force_match_all = false, bool $capture = false): string
    {
        $regex = ''; // Initialize.

        if (is_string($patterns)) { // Convert to array.
            $patterns = preg_split('/\v+/u', $patterns, -1, PREG_SPLIT_NO_EMPTY);
        }
        if (!is_array($patterns)) { // Require an array now.
            throw $this->c::issue('Invalid data type for patterns.');
        }
        $patterns            = $this->c::remove0Bytes($patterns);
        $regex_pattern_frags = $this->frag($patterns, $exclusion_chars, $force_match_all);

        if ($regex_pattern_frags) { // Have an array of regex pattern fragments?
            $regex = '/('.($capture ? '' : '?:').implode('|', $regex_pattern_frags).')/u';
        }
        return $regex;
    }

    /**
     * Convert watered-down regex to real regex fragment.
     *
     * @since 150424 Watered-down regex.
     *
     * @param mixed  $value           Input value(s) w/ watered-down regex.
     * @param string $exclusion_chars The behavior of `*` & `?`. Defaults to excluding `/`.
     * @param bool   $force_match_all Force `^$` into play & don't treat `^$` as special chars.
     *
     * @internal `$exclusion_chars` may not contain: `⁅`, `⒯`, `⁆`, `?` or `*`
     *
     * @return string|array|object Value(s) as true regex fragments.
     */
    public function frag($value, string $exclusion_chars = '/', bool $force_match_all = false)
    {
        if (is_array($value) || is_object($value)) {
            foreach ($value as $_key => &$_value) {
                $_value = $this->frag($_value, $exclusion_chars, $force_match_all);
            } // unset($_key, $_value); // Housekeeping.
            return $value;
        }
        if (!($string = (string) $value)) {
            return $string; // Empty.
        }
        if (!$exclusion_chars) { // Must have this.
            throw $this->c::issue('Missing `exclusion_chars`.');
        } elseif ($exclusion_chars !== '/') {
            $_exclusion_chars_array = $this->c::mbStrSplit($exclusion_chars);
            if (array_diff($_exclusion_chars_array, ['⁅', '⒯', '⁆', '?', '*']) !== $_exclusion_chars_array) {
                throw $this->c::issue('Invalid `exclusion_chars`. May not exclude: `⁅`, `⒯`, `⁆`, `?` or `*`.');
            } // unset($_exclusion_chars_array); // Housekeeping.
        }
        $tokens          = []; // Initialize.
        $string          = $this->c::escRegex($string);
        $exclusion_chars = $this->c::escRegex($exclusion_chars);

        // Convert `[!a-z0-9]` into a real regex `[^character class]`.
        // Special characters inside a character class have no further meaning.
        // For this reason, we remove them from the equation by tokenizing them, for now.
        $string = preg_replace_callback('/\\\\\[((?:(?:[^[\]]+)|(?R))*)\\\\\]/u', function ($m) use (&$tokens) {
            $m[1] = mb_strpos($m[1], '\\!') === 0 ? '^'.mb_substr($m[1], 2) : $m[1];
            $m[1] = preg_replace('/([a-z0-9])\\\\\-([a-z0-9])/u', '${1}-${2}', $m[1]);
            $tokens[] = '['.$m[1].']';
            return '⁅⒯'.(count($tokens) - 1).'⒯⁆';
        }, $string); // See restoration below.

        // Convert `{}` into `(?:this|that)`; i.e., a list of alternates.
        $string = preg_replace_callback('/\\\\\{((?:(?:[^{}]+)|(?R))*)\\\\\}/u', function ($m) {
            return '(?:'.str_replace(['\\{', '\\}', ','], ['(?:', ')', '|'], $m[1]).')';
        }, $string); // `,` becomes `|` for regex alternation.

        // Deal with `^` and `$`, depending on `$force_match_all`.
        if (!$force_match_all) { // Must come before `$exclusion_chars` below.
            // If not forcing `^$`, we need to treat them as special chars here.
            $string = preg_replace(['/\\\\\^/u', '/\\\\\$/u'], ['^', '$'], $string);
        }
        // `???` (or more) should be treated like `?` instead of as a partial `??` operator.
        $string = preg_replace_callback('/(?:\\\\\?){3,}/u', function ($m) use ($exclusion_chars) {
            return '[^'.$exclusion_chars.']{'.strlen(str_replace('\\', '', $m[0])).'}';
        }, $string); // i.e., Treat as three or more instances of `?` by itself.

        // Now convert `??` and `**` (or more) after having already converted `???`... above.
        $string = preg_replace(['/(?:\\\\\?){2}/u', '/(?:\\\\\*){2,}/u'], ['[\s\S]', '[\s\S]*?'], $string);

        // Now convert any remaining `?` and/or `*` operators after having dealt with `??` and `**` (or more) above.
        $string = preg_replace(['/\\\\\?/u', '/\\\\\*/u'], ['[^'.$exclusion_chars.']', '[^'.$exclusion_chars.']*?'], $string);

        // Restore special characters tokenized when we began above.
        foreach (array_reverse($tokens, true) as $_token => $_brackets) {
            // Must go in reverse order so nested tokens unfold properly.
            $string = str_replace('⁅⒯'.$_token.'⒯⁆', $_brackets, $string);
        } // unset($_token, $_brackets); // Housekeeping.

        // Return value based on `$force_match_all`.
        return $force_match_all ? '^'.$string.'$' : $string;
    }

    /**
     * Bracket special chars.
     *
     * @since 170124.74961 New name. Old name `bracket()`.
     * @since 170124.74961 Adding `$will_force_match_all` param.
     *
     * @param mixed $value                Input value(s) to bracket here.
     * @param bool  $will_force_match_all If true, don't treat `^$` as special chars.
     *
     * @return string|array|object Bracketed value(s).
     */
    public function bracketSpecialChars($value, bool $will_force_match_all = false)
    {
        if (is_array($value) || is_object($value)) {
            foreach ($value as $_key => &$_value) {
                $_value = $this->bracketSpecialChars($_value, $will_force_match_all);
            } // unset($_key, $_value); // Housekeeping.
            return $value;
        }
        if (!($string = (string) $value)) {
            return $string; // Empty.
        }
        if (!$will_force_match_all) {
            $string = preg_replace('/[*?[\]!{},\^$]/u', '[${0}]', $string);
        } else { // `^` and `$` are not special characters.
            $string = preg_replace('/[*?[\]!{},]/u', '[${0}]', $string);
        }
        return $string;
    }

    /**
     * URL to WRegx URI pattern.
     *
     * @since 160428 Watered-down regex.
     * @since 170124.74961 Adding `$will_force_match_all` param.
     * @since 170124.74961 Fixing bug in query string wildcard pattern.
     *
     * @param mixed $value                Input value(s) containing:
     *                                    URLs, URIs, or query strings w/ a leading `?`.
     * @param bool  $will_force_match_all If true, don't treat `^$` as special chars.
     *
     * @return string WRegx URI pattern, else an empty string if unable to parse.
     *
     * - This considers all possible `/endpoints` after the base.
     *   Including the possibility of there being `index.php/path/info/`.
     *
     * - If a URI contains multiple query string variables,
     *   the best we can do is `{&**&,&}` (searching in the order given).
     *
     * - In the case of a root URI or empty path, this returns a pattern
     *   matching any path [/endpoints] & query string; i.e., matches all URIs.
     *   This will also be true if only a query string is given. Path is empty.
     */
    public function urlToUriPattern($value, bool $will_force_match_all = false)
    {
        // Note: We must allow for `0` here.
        // It will parse as `[path => '0']`, which is valid.

        if (is_array($value) || is_object($value)) {
            foreach ($value as $_key => &$_value) {
                $_value = $this->urlToUriPattern($_value, $will_force_match_all);
            } // unset($_key, $_value); // Housekeeping.
            return $value;
        }
        $url_uri_qsl = (string) $value; // Force string.

        if (!isset($url_uri_qsl[0])) {
            return ''; // Not possible.
        } elseif (!($parts = $this->c::parseUrl($url_uri_qsl))) {
            return ''; // Not possible.
        }
        $uri_no_fragment     = $parts['uri_no_fragment'] ?? '';
        $uri_no_fragment_lts = $uri_no_fragment ? $this->c::mbTrim($uri_no_fragment, '/') : $uri_no_fragment;
        $uri_pattern         = $uri_no_fragment_lts ? $this->bracketSpecialChars($uri_no_fragment_lts, $will_force_match_all) : $uri_no_fragment_lts;

        if (!isset($uri_pattern[0])) { // Treat as root URI.
            return '{/**,}'; // Any path [/endpoints] & query string.
            //
        } elseif (mb_strpos($uri_pattern, '[?]') !== false) {
            $uri_pattern        = preg_replace('/(^|[^\/])\[\?\]/u', '${1}{/**,}[?]', $uri_pattern);
            $uri_pattern        = preg_replace('/&/u', '{&**&,&}', $this->c::mbRTrim($uri_pattern, '&'));
            $uri_pattern        = preg_replace('/\[\?\]/u', '[?]{**&,}', $uri_pattern); // After `&` replacements.

            if (mb_strpos($uri_pattern, '{/**,}') === 0) {
                return $uri_pattern = $uri_pattern.'{&**,}'; // Any path [/endpoints] w/ the query string.
            } else {
                return $uri_pattern = '/'.$uri_pattern.'{&**,}'; // Specific path [/endpoints] w/ the query string.
            }
        } else {
            return $uri_pattern = '/'.$uri_pattern.'{/**,}'; // Specific path [/endpoints] & query 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\Base;

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

/**
 * Size which describes a google map size.
 *
 * @see http://code.google.com/apis/maps/documentation/javascript/reference.html#Size
 * @author GeLo <geloen.eric@gmail.com>
 */
class Size extends AbstractJavascriptVariableAsset
{
    /** @var double */
    protected $width;

    /** @var double */
    protected $height;

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

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

    /**
     * Create a size.
     *
     * @param double $width      The width.
     * @param double $height     The height.
     * @param string $widthUnit  The width unit.
     * @param string $heightUnit The height unit.
     */
    public function __construct($width = 1, $height = 1, $widthUnit = null, $heightUnit = null)
    {
        $this->setPrefixJavascriptVariable('size_');

        $this->setWidth($width);
        $this->setHeight($height);

        $this->setWidthUnit($widthUnit);
        $this->setHeightUnit($heightUnit);
    }

    /**
     * Gets the width size.
     *
     * @return double The width size.
     */
    public function getWidth()
    {
        return $this->width;
    }

    /**
     * Sets the width size.
     *
     * @param double $width The width size.
     *
     * @throws \Ivory\GoogleMap\Exception\BaseException If the width is not valid.
     */
    public function setWidth($width)
    {
        if (!is_numeric($width)) {
            throw BaseException::invalidSizeWidth();
        }

        $this->width = $width;
    }

    /**
     * Gets the height size
     *
     * @return double The height size.
     */
    public function getHeight()
    {
        return $this->height;
    }

    /**
     * Sets the height size.
     *
     * @param double $height The height size.
     *
     * @throws \Ivory\GoogleMap\Exception\BaseException If the height is not valid.
     */
    public function setHeight($height)
    {
        if (!is_numeric($height)) {
            throw BaseException::invalidSizeHeight();
        }

        $this->height = $height;
    }

    /**
     * Checks if the size has units.
     *
     * @return boolean TRUE if the size has units else FALSE.
     */
    public function hasUnits()
    {
        return ($this->widthUnit !== null) && ($this->heightUnit !== null);
    }

    /**
     * Gets the width unit size.
     *
     * @return string The width unit size.
     */
    public function getWidthUnit()
    {
        return $this->widthUnit;
    }

    /**
     * Sets the width unit size.
     *
     * @param string $widthUnit The width unit size.
     *
     * @throws \Ivory\GoogleMap\Exception\BaseException If the width unit is not valid.
     */
    public function setWidthUnit($widthUnit)
    {
        if (!is_string($widthUnit) && ($widthUnit !== null)) {
            throw BaseException::invalidSizeWidthUnit();
        }

        $this->widthUnit = $widthUnit;
    }

    /**
     * Gets the height unit size.
     *
     * @return string The height unit size.
     */
    public function getHeightUnit()
    {
        return $this->heightUnit;
    }

    /**
     * Sets the height unit size.
     *
     * @param string $heightUnit The height unit size.
     *
     * @throws \Ivory\GoogleMap\Exception\BaseException If the height unit is not valid.
     */
    public function setHeightUnit($heightUnit)
    {
        if (!is_string($heightUnit) && ($heightUnit !== null)) {
            throw BaseException::invalidSizeHeightUnit();
        }

        $this->heightUnit = $heightUnit;
    }
}


<?php
/**
 * Defuse encryption.
 *
 * @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 Defuse\Crypto\Key;
use Defuse\Crypto\Crypto;

/**
 * Defuse encryption.
 *
 * @since 170309.60830 Defuse.
 */
class Defuse extends Classes\Core\Base\Core
{
    /**
     * Defuse keygen.
     *
     * @since 170309.60830 Defuse.
     *
     * @return string Defuse key.
     * @note Keys begin w/ `def00000`.
     */
    public function keygen(): string
    {
        try { // Catch Defuse exceptions.
            if (!($key = Key::createNewRandomKey()->saveToAsciiSafeString())) {
                throw new Exception('Defuse keygen failure.');
            }
        } catch (\Throwable $Exception) {
            throw $this->c::issue($Exception->getMessage());
        }
        return $key;
    }

    /**
     * Defuse encryption.
     *
     * @since 170309.60830 Defuse.
     *
     * @param string $string String.
     * @param string $key    Encryption key.
     * @param bool   $raw    Return raw binary?
     *
     * @return string Raw binary or hex (default).
     */
    public function encrypt(string $string, string $key = '', bool $raw = false): string
    {
        if (!$key && !($key = $this->App->Config->©encryption['©key'])) {
            throw $this->c::issue('Missing encryption key.');
        }
        if (mb_strpos($key, 'def00000') !== 0) {
            return $this->c::rjEncrypt($string, $key);
        } // @TODO Remove; only for back compat.

        try { // Catch Defuse exceptions.
            $Key              = Key::loadFromAsciiSafeString($key);
            return $encrypted = Crypto::encrypt($string, $Key, $raw);
        } catch (\Throwable $Exception) {
            throw $this->c::issue($Exception->getMessage());
        }
    }

    /**
     * Defuse decryption.
     *
     * @since 170309.60830 Defuse.
     *
     * @param string $string String.
     * @param string $key    Decryption key.
     * @param bool   $raw    Is raw binary?
     *
     * @return string Decrypted string.
     */
    public function decrypt(string $string, string $key = '', bool $raw = false): string
    {
        if (!$key && !($key = $this->App->Config->©encryption['©key'])) {
            throw $this->c::issue('Missing decryption key.');
        }
        if (mb_strpos($key, 'def00000') !== 0) {
            return $this->c::rjDecrypt($string, $key);
        } // @TODO Remove; only for back compat.

        try { // Catch Defuse exceptions.
            $Key              = Key::loadFromAsciiSafeString($key);
            return $decrypted = Crypto::decrypt($string, $Key, $raw);
        } catch (\Throwable $Exception) {
            return ''; // Soft failure.
        }
    }
}


<?php
/**
 * Copyright (C) php-fn. See LICENSE file for license details.
 */

namespace fn\Map;

use Countable;
use fn;
use Iterator;
use RecursiveIterator;

/**
 * Consolidates implementation of SPL array_* functions
 */
class Tree implements RecursiveIterator, Countable
{
    /**
     * @var callable[]
     */
    protected $mappers;

    private $inner;
    private $needsRewind = true;
    private $needsNext = true;
    private $needsMap = true;
    private $currentValid;
    private $currentKey;
    private $currentValue;
    private $children;

    /**
     * @param iterable|\Traversable $inner
     * @param callable ...$mappers
     */
    public function __construct($inner, callable ...$mappers)
    {
        $this->inner   = $inner;
        $this->mappers = $mappers;
    }

    /**
     * @inheritdoc
     */
    public function count(): int
    {
        return iterator_count($this);
    }

    /**
     * @return Inner
     */
    public function getInnerIterator(): Inner
    {
        if (!$this->inner instanceof Inner) {
            $this->inner = new Inner($this->inner);
        }
        return $this->inner;
    }

    /**
     * @return RecursiveIterator
     */
    private function getChildrenIterator(): ?RecursiveIterator
    {
        if ($this->mappers && $this->doMap() && $this->children) {
            if (fn\isCallable($this->children)) {
                $this->children = call_user_func($this->children, $this->current(), $this->key(), $this);
            }
            if (!$this->children instanceof RecursiveIterator) {
                $this->children = new static($this->children);
            }
            return $this->children;
        }
        return null;
    }

    /**
     * @return bool
     */
    public function isLast(): bool
    {
        return $this->getInnerIterator()->isLast();
    }

    /**
     * @return bool
     */
    private function doMap(): bool
    {
        static $break, $null;
        if (!$break) {
            $break = fn\mapBreak();
            $null = fn\mapNull();
        }
        if ($this->currentValid === $break) {
            return false;
        }
        $this->needsRewind && $this->rewind();

        if (!$this->needsMap) {
            return $this->currentValid;
        }

        $iter = $this->getInnerIterator();
        while (true) {
            if (!$this->validateInner($iter)) {
                return false;
            }
            if ($this->needsNext) {
                $this->needsNext = false;
                $this->needsMap = true;
                $iter->next();
                if (!$this->validateInner($iter)) {
                    return false;
                }
            }
            if (!$this->needsMap) {
                continue;
            }
            $this->needsMap = false;

            $value = $curValue = $iter->current();
            $curKey = $iter->key();

            foreach ($this->mappers as $mapper) {
                $curValue = call_user_func_array($mapper, [$curValue === $null ? null : $curValue, &$curKey, $this]);
                if ($curValue === null) {
                    $this->needsNext = true;
                    continue 2;
                }
                if ($curValue === $break) {
                    $this->validateInner($iter);
                    $this->currentValid = $break;
                    return false;
                }

                if ($curValue instanceof Value) {
                    /**
                     * if there is at least one group instruction, we have to traverse the inner iterator completely
                     * from the current position (inclusive)
                     * @todo in this case the remaining children information is lost, fix it ASAP
                     */
                    if ($curValue->group) {
                        $iter = $this->inner = new Inner(fn\traverse($iter, $mapper, false));
                        $iter->rewind();
                        $this->needsMap = true;
                        $this->mappers = [function() {
                            return new Value;
                        }];
                        continue;
                    }
                    // @todo only children of the last value are handled
                    $this->children = $curValue->children;

                    $curKey = $curValue->key ?? $curKey;
                    $curValue = $curValue->value ?? $value;

                }

            }
            $this->currentKey = $curKey;
            $this->currentValue = $curValue === $null ? null : $curValue;            break;
        }
        return true;
    }

    /**
     * @inheritdoc
     */
    public function rewind(): void
    {
        if ($this->mappers) {
            $this->needsRewind = false;
            $this->needsNext = false;
            $this->needsMap = true;

            $this->currentValid = null;
            $this->currentKey = null;
            $this->currentValue = null;
            $this->children = null;

        }
        $this->getInnerIterator()->rewind();
    }

    /**
     * @param Iterator $inner
     *
     * @return bool
     */
    private function validateInner(Iterator $inner): bool
    {
        if (!($this->currentValid = $inner->valid())) {
            $this->currentKey = null;
            $this->currentValue = null;
            $this->children = null;
        }
        return $this->currentValid;
    }

    /**
     * @inheritdoc
     */
    public function current()
    {
        if ($this->mappers) {
            return $this->doMap() ? $this->currentValue : null;
        }
        return $this->getInnerIterator()->current();
    }

    /**
     * @inheritdoc
     */
    public function key()
    {
        if ($this->mappers) {
            return $this->doMap() ? $this->currentKey : null;
        }
        return $this->getInnerIterator()->key();
    }

    /**
     * @inheritdoc
     */
    public function hasChildren(): bool
    {
        if ($this->getChildrenIterator()) {
            return true;
        }
        $in = $this->getInnerIterator();
        return $in->valid() && is_iterable($in->current());
    }

    /**
     * @inheritdoc
     */
    public function getChildren(): RecursiveIterator
    {
        if ($childrenIterator = $this->getChildrenIterator()) {
            return $childrenIterator;
        }
        $in = $this->getInnerIterator();
        if ($in->valid() && is_iterable($current = $in->current())) {
            return new static($current);
        }
        static $empty;
        if (!$empty) {
            $empty = new static([]);
        }
        return $empty;
    }

    /**
     * @inheritdoc
     */
    public function next(): void
    {
        if ($this->mappers) {
            $this->needsNext = false;
            $this->needsMap = true;
        }
        $this->getInnerIterator()->next();
    }

    /**
     * @inheritdoc
     */
    public function valid(): bool
    {
        if ($this->mappers) {
            return $this->doMap();
        }
        return $this->getInnerIterator()->valid();
    }

    /**
     * @param callable $mapper
     *
     * @return static|\Traversable
     */
    public function recursive(callable $mapper = null): self
    {
        return fn\_\recursive($this, false, $mapper);
    }

    /**
     * @param callable $mapper
     *
     * @return static|\Traversable
     */
    public function flatten(callable $mapper = null): self
    {
        return fn\_\recursive($this, true, $mapper);
    }
}


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

/**
 * Sha256 utilities.
 *
 * @since 150424 Initial release.
 */
class Sha256 extends Classes\Core\Base\Core
{
    /**
     * Is a SHA-256 hash?
     *
     * @since 17xxxx SHA-1 utils.
     *
     * @param mixed $value Value.
     *
     * @return bool True if a SHA-256 hash.
     */
    public function is($value): bool
    {
        if (!$value) {
            return false;
        } elseif (!is_string($value)) {
            return false;
        } elseif (strlen($value) !== 64) {
            return false;
        } elseif (!ctype_xdigit($value)) {
            return false;
        }
        return true;
    }

    /**
     * Generates a keyed SHA-256 signature.
     *
     * @since 150424 Initial release.
     *
     * @param string $string String to sign.
     * @param string $key    Encryption key.
     *
     * @return string SHA-256 signature string (64 bytes).
     */
    public function keyedHash(string $string, string $key = ''): string
    {
        if (!$key && !($key = $this->App->Config->©hash['©key'])) {
            throw $this->c::issue('Missing HMAC hash key.');
        }
        return hash_hmac('sha256', $string, $key);
    }
}


<?php
/**
 * URL query 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 query utilities.
 *
 * @since 150424 Initial release.
 */
class UrlQuery extends Classes\Core\Base\Core implements Interfaces\UrlConstants
{
    /**
     * Default.
     *
     * @since 150424
     *
     * @type string
     */
    const DEFAULT_SIG_VAR = '_sig';

    /**
     * Stripe query string.
     *
     * @since 150424 Initial release.
     *
     * @param string $url_uri_qsl Input URL, URI, or query string w/ a leading `?`.
     *
     * @return string Input `$url_uri_qsl` without a query string.
     */
    public function strip(string $url_uri_qsl): string
    {
        if (!$qs_url_uri) {
            return $qs_url_uri; // Possible `0`.
        }
        if (mb_strpos($url_uri_qsl, '?') !== false) {
            $url_uri_qsl = mb_strstr($url_uri_qsl, '?', true);
        }
        return $url_uri_qsl;
    }

    /**
     * Array from query vars.
     *
     * @since 150424 Initial release.
     *
     * @param string $qs_url_uri A query string (w/ or w/o a leading `?`), a URL, or URI.
     *
     * @return array An array of args; based on the `$qs_url_uri` value.
     */
    public function parse(string $qs_url_uri): array
    {
        $qs = $this->string($qs_url_uri);

        parse_str($qs, $args); // Possible `0`.
        // Note that `0` becomes `[0 => '']`.

        return $args;
    }

    /**
     * Build a URL-encoded query string.
     *
     * @since 150424 Initial release.
     *
     * @param array       $args           Input array of query args.
     * @param null|string $numeric_prefix Defaults to an empty string.
     * @param string      $arg_separator  Defaults to `&`. Empty = INI `arg_separator.output`.
     * @param int         $enc_type       Defaults to {@link PHP_QUERY_RFC1738} indicating `urlencode()`.
     *                                    Or, {@link PHP_QUERY_RFC3986} indicating `rawurlencode()`.
     *
     * @return string A (possibly raw) URL-encoded query string (w/o a leading `?`).
     */
    public function build(array $args, string $numeric_prefix = '', string $arg_separator = '&', int $enc_type = PHP_QUERY_RFC1738): string
    {
        if (!isset($arg_separator[0])) {
            $arg_separator = ini_get('arg_separator.output');
        }
        $query = http_build_query($args, $numeric_prefix, $arg_separator, $enc_type);
        $query = $this->c::mbTrim(str_replace('=&', '&', $query), '=&');

        return $query;
    }

    /**
     * Add query arg(s) to a URL.
     *
     * @since 150424 Initial release.
     *
     * @param array  $new_args    Query args to add (not URL-encoded).
     * @param string $url_uri_qsl Input URL, URI, or query string w/ a leading `?`.
     *
     * @return string The `$url_uri_qsl` w/ new query arg(s).
     */
    public function addArgs(array $new_args, string $url_uri_qsl): string
    {
        $url_uri_qsl = $this->c::parseUrl($url_uri_qsl);
        $args        = []; // Initialize.

        if (isset($url_uri_qsl['query'][0])) {
            $args = $this->parse($url_uri_qsl['query']);
        }
        $args                 = array_merge($args, $new_args);
        $url_uri_qsl['query'] = $this->build($args);
        $url_uri_qsl          = $this->c::unparseUrl($url_uri_qsl);

        return $url_uri_qsl;
    }

    /**
     * Remove query arg(s) from a URL.
     *
     * @since 150424 Initial release.
     *
     * @param array  $arg_keys    Query args to remove (keys only).
     * @param string $url_uri_qsl Input URL, URI, or query string w/ a leading `?`.
     *
     * @return string The `$url_uri_qsl` w/o query arg(s).
     */
    public function removeArgs(array $arg_keys, string $url_uri_qsl): string
    {
        $url_uri_qsl = $this->c::parseUrl($url_uri_qsl);
        $args        = []; // Initialize.

        if (isset($url_uri_qsl['query'][0])) {
            $args = $this->parse($url_uri_qsl['query']);
        }
        $arg_keys             = array_fill_keys($arg_keys, null);
        $args                 = array_diff_key($args, $arg_keys);
        $url_uri_qsl['query'] = $this->build($args);
        $url_uri_qsl          = $this->c::unparseUrl($url_uri_qsl);

        return $url_uri_qsl;
    }

    /**
     * Adds a signature.
     *
     * @since 150424 Initial release.
     *
     * @param string $url_uri_qsl Input URL, URI, or query string w/ a leading `?`.
     * @param string $key         Encryption key (optional).
     * @param string $sig_var     Defaults to `sig`.
     *
     * @return string The `$url_uri_qsl` w/ a signature var.
     */
    public function addSha256Sig(string $url_uri_qsl, string $key = '', string $sig_var = ''): string
    {
        $sig_var     = $sig_var ?: $this::DEFAULT_SIG_VAR;
        $sig         = $this->sha256Sig($url_uri_qsl, $key, $sig_var);
        $url_uri_qsl = $this->addArgs([$sig_var => $sig], $url_uri_qsl);

        return $url_uri_qsl;
    }

    /**
     * Removes a signature.
     *
     * @since 150424 Initial release.
     *
     * @param string $url_uri_qsl Input URL, URI, or query string w/ a leading `?`.
     * @param string $sig_var     Defaults to `sig`.
     *
     * @return string The `$url_uri_qsl` w/o a signature.
     */
    public function removeSha256Sig(string $url_uri_qsl, string $sig_var = ''): string
    {
        $sig_var     = $sig_var ?: $this::DEFAULT_SIG_VAR;
        $url_uri_qsl = $this->removeArgs([$sig_var], $url_uri_qsl);

        return $url_uri_qsl;
    }

    /**
     * Checks a signature.
     *
     * @since 150424 Initial release.
     *
     * @param string $qs_url_uri A query string (w/ or w/o a leading `?`), a URL, or URI.
     * @param string $key        Encryption key (optional).
     * @param string $sig_var    Defaults to `sig`.
     *
     * @return bool True if a valid signature exists.
     */
    public function sha256SigOk(string $qs_url_uri, string $key = '', string $sig_var = ''): bool
    {
        $sig_var = $sig_var ?: $this::DEFAULT_SIG_VAR;
        $args    = $this->parse($qs_url_uri);
        $sig     = $this->sha256Sig($qs_url_uri, $key, $sig_var);

        return !empty($args[$sig_var]) && $args[$sig_var] === $sig;
    }

    /**
     * Builds a keyed SHA-256 signature.
     *
     * @since 150424 Initial release.
     *
     * @param string $qs_url_uri A query string (w/ or w/o a leading `?`), a URL, or URI.
     * @param string $key        Encryption key (optional).
     * @param string $sig_var    Defaults to `sig`.
     *
     * @return string SHA-256 signature string.
     */
    protected function sha256Sig(string $qs_url_uri, string $key = '', string $sig_var = ''): string
    {
        if (!$key && !($key = $this->App->Config->©urls['©sig_key'])) {
            throw $this->c::issue('Missing URL signature key.');
        }
        $sig_var = $sig_var ?: $this::DEFAULT_SIG_VAR;
        $args    = $this->parse($qs_url_uri);
        unset($args[$sig_var]); // Exclude.

        $args            = $this->c::mbTrim($args);
        $args            = $this->c::sortByKey($args);
        $serialized_args = serialize($args); // After sorting by key.
        $sig             = $this->c::sha256KeyedHash($serialized_args, $key);

        return $sig; // Signature.
    }

    /**
     * Acquire/sanitize query string.
     *
     * @since 150424 Improving query strings.
     *
     * @param string $qs_url_uri A query string (w/ or w/o a leading `?`), a URL, or URI.
     *
     * @return string Query string (no leading `?`); w/ normalized ampersands.
     */
    protected function string(string $qs_url_uri): string
    {
        if (!$qs_url_uri) {
            return $qs_url_uri; // Possible `0`.
        }
        $qs_url_uri = $this->c::normalizeUrlAmps($qs_url_uri);

        if (mb_strpos($qs_url_uri, '?') !== false) {
            list(, $qs) = explode('?', $qs_url_uri, 2);
        } elseif ($qs_url_uri[0] === '/' || preg_match('/^'.$this::URL_REGEX_FRAG_SCHEME.'/u', $qs_url_uri)) {
            $qs = ''; // There is no query string in this case.
        } else {
            $qs = $qs_url_uri; // Assume it's a query string.
        }
        return $qs = $this->c::mbTrim($qs, '', '?=&');
    }
}


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

use Ivory\GoogleMap\Assets\AbstractJavascriptVariableAsset;
use Ivory\GoogleMap\Exception\OverlayException;

/**
 * Marker shape which describes a google map marker shape.
 *
 * @see http://code.google.com/apis/maps/documentation/javascript/reference.html#MarkerShape
 * @author GeLo <geloen.eric@gmail.com>
 */
class MarkerShape extends AbstractJavascriptVariableAsset
{
    /** @var string */
    protected $type;

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

    /**
     * Creates a marker shape.
     *
     * @param string $type        The marker shape type.
     * @param array  $coordinates The marker shape coordinates.
     */
    public function __construct($type = 'poly', array $coordinates = array(1, 1, 1, -1, -1, -1, -1, 1))
    {
        $this->setPrefixJavascriptVariable('marker_shape_');
        $this->setType($type);
        $this->setCoordinates($coordinates);
    }

    /**
     * Gets the marker shape type.
     *
     * @return string The marker sape type.
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Sets the marker shape type.
     *
     * The allowing marker shape type are : circle, poly & rect.
     *
     * @param string $type The marker schape type.
     *
     * @throws \Ivory\GoogleMap\Exception\OverlayException If the type is not valid.
     */
    public function setType($type)
    {
        switch (strtolower($type)) {
            case 'circle':
            case 'poly':
            case 'rect':
                $this->type = $type;
                break;
            default:
                throw OverlayException::invalidMarkerShapeType();
        }
    }

    /**
     * Resets the marker shape coordinates.
     */
    public function resetCoordinates()
    {
        $this->coordinates = array();
    }

    /**
     * Cheks if the marker shape has coordinates
     *
     * @return boolean TRUE if the marker shape has coordinates else FALSE.
     */
    public function hasCoordinates()
    {
        return !empty($this->coordinates);
    }

    /**
     * Gets the marker shape coordinates.
     *
     * @return array The marker shape coordinates.
     */
    public function getCoordinates()
    {
        return $this->coordinates;
    }

    /**
     * Sets the marker shape coordinates.
     *
     * @param array $coordinates The marker shape coordinates.
     *
     * @throws \Ivory\GoogleMap\Exception\OverlayException If the coordinates are not valid according to the type.
     */
    public function setCoordinates(array $coordinates)
    {
        switch (strtolower($this->type)) {
            case 'circle':
                if ((count($coordinates) === 3)
                    && is_numeric($coordinates[0])
                    && is_numeric($coordinates[1])
                    && is_numeric($coordinates[2])
                ) {
                    $this->coordinates = $coordinates;
                } else {
                    throw OverlayException::invalidMarkerShapeCircleCoordinates();
                }
                break;
            case 'poly':
                if ((count($coordinates) <= 0) || ((count($coordinates) % 2) !== 0)) {
                    throw OverlayException::invalidMarkerShapePolyCoordinates();
                }

                foreach ($coordinates as $coordinate) {
                    if (!is_numeric($coordinate)) {
                        throw OverlayException::invalidMarkerShapePolyCoordinates();
                    }
                }

                $this->coordinates = $coordinates;
                break;
            case 'rect':
                if ((count($coordinates) === 4)
                    && is_numeric($coordinates[0])
                    && is_numeric($coordinates[1])
                    && is_numeric($coordinates[2])
                    && is_numeric($coordinates[3])
                ) {
                    $this->coordinates = $coordinates;
                } else {
                    throw OverlayException::invalidMarkerShapeRectCoordinates();
                }
                break;
        }
    }

    /**
     * Adds a coordinate to the marker shape if the type is poly.
     *
     * @param integer $x The X coordinate.
     * @param integer $y The Y coordinate.
     *
     * @throws \Ivory\GoogleMap\Exception\OverlayException If the type is not poly or if the poly coordinate is not
     *                                                     valid.
     */
    public function addPolyCoordinate($x, $y)
    {
        if ($this->type !== 'poly') {
            throw OverlayException::invalidMarkerShapeAddPolyCoordinateCall();
        }

        if (!is_numeric($x) || !is_numeric($y)) {
            throw OverlayException::invalidMarkerShapePolyCoordinate();
        }

        $this->coordinates[] = $x;
        $this->coordinates[] = $y;
    }
}


<?php
/**
 * Stripe 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 Stripe\Token;
use Stripe\Customer;
use Stripe\Charge;
use Stripe\Plan;
use Stripe\Subscription;
#
use Stripe\Error\Base as StripeError;
use Stripe\Error\Card as StripeCardError;
use Stripe\Error\RateLimit as StripeRateLimitError;
use Stripe\Error\InvalidRequest as StripeInvalidRequestError;
use Stripe\Error\Authentication as StripeAuthenticationError;
use Stripe\Error\ApiConnection as StripeApiConnectionError;
use Stripe\Error\Permission as StripePermissionError;

/**
 * Stripe utilities.
 *
 * @since 170329.13807 Stripe utils.
 */
class Stripe extends Classes\Core\Base\Core
{
    /**
     * Stripe API version.
     *
     * @since 170329.13807 Stripe utils.
     *
     * @type string Stripe API version.
     */
    const API_VERSION = '2017-02-14';

    /**
     * Class constructor.
     *
     * @since 170329.13807 Stripe utils.
     *
     * @param Classes\App $App Instance of App.
     */
    public function __construct(Classes\App $App)
    {
        parent::__construct($App);
    }

    /**
     * Get, update, or create customer.
     *
     * @since 170329.13807 Stripe utils.
     *
     * @param string|array $args ID or args.
     *
     * @return Customer|Error Customer on success.
     */
    public function customer($args)
    {
        if (is_string($args)) {
            $args = ['customer' => $args];
        } // Convert customer ID into args.

        $default_args = [
            'api_key'  => '', // Stripe API key.
            'customer' => '', // Existing customer ID.

            // Everything else is for a new customer, or for an update.
            // i.e., If `update_existing=true`, customer is updated to this info.
            // ~ Anything that is not passed in will not be changed in any way.
            'update_existing' => false, // Update if exists?

            'ip'    => '', // New customer IP address.
            'email' => '', // New customer email address.

            'fname' => '', // New customer first name.
            'lname' => '', // New customer last name.

            'source'            => '', // New customer source.
            'is_default_source' => true, // Is the default source?
            'description'       => '', // New customer description.

            'user_id'  => '', // New customer user ID.
            'metadata' => '', // New customer metadata.
        ];
        $args = (array) $args; // Force array.
        $args = array_merge($default_args, $args);
        $args = array_intersect_key($args, $default_args);
        extract($this->parseArgs(__FUNCTION__, $args));

        if (!$args['description']) { // Full name.
            $args['description'] = $this->c::mbTrim($args['fname'].' '.$args['lname']);
        }
        $Customer = null; // Initialize.

        if ($args['customer']) {
            try { // Acquire customer by ID.
                $Customer = Customer::retrieve($args['customer'], $opts);
            } catch (\Throwable $Exception) {
                return $this->exceptionError($Exception);
            }
            if ($Customer instanceof Customer && $args['update_existing']) {
                $Customer = $this->updateCustomer($args, $Customer);
            }
        } else { // Create a new customer.
            try {
                $new_customer_args = [
                    'email'       => $args['email'] ?: null,
                    'source'      => $args['source'] ?: null,
                    'description' => $args['description'] ?: null,

                    'metadata' => array_merge([
                        'ip'    => $args['ip'],
                        'email' => $args['email'],

                        'fname' => $args['fname'],
                        'lname' => $args['lname'],

                        'user_id' => $args['user_id'],
                    ], $args['metadata']),
                ];
                $new_customer_args = $this->c::removeNulls($new_customer_args);
                $Customer          = Customer::create($new_customer_args, $opts);
            } catch (\Throwable $Exception) {
                return $this->exceptionError($Exception);
            }
        }
        if (!($Customer instanceof Customer)) {
            $_error = sprintf('Failed to acquire customer.');
            return $this->exceptionError(new Exception($_error));
        }
        return $Customer;
    }

    /**
     * Update existing customer.
     *
     * @since 170329.13807 Stripe utils.
     *
     * @param array         $args     Arguments.
     * @param Customer|null $Customer Instance (optional).
     *
     * @return Customer|Error Customer on success.
     */
    public function updateCustomer(array $args, Customer $Customer = null)
    {
        $default_args = [
            'api_key'  => '', // Stripe API key.
            'customer' => '', // Existing customer ID.

            // Everything else is optional (for update).
            // Anything not passed in will not be changed.

            'ip'    => '', // New IP address for customer.
            'email' => '', // New email address for customer.

            'fname' => '', // New first name for customer.
            'lname' => '', // New last name for customer.

            'source'            => '', // New source for customer.
            'is_default_source' => true, // Is the default source?
            'description'       => '', // New description for customer.

            'user_id'  => '', // New user ID for customer.
            'metadata' => '', // New metadata for customer.
        ];
        $args = (array) $args; // Force array.
        $args = array_merge($default_args, $args);
        $args = array_intersect_key($args, $default_args);
        extract($this->parseArgs(__FUNCTION__, $args));

        if (!$Customer && $args['customer']) {
            $Customer = $this->customer($args['customer']);
        }
        if (!($Customer instanceof Customer)) {
            $_error = sprintf('Failed to acquire customer for update.');
            return $this->exceptionError(new Exception($_error));
        }
        try { // Update.
            if ($args['email']) {
                $Customer->email = $args['email'];
            }
            if ($args['source']) {
                $Customer->source = $args['source'];

                if ($args['is_default_source']) {
                    $Customer->default_source = $args['source'];
                } // Update customer's default source.
            }
            if ($args['description']) {
                $Customer->description = $args['description'];
            }
            if ($args['ip']) {
                $Customer->metadata->ip = $args['ip'];
            }
            if ($args['email']) {
                $Customer->metadata->email = $args['email'];
            }
            if ($args['fname']) {
                $Customer->metadata->fname = $args['fname'];
            }
            if ($args['lname']) {
                $Customer->metadata->lname = $args['lname'];
            }
            if ($args['user_id']) {
                $Customer->metadata->user_id = $args['user_id'];
            }
            if ($args['metadata']) {
                foreach ($args['metadata'] as $_key => $_value) {
                    $Customer->metadata->{$_key} = $_value;
                } // unset($_key, $_value);
            }
            $Customer->save();
        } catch (\Throwable $Exception) {
            return $this->exceptionError($Exception);
        }
        return $Customer;
    }

    /**
     * Get (or create) a charge.
     *
     * @since 170329.13807 Stripe utils.
     *
     * @param string|array $args ID or args.
     *
     * @return Charge|Error Charge on success.
     */
    public function charge($args)
    {
        if (is_string($args)) {
            $args = ['charge' => $args];
        } // Convert charge ID into args.

        $default_args = [
            'api_key' => '', // Stripe API key.
            'charge'  => '', // Get existing ID.

            // Everything else is for a new charge.
            // A new charge requires a customer ID.
            'customer'      => '', // Customer ID to charge.
            'receipt_email' => '', // Force email receipt w/ this.
            // i.e., An email receipt is sent regardless of account-level setting.

            'amount'      => .50, // New charge amount.
            'currency'    => 'USD', // New charge currency.
            'description' => '', // New charge description.

            'order_id' => '', // New charge order ID.
            'metadata' => '', // New charge metadata.
        ];
        $args = (array) $args; // Force array.
        $args = array_merge($default_args, $args);
        $args = array_intersect_key($args, $default_args);
        extract($this->parseArgs(__FUNCTION__, $args));

        $Charge = null; // Initialize.

        if ($args['charge']) {
            try { // Acquire charge by ID.
                $Charge = Charge::retrieve($args['charge'], $opts);
            } catch (\Throwable $Exception) {
                return $this->exceptionError($Exception);
            }
        } else { // Create a new charge.
            try {
                $new_charge_args = [
                    'customer'      => $args['customer'],
                    'receipt_email' => $args['receipt_email'] ?: null,

                    'amount'      => $args['amount'],
                    'currency'    => $args['currency'],
                    'description' => $args['description'] ?: null,

                    'metadata' => array_merge([
                        'order_id' => $args['order_id'],
                    ], $args['metadata']),
                ];
                $new_charge_args = $this->c::removeNulls($new_charge_args);
                $Charge          = Charge::create($new_charge_args, $opts);
            } catch (\Throwable $Exception) {
                return $this->exceptionError($Exception);
            }
        }
        if (!($Charge instanceof Charge)) {
            $_error = sprintf('Failed to acquire charge.');
            return $this->exceptionError(new Exception($_error));
        }
        return $Charge;
    }

    /**
     * Get (or create) a plan.
     *
     * @since 170329.13807 Stripe utils.
     *
     * @param string|array $args ID or args.
     *
     * @return Plan|Error Plan on success.
     */
    public function plan($args)
    {
        if (is_string($args)) {
            $args = ['plan' => $args];
        } // Convert plan ID into args.

        $default_args = [
            'api_key' => '', // Stripe API key.
            'plan'    => '', // Get existing ID.

            // Everything else is for a new plan.

            'trial_period_days' => 0, // New plan trial.
            'interval'          => '', // New plan interval.
            // One of these: `day`, `week`, `month` or `year`.
            'interval_count' => '', // New plan interval count.
            // The number of intervals between each subscription billing.
            // For example, interval=month and interval_count=3, bills every 3 months.
            // Maximum of one year interval allowed (1 year, 12 months, 52 weeks, 365 days).

            'amount'   => .50, // New plan amount.
            'currency' => 'USD', // New plan currency.
            'name'     => '', // New plan name/description.

            'metadata' => '', // New charge metadata.
        ];
        $args = (array) $args; // Force array.
        $args = array_merge($default_args, $args);
        $args = array_intersect_key($args, $default_args);
        extract($this->parseArgs(__FUNCTION__, $args));

        $Plan = null; // Initialize.

        if ($args['plan']) {
            try { // Acquire plan by ID.
                $Plan = Plan::retrieve($args['plan'], $opts);
            } catch (\Throwable $Exception) {
                return $this->exceptionError($Exception);
            }
        } else { // Maybe create a new plan.
            $hash = 'pln_'.md5(serialize($args));

            try { // Check for existing plan.
                $Plan = Plan::retrieve($hash, $opts);
            } catch (\Throwable $Exception) {
                $Plan = null; // Soft failure.
            }
            if (!($Plan instanceof Plan)) {
                try { // New plan.
                    $new_plan_args = [
                        'id' => $hash, // Auto-generated ID.

                        'trial_period_days' => $args['trial_period_days'] ?: null,
                        'interval'          => $args['interval'],
                        'interval_count'    => $args['interval_count'],

                        'amount'   => $args['amount'],
                        'currency' => $args['currency'],
                        'name'     => $args['name'] ?: __('Subscription'),

                        'metadata' => $args['metadata'],
                    ];
                    $new_plan_args = $this->c::removeNulls($new_plan_args);
                    $Plan          = Plan::create($new_plan_args, $opts);
                } catch (\Throwable $Exception) {
                    return $this->exceptionError($Exception);
                }
            }
        }
        if (!($Plan instanceof Plan)) {
            $_error = sprintf('Failed to acquire plan.');
            return $this->exceptionError(new Exception($_error));
        }
        return $Plan;
    }

    /**
     * Get (or create) a subscription.
     *
     * @since 170329.13807 Stripe utils.
     *
     * @param string|array $args ID or args.
     *
     * @return Subscription|Error Subscription on success.
     */
    public function subscription($args)
    {
        if (is_string($args)) {
            $args = ['subscription' => $args];
        } // Convert subscription ID into args.

        $default_args = [
            'api_key'       => '', // Stripe API key.
            'subscription'  => '', // Get existing ID.

            // Everything else is for a new subscription.
            // A new subscription requires a customer ID + plan.

            'customer' => '', // Customer ID to subscribe.
            'plan'     => '', // Plan ID to subscribe them to.

            'order_id' => '', // New subscription order ID.
            'metadata' => '', // New subscription metadata.
        ];
        $args = (array) $args; // Force array.
        $args = array_merge($default_args, $args);
        $args = array_intersect_key($args, $default_args);
        extract($this->parseArgs(__FUNCTION__, $args));

        $Subscription = null; // Initialize.

        if ($args['subscription']) {
            try { // Acquire subscription by ID.
                $Subscription = Subscription::retrieve($args['subscription'], $opts);
            } catch (\Throwable $Exception) {
                return $this->exceptionError($Exception);
            }
        } else { // Create a new subscription.
            try {
                $new_subscription_args = [
                    'customer' => $args['customer'],
                    'plan'     => $args['plan'],

                    'metadata' => array_merge([
                        'order_id' => $args['order_id'],
                    ], $args['metadata']),
                ];
                $new_subscription_args = $this->c::removeNulls($new_subscription_args);
                $Subscription          = Subscription::create($new_subscription_args, $opts);
            } catch (\Throwable $Exception) {
                return $this->exceptionError($Exception);
            }
        }
        if (!($Subscription instanceof Subscription)) {
            $_error = sprintf('Failed to acquire subscription.');
            return $this->exceptionError(new Exception($_error));
        }
        return $Subscription;
    }

    /**
     * JS token as HTML markup.
     *
     * @since 170329.13807 Stripe utils.
     *
     * @param \StdClass $token JS token.
     * @param array     $args  Behavioral args.
     *
     * @return string HTML markup representation.
     */
    public function jsTokenMarkup(\StdClass $token, array $args = []): string
    {
        if (empty($token->id) || !is_string($token->id)) {
            throw $this->c::issue(vars(), 'Token missing `id` property.');
        } elseif (empty($token->type) || !is_string($token->type)) {
            throw $this->c::issue(vars(), 'Token missing `type` property.');
        }
        if ($token->type === 'card' && !empty($token->card->brand) && is_string($token->card->brand)
                && !empty($token->card->last4) && is_string($token->card->last4)
            ) {
            if ($token->card->brand === 'Visa') {
                $_icon = '';
            } elseif ($token->card->brand === 'MasterCard') {
                $_icon = '';
            } elseif ($token->card->brand === 'American Express') {
                $_icon = '';
            } elseif ($token->card->brand === 'Discover') {
                $_icon = '';
            } elseif ($token->card->brand === 'JCB') {
                $_icon = '';
            } elseif ($token->card->brand === 'Diners Club') {
                $_icon = '';
            } else { // Just indicate that it's a credit card.
                $_icon = '';
            }
            $markup = $_icon.' <tt data-token="'.$this->c::escAttr(json_encode($token)).'">•••• •••• •••• '.$this->c::escHtml($token->card->last4).'</tt>';
            //
        } elseif ($token->type === 'alipay_account' && !empty($token->alipay_account->username) && is_string($token->alipay_account->username)) {
            $markup = ' <tt data-token="'.$this->c::escAttr(json_encode($token)).'">'.$this->c::escHtml($token->alipay_account->username).'</tt>';
            //
        } elseif ($token->type === 'bank_account' && !empty($token->bank_account->last4) && is_string($token->bank_account->last4)) {
            $markup = ' <tt data-token="'.$this->c::escAttr(json_encode($token)).'">…••••'.$this->c::escHtml($token->bank_account->last4).'</tt>';
            //
        } elseif ($token->type === 'source_bitcoin' && !empty($token->bitcoin->address) && is_string($token->bitcoin->address)) {
            $markup = ' <tt data-token="'.$this->c::escAttr(json_encode($token)).'">'.$this->c::escHtml($token->bitcoin->address).'</tt>';
            //
        } else { // Generic default display using Stripe icon.
            $markup = ' <tt data-token="'.$this->c::escAttr(json_encode($token)).'">'.$this->c::escHtml($token->id).'</tt>';
        }
        return $markup;
    }

    /**
     * Smallest currency unit.
     *
     * @since 170329.13807 Stripe utils.
     *
     * @param float  $amount   Amount.
     * @param string $currency Currency code.
     *
     * @return int Amount represented as an integer (always).
     *
     * @see Units explained here <http://jas.xyz/2meM1pO>
     */
    protected function unitAmount(float $amount, string $currency): int
    {
        switch (mb_strtoupper($currency)) {
            case 'BIF':
            case 'DJF':
            case 'JPY':
            case 'KRW':
            case 'PYG':
            case 'VND':
            case 'XAF':
            case 'XPF':
            case 'CLP':
            case 'GNF':
            case 'KMF':
            case 'MGA':
            case 'RWF':
            case 'VUV':
            case 'XOF':
                return (int) $amount;

            default: // In cents.
                return (int) ($amount * 100);
        }
    }

    /**
     * Exception to error.
     *
     * @since 170329.13807 Stripe utils.
     *
     * @param \Throwable $Exception Exception
     *
     * @return Classes\Core\Error An error object instance.
     */
    protected function exceptionError(\Throwable $Exception): Classes\Core\Error
    {
        if ($Exception instanceof StripeCardError) {
            $json  = (object) $Exception->getJsonBody();
            $error = (object) ($json->error ?? []);

            $code    = (string) ($error->code ?? 'error');
            $message = (string) ($error->message ?? '');

            if ($code && $message) { // If Stripe has given details.
                return $this->c::error('stripe-'.$code, $message, $json);
            }
        }
        if ($this->App->Config->©debug['©log']) {
            $this->c::review(vars(), 'Stripe error.', __METHOD__.'#stripe');
        }
        return $this->c::error('stripe-error', __('Processing error, please try again.'), $Exception->getMessage());
    }

    /**
     * Argument parser.
     *
     * @since 170329.13807 Stripe utils.
     *
     * @param string $func Function.
     * @param array  $args Request args.
     *
     * @return array ['args' => [], 'opts' => []]
     */
    protected function parseArgs(string $func, array $args): array
    {
        $default_api_key = $this->App->Config->©stripe['©secret_key'];
        $api_key         = (string) ($args['api_key'] ?? '');
        $api_key         = $api_key ?: $default_api_key;
        unset($args['api_key']); // Ditch this now.

        // NOTE: The number `500` in clipping below comes
        // from a limit imposed by the Stripe API for metadata.

        foreach ($args as $_key => &$_value) {
            if ($_key === 'amount') {
                $_value    = (float) $_value;
                $_currency = (string) ($args['currency'] ?? '');
                $_currency = mb_substr(mb_strtoupper($_currency), 0, 3);
                $_value    = $this->unitAmount($_value, $_currency);
                //
            } elseif ($_key === 'currency') {
                $_value = mb_substr(mb_strtoupper((string) $_value), 0, 3);
                //
            } elseif ($_key === 'metadata') {
                if (is_object($_value)) {
                    $_value = (array) $_value;
                } elseif (!is_array($_value)) {
                    $_value = [];
                } // Forces an array value.

                foreach ($_value as $__key => &$__value) {
                    $__value = $this->c::clip((string) $__value, 500);
                } // Must unset value in sub-loop by reference.
                unset($__key, $__value); // Housekeeping.
                //
            } elseif (in_array($_key, ['update_existing', 'is_default_source'], true)) {
                $_value = (bool) $_value; // Booleans.
                //
            } elseif (in_array($_key, ['trial_period_days', 'interval_count'], true)) {
                $_value = (int) $_value; // Integers.
                //
            } else {  // Everything else becomes a string.
                $_value = (string) $_value; // Force string value.
                if (!in_array($_key, ['customer', 'charge', 'subscription', 'plan', 'source', 'interval', 'currency'], true)) {
                    $_value = $this->c::clip($_value, 500); // Max of 500 chars.
                }
            }
        } // Must unset value by reference.
        unset($_key, $_value, $_currency); // Housekeeping.

        return $data = [
            'args' => $args,
            'opts' => [
                'api_key'        => $api_key,
                'stripe_version' => $this::API_VERSION,
            ],
        ];
    }
}


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

/**
 * Backtrace utilities.
 *
 * @since 170211.63148 Debugging utilities.
 */
class Backtrace extends Classes\Core\Base\Core
{
    /**
     * Get backtrace callers.
     *
     * @since 170211.63148 Debugging utilities.
     *
     * @param int|null $at        Starting at position.
     * @param bool     $all       Include magic middle-men?
     * @param int      $___offset For internal use only.
     *
     * @return array Backtrace callers.
     */
    public function callers(int $at = null, bool $all = false, $___offset = 0): array
    {
        $callers = []; // Initialize.

        // `[0]` is the call to this function.
        // So we start searching from offset index `1`.
        // An additional `$___offset` location is added to this.
        // And then an additional `$at` location can be added also.

        // @TODO Use the limit parameter to `debug_backtrace()` as a way
        // of further optimizing this routine. No need to pull everything at all times.

        if (!($backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS))) {
            return $callers; // Not possible; unexpected trace.
        }
        for ($_i = 1 + $___offset + $at; $_i < count($backtrace); ++$_i) {
            $_caller   = $backtrace[$_i];
            $_function = $_caller['function'] ?? '';
            $_class    = $_caller['class'] ?? '';
            $_type     = $_caller['type'] ?? '';

            if (!$all) { // If not `$all`, exclude magicals, facades, and other middle-men.
                if ($_class && mb_strpos($_function, '__') === 0 && isset($_function[2]) && $_function[2] !== '_') {
                    continue; // Bypass magic (middle-man) methods in classes.
                    //
                } elseif ($_class && $_type === '::' && mb_stripos($_class, '\\Facades') !== false) {
                    continue; // Bypass facades that sit in the middle also.
                    //
                } elseif ($_class && mb_strtolower($_class) === 'wp_hook') {
                    continue; // Bypass everything in `WP_Hook` class.
                    //
                } elseif (!$_class) { // A few others.
                    $_lc_function = mb_strtolower($_function);

                    if (in_array($_lc_function, ['call_user_func', 'call_user_func_array'], true)) {
                        continue; // Bypass these also.
                    } elseif (in_array($_lc_function, ['do_action', 'apply_filters'], true)) {
                        continue; // Bypass these also.
                    }
                } // This covers WordPress and the WP Sharks Core also.
            }
            $callers[] = ($_class && $_type ? $_class.$_type : '').$_function;
            // e.g., `a->method`, `b::method`, `function`, `__`, etc.
        } // unset($_caller, $_function, $_lc_function, $_class, $_type); // Housekeeping.

        return $callers;
    }

    /**
     * Has backtrace callers?
     *
     * @since 170211.63148 Debugging utilities.
     *
     * @param string|array $caller    Caller(s).
     * @param int|null     $at        Starting at position.
     * @param bool         $all       Include magic middle-men?
     * @param int          $___offset For internal use only.
     *
     * @return bool True if has backtrace caller(s).
     *
     * @note If `$caller` is an array, the order of the given array matters.
     * i.e., Callers must exist in a matching order as given by the `$caller` param.
     *
     * @note If `$at` is set (even if `0`), then position matters too.
     * i.e., Caller(s) must exist starting at the given position.
     * Conversely, if `$at` is not set, the position does not matter.
     */
    public function hasCaller($caller, int $at = null, bool $all = false, $___offset = 0): bool
    {
        if (!$caller) {
            return false;
        }
        $caller  = (array) $caller;
        $caller  = array_map('strval', $caller);
        $callers = $this->callers($at, $all, 1 + $___offset);

        $caller  = '.'.implode('.', $caller).'.';
        $callers = '.'.implode('.', $callers).'.';

        if (isset($at)) { // Position matters.
            return mb_stripos($callers, $caller) === 0;
        }
        return mb_stripos($callers, $caller) !== false;
    }
}


<?php
/**
 * OAuth scope repository.
 *
 * @author @jaswrks
 * @copyright WebSharks™
 */
declare(strict_types=1);
namespace WebSharks\Core\Classes\Core\OAuth\Server\Repositories;

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 League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\ScopeEntityInterface;
#
use WebSharks\Core\Classes\Core\OAuth\Server\Entities\ClientEntity;
use WebSharks\Core\Classes\Core\OAuth\Server\Entities\ScopeEntity;
#
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;

/**
 * OAuth scope repository.
 *
 * @since 17xxxx
 */
abstract class ScopeRepository extends Repository implements ScopeRepositoryInterface
{
    /**
     * Hard-coded scopes array.
     *
     * @since 17xxxx OAuth scope repository.
     *
     * @type array Hard-coded scopes array.
     */
    const SCOPES = [];

    /**
     * Get scope entity.
     *
     * @since 17xxxx OAuth scope repository.
     *
     * @param string $identifier Identifier.
     *
     * @return ScopeEntity|null Scope entity.
     */
    public function getScopeEntityByIdentifier($identifier)
    {
        if (!empty($this::SCOPES[$identifier])) {
            return $this->App->Di->get(ScopeEntity::class, ['identifier' => $identifier]);
        }
    }

    /**
     * Given a client, grant type and optional user identifier,
     * validate the set of scopes requested are valid and optionally
     * append additional scopes or remove requested scopes.
     *
     * @since 17xxxx OAuth scope repository.
     *
     * @param ScopeEntityInterface[] $scopes          Scopes.
     * @param string                 $grant_type      Grant type.
     * @param ClientEntityInterface  $ClientEntity    Client entity.
     * @param string|null            $user_identifier User identifier.
     *
     * @return ScopeEntityInterface[] Scope entities.
     */
    public function finalizeScopes(array $scopes, $grant_type, ClientEntityInterface $ClientEntity, $user_identifier = null)
    {
        return $scopes; // No filters at this time.
    }
}


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

use Ivory\GoogleMap\Controls\ScaleControl;
use Ivory\GoogleMap\Helper\AbstractHelper;

/**
 * Scale control helper.
 *
 * @author GeLo <geloen.eric@gmail.com>
 */
class ScaleControlHelper extends AbstractHelper
{
    /** @var \Ivory\GoogleMap\Helper\Controls\ControlPositionHelper */
    protected $controlPositionHelper;

    /** @var \Ivory\GoogleMap\Helper\Controls\ScaleControleStyleHelper */
    protected $scaleControlStyleHelper;

    /**
     * Construct a scale control helper.
     *
     * @param \Ivory\GoogleMap\Helper\Controls\ControlPositionHelper    $controlPositionHelper   The control position
     *                                                                                helper.
     * @param \Ivory\GoogleMap\Helper\Controls\ScaleControleStyleHelper $scaleControlStyleHelper The scale control
     *                                                                                style helper.
     */
    public function __construct(
        ControlPositionHelper $controlPositionHelper = null,
        ScaleControlStyleHelper $scaleControlStyleHelper = null
    ) {
        parent::__construct();

        if ($controlPositionHelper === null) {
            $controlPositionHelper = new ControlPositionHelper();
        }

        if ($scaleControlStyleHelper === null) {
            $scaleControlStyleHelper = new ScaleControlStyleHelper();
        }

        $this->setControlPositionHelper($controlPositionHelper);
        $this->setScaleControlStyleHelper($scaleControlStyleHelper);
    }

    /**
     * Gets the control position helper.
     *
     * @return \Ivory\GoogleMap\Helper\Controls\ControlPositionHelper The control position helper.
     */
    public function getControlPositionHelper()
    {
        return $this->controlPositionHelper;
    }

    /**
     * Sets the control position helper.
     *
     * @param \Ivory\GoogleMap\Helper\Controls\ControlPositionHelper $controlPositionHelper The control position helper.
     */
    public function setControlPositionHelper(ControlPositionHelper $controlPositionHelper)
    {
        $this->controlPositionHelper = $controlPositionHelper;
    }

    /**
     * Gets the scale control style helper.
     *
     * @return \Ivory\GoogleMap\Helper\Controls\ScaleControlStyleHelper The scale control style helper.
     */
    public function getScaleControlStyleHelper()
    {
        return $this->scaleControlStyleHelper;
    }

    /**
     * Sets the scale control style helper.
     *
     * @param \Ivory\GoogleMap\Helper\Controls\ScaleControlStyleHelper $scaleControlStyleHelper The scale control style
     *                                                                                helper.
     */
    public function setScaleControlStyleHelper(ScaleControlStyleHelper $scaleControlStyleHelper)
    {
        $this->scaleControlStyleHelper = $scaleControlStyleHelper;
    }

    /**
     * Renders a scale control.
     *
     * @param \Ivory\GoogleMap\Controls\ScaleControl $scaleControl The scale control.
     *
     * @return string The JS output.
     */
    public function render(ScaleControl $scaleControl)
    {
        return $this->jsonBuilder
            ->reset()
            ->setValue('[position]', $this->controlPositionHelper->render($scaleControl->getControlPosition()), false)
            ->setValue('[style]', $this->scaleControlStyleHelper->render($scaleControl->getScaleControlStyle()), false)
            ->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\Services\Geocoding;

use Geocoder\HttpAdapter\HttpAdapterInterface;
use Geocoder\Provider\AbstractProvider;
use Geocoder\Provider\ProviderInterface;
use Ivory\GoogleMap\Base\Bound;
use Ivory\GoogleMap\Base\Coordinate;
use Ivory\GoogleMap\Exception\GeocodingException;
use Ivory\GoogleMap\Services\BusinessAccount;
use Ivory\GoogleMap\Services\Geocoding\Result\GeocoderAddressComponent;
use Ivory\GoogleMap\Services\Geocoding\Result\GeocoderGeometry;
use Ivory\GoogleMap\Services\Geocoding\Result\GeocoderResponse;
use Ivory\GoogleMap\Services\Geocoding\Result\GeocoderResult;
use Ivory\GoogleMap\Services\Utils\XmlParser;

/**
 * Geocoder provider.
 *
 * @author GeLo <geloen.eric@gmail.com>
 */
class GeocoderProvider extends AbstractProvider implements ProviderInterface
{
    /** @var string */
    protected $url;

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

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

    /** @var \Ivory\GoogleMap\Services\Utils\XmlParser */
    protected $xmlParser;

    /** @var \Ivory\GoogleMap\Services\BusinessAccount */
    protected $businessAccount;

    /**
     * {@inheritdoc}
     */
    public function __construct(HttpAdapterInterface $adapter, $locale = null)
    {
        parent::__construct($adapter, $locale);

        $this->setUrl('http://maps.googleapis.com/maps/api/geocode');
        $this->setHttps(false);
        $this->setFormat('json');
        $this->setXmlParser(new XmlParser());
    }

    /**
     * Gets the service url according to the https flag.
     *
     * @return string The service url.
     */
    public function getUrl()
    {
        if ($this->isHttps()) {
            return str_replace('http://', 'https://', $this->url);
        }

        return $this->url;
    }

    /**
     * Sets the service url.
     *
     * @param string $url The service url.
     *
     * @throws \Ivory\GoogleMap\Exception\GeocodingException If the url is not valid.
     */
    public function setUrl($url)
    {
        if (!is_string($url)) {
            throw GeocodingException::invalidGeocoderProviderUrl();
        }

        $this->url = $url;
    }

    /**
     * Checks if the service uses HTTPS.
     *
     * @return boolean TRUE if the service uses HTTPS else FALSE.
     */
    public function isHttps()
    {
        return $this->https;
    }

    /**
     * Sets the service HTTPS flag.
     *
     * @param boolean $https TRUE if the service uses HTTPS else FALSE.
     *
     * @throws \Ivory\GoogleMap\Exception\GeocodingException If the https flag is not valid.
     */
    public function setHttps($https)
    {
        if (!is_bool($https)) {
            throw GeocodingException::invalidGeocoderProviderHttps();
        }

        $this->https = $https;
    }

    /**
     * Gets the service format.
     *
     * @return string The service format.
     */
    public function getFormat()
    {
        return $this->format;
    }

    /**
     * Sets the service format.
     *
     * @param string $format The service format.
     *
     * @throws \Ivory\GoogleMap\Exception\GeocodingException If the format is not valid.
     */
    public function setFormat($format)
    {
        if (($format !== 'json') && ($format !== 'xml')) {
            throw GeocodingException::invalidGeocoderProviderFormat();
        }

        $this->format = $format;
    }

    /**
     * Gets the xml parser.
     *
     * @return \Ivory\GoogleMap\Services\Utils\XmlParser The xml parser.
     */
    public function getXmlParser()
    {
        return $this->xmlParser;
    }

    /**
     * Sets the xml parser.
     *
     * @param \Ivory\GoogleMap\Services\Geocoding\XmlParser $xmlParser The xml parser.
     */
    public function setXmlParser(XmlParser $xmlParser)
    {
        $this->xmlParser = $xmlParser;
    }

    /**
     * Checks if the geocoder provider has a business account.
     *
     * @return boolean TRUE if the geocoder provider has a business account else FALSE.
     */
    public function hasBusinessAccount()
    {
        return $this->businessAccount !== null;
    }

    /**
     * Gets the business account.
     *
     * @return \Ivory\GoogleMap\Services\BusinessAccount The business account.
     */
    public function getBusinessAccount()
    {
        return $this->businessAccount;
    }

    /**
     * Sets the business account.
     *
     * @param \Ivory\GoogleMap\Services\BusinessAccount $businessAccount The business account.
     */
    public function setBusinessAccount(BusinessAccount $businessAccount = null)
    {
        $this->businessAccount = $businessAccount;
    }

    /**
     * {@inheritdoc}
     *
     * @throws \Ivory\GoogleMap\Exception\GeocodingException If the request is not valid.
     */
    public function getGeocodedData($request)
    {
        if (is_string($request)) {
            $geocoderRequest = new GeocoderRequest();
            $geocoderRequest->setAddress($request);
        } elseif ($request instanceof GeocoderRequest) {
            $geocoderRequest = $request;
        } else {
            throw GeocodingException::invalidGeocoderProviderRequestArguments();
        }

        if (!$geocoderRequest->isValid()) {
            throw GeocodingException::invalidGeocoderProviderRequest();
        }

        $url = $this->generateUrl($geocoderRequest);
        $response = $this->getAdapter()->getContent($url);

        if ($response === null) {
            throw GeocodingException::invalidServiceResult();
        }

        $normalizedResponse = $this->parse($response);

        return $this->buildGeocoderResponse($normalizedResponse);
    }

    /**
     * {@inheritdoc}
     */
    public function getReversedData(array $coordinates)
    {
        $request = new GeocoderRequest();
        $request->setCoordinate($coordinates[0], $coordinates[1]);

        return $this->getGeocodedData($request);
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'ivory_google_map';
    }

    /**
     * Generates geocoding URL according to the request.
     *
     * @param \Ivory\GoogleMap\Services\Geocoding\GeocoderRequest $geocoderRequest The geocoder request.
     *
     * @return string The generated URL.
     */
    protected function generateUrl(GeocoderRequest $geocoderRequest)
    {
        $httpQuery = array();

        if ($geocoderRequest->hasAddress()) {
            $httpQuery['address'] = $geocoderRequest->getAddress();
        } else {
            $httpQuery['latlng'] = sprintf(
                '%s,%s',
                $geocoderRequest->getCoordinate()->getLatitude(),
                $geocoderRequest->getCoordinate()->getLongitude()
            );
        }

        if ($geocoderRequest->hasBound()) {
            $httpQuery['bound'] = sprintf(
                '%s,%s|%s,%s',
                $geocoderRequest->getBound()->getSouthWest()->getLatitude(),
                $geocoderRequest->getBound()->getSouthWest()->getLongitude(),
                $geocoderRequest->getBound()->getNorthEast()->getLatitude(),
                $geocoderRequest->getBound()->getNorthEast()->getLongitude()
            );
        }

        if ($geocoderRequest->hasRegion()) {
            $httpQuery['region'] = $geocoderRequest->getRegion();
        }

        if ($geocoderRequest->hasLanguage()) {
            $httpQuery['language'] = $geocoderRequest->getLanguage();
        }

        $httpQuery['sensor'] = $geocoderRequest->hasSensor() ? 'true' : 'false';

        $url = sprintf('%s/%s?%s', $this->getUrl(), $this->getFormat(), http_build_query($httpQuery));

        return $this->signUrl($url);
    }

    /**
     * Sign an url for business account.
     *
     * @param string $url The url.
     *
     * @return string The signed url.
     */
    protected function signUrl($url)
    {
        if (!$this->hasBusinessAccount()) {
            return $url;
        }

        return $this->businessAccount->signUrl($url);
    }

    /**
     * Parses & normalizes the geocoding result response.
     *
     * @param string $response The response.
     *
     * @return \stdClass The parsed & normalized response.
     */
    protected function parse($response)
    {
        if ($this->format == 'json') {
            return $this->parseJSON($response);
        }

        return $this->parseXML($response);
    }

    /**
     * Parses & normalizes a JSON geocoding result response.
     *
     * @param string $response The response.
     *
     * @return \stdClass The parsed & normalized response.
     */
    protected function parseJSON($response)
    {
        return json_decode($response);
    }

    /**
     * Parses & normalizes an XML geocoding result response.
     *
     * @param string $response The response.
     *
     * @return \stdClass The parsed & normalized response.
     */
    protected function parseXML($response)
    {
        $rules = array(
            'address_component' => 'address_components',
            'type'              => 'types',
            'result'            => 'results',
        );

        return $this->xmlParser->parse($response, $rules);
    }

    /**
     * Builds the geocoder results accordint to a normalized geocoding results.
     *
     * @param \stdClass $geocoderResponse The normalized geocder response.
     *
     * @return \Ivory\GoogleMap\Services\Geocoding\Result\GeocoderResponse The builded geocoder response.
     */
    protected function buildGeocoderResponse(\stdClass $geocoderResponse)
    {
        $results = array();
        foreach ($geocoderResponse->results as $geocoderResult) {
            $results[] = $this->buildGeocoderResult($geocoderResult);
        }

        $status = $geocoderResponse->status;

        return new GeocoderResponse($results, $status);
    }

    /**
     * Builds a geocoder result according to a normalized geocoding result.
     *
     * @param \stdClass $geocoderResult The normalized geocoder result.
     *
     * @return \Ivory\GoogleMap\Services\Geocoding\Result\GeocoderResult The builded geocoder result.
     */
    protected function buildGeocoderResult(\stdClass $geocoderResult)
    {
        $addressComponents = $this->buildGeocoderAddressComponents($geocoderResult->address_components);
        $formattedAddress = $geocoderResult->formatted_address;
        $geometry = $this->buildGeocoderGeometry($geocoderResult->geometry);
        $types = $geocoderResult->types;
        $partialMatch = isset($geocoderResult->partial_match) ? $geocoderResult->partial_match : null;

        return new GeocoderResult($addressComponents, $formattedAddress, $geometry, $types, $partialMatch);
    }

    /**
     * Builds the gecoder address components according to a normalized geocoding address components.
     *
     * @param array $geocoderAddressComponents The normalized geocoder address components.
     *
     * @return array The builded geocoder address components.
     */
    protected function buildGeocoderAddressComponents(array $geocoderAddressComponents)
    {
        $results = array();

        foreach ($geocoderAddressComponents as $geocoderAddressComponent) {
            $results[] = $this->buildGeocoderAddressComponent($geocoderAddressComponent);
        }

        return $results;
    }

    /**
     * Builds a geocoder address component according to a normalized geocoding address component.
     *
     * @param \stdClass $geocoderAddressComponent The normalized geocoder address component.
     *
     * @return \Ivory\GoogleMap\Services\Geocoding\Result\GeocoderAddressComponent The builded geocoder address
     *                                                                             component.
     */
    protected function buildGeocoderAddressComponent(\stdClass $geocoderAddressComponent)
    {
        $longName = $geocoderAddressComponent->long_name;
        $shortName = $geocoderAddressComponent->short_name;
        $types = $geocoderAddressComponent->types;

        return new GeocoderAddressComponent($longName, $shortName, $types);
    }

    /**
     * Builds a geocoder geometry according to a normalized geocoding geometry.
     *
     * @param \stdClass $geocoderGeometry The normalized geocoder geomety.
     *
     * @return \Ivory\GoogleMap\Services\Geocoding\Result\GeocoderGeometry The builded geocoder geometry.
     */
    protected function buildGeocoderGeometry(\stdClass $geocoderGeometry)
    {
        $location = new Coordinate(
            $geocoderGeometry->location->lat,
            $geocoderGeometry->location->lng
        );

        $locationType = $geocoderGeometry->location_type;

        $viewport = new Bound(
            new Coordinate($geocoderGeometry->viewport->southwest->lat, $geocoderGeometry->viewport->southwest->lng),
            new Coordinate($geocoderGeometry->viewport->northeast->lat, $geocoderGeometry->viewport->northeast->lng)
        );

        $bound = null;
        if (isset($geocoderGeometry->bounds)) {
            $bound = new Bound(
                new Coordinate($geocoderGeometry->bounds->southwest->lat, $geocoderGeometry->bounds->southwest->lng),
                new Coordinate($geocoderGeometry->bounds->northeast->lat, $geocoderGeometry->bounds->northeast->lng)
            );
        }

        return new GeocoderGeometry($location, $locationType, $viewport, $bound);
    }
}