<?php
/*
 * Copyright (c) 2016 @trashtoy
 * https://github.com/trashtoy/
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
 * Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/**
 * PHP class file.
 * @auhtor trashtoy
 * @since  2.2.0
 */
namespace Peach\Markup;

use InvalidArgumentException;
use Peach\Util\Values;

/**
 * HTML の出力に特化した Helper です.
 * XML 宣言, DOCTYPE 宣言, select 要素やコメントの出力機能などを備えています.
 *
 * このクラスは通常コンストラクタではなく newInstance() メソッドを使って初期化を行います.
 * フォーマットのきめ細やかなカスタマイズを行いたい場合のみコンストラクタから生成してください.
 */
class HtmlHelper extends AbstractHelper
{

    /**
     * HTML 4.01 Strict のモードです.
     *
     * @var string
     */
    const MODE_HTML4_STRICT        = "html4s";

    /**
     * HTML 4.01 Transitional のモードです.
     *
     * @var string
     */
    const MODE_HTML4_TRANSITIONAL  = "html4t";

    /**
     * XHTML 1.0 Strict のモードです.
     *
     * @var string
     */
    const MODE_XHTML1_STRICT       = "xhtml1s";

    /**
     * XHTML 1.0 Transitional のモードです.
     *
     * @var string
     */
    const MODE_XHTML1_TRANSITIONAL = "xhtml1t";

    /**
     * XHTML 1.1 のモードです.
     *
     * @var string
     */
    const MODE_XHTML1_1            = "xhtml1_1";

    /**
     * HTML5 のモードです.
     *
     * @var string
     */
    const MODE_HTML5               = "html5";

    /**
     * このオブジェクトが出力する文書型宣言のコードです.
     *
     * @var string
     */
    private $docType;

    /**
     * このオブジェクトが XHTML 形式かどうかをあらわします.
     * xmlDec() メソッドの返り値に影響します.
     *
     * @var bool
     */
    private $isXhtml;

    /**
     * 指定された Helper オブジェクトを利用して HTML タグの出力を行う, 新しい
     * HtmlHelper オブジェクトを構築します.
     *
     * 第 2 引数の文字列は docType() メソッドの返り値に関係します.
     * もしも未指定の場合, このオブジェクトが docType() メソッドで生成する
     * Component は何も出力しません.
     * 第 3 引数のフラグは xmlDec() メソッドの返り値に関係します.
     * true の場合は xmlDec() が返す Component は XML 宣言を出力しますが,
     * それ以外の場合は何も出力しません.
     *
     * @param Helper $parent  カスタマイズ対象の Helper オブジェクト
     * @param string $docType この Helper が生成する文書型宣言の文字列
     * @param bool   $isXhtml XHTML として生成する場合のみ true
     */
    public function __construct(Helper $parent, $docType = null, $isXhtml = null)
    {
        parent::__construct($parent);
        $this->docType = $docType;
        $this->isXhtml = $isXhtml;
    }

    /**
     * 指定されたモードで HtmlHelper オブジェクトを生成します.
     * 引数には以下の定数を指定してください.
     *
     * - {@link HtmlHelper::MODE_HTML4_STRICT}
     * - {@link HtmlHelper::MODE_HTML4_TRANSITIONAL}
     * - {@link HtmlHelper::MODE_XHTML1_STRICT}
     * - {@link HtmlHelper::MODE_XHTML1_TRANSITIONAL}
     * - {@link HtmlHelper::MODE_XHTML1_1}
     * - {@link HtmlHelper::MODE_HTML5}
     *
     * @param  string $mode
     * @return HtmlHelper
     */
    public static function newInstance($mode = null)
    {
        $actualMode     = self::detectMode($mode);
        $baseHelper     = self::createBaseHelper($actualMode);
        $docType        = self::getDocTypeFromMode($actualMode);
        $isXhtml        = self::checkXhtmlFromMode($actualMode);
        return new self($baseHelper, $docType, $isXhtml);
    }

    /**
     * 指定されたモードに応じた BaseHelper を返します.
     *
     * @param  string $mode
     * @return BaseHelper
     */
    private static function createBaseHelper($mode)
    {
        $isXhtml        = self::checkXhtmlFromMode($mode);
        $builder        = self::createBuilder($isXhtml);
        $emptyNodeNames = self::getEmptyNodeNames();
        return new BaseHelper($builder, $emptyNodeNames);
    }

    /**
     * XML 宣言をあらわす Component を返します.
     * もしもこの HtmlHelper が XHTML モードで生成された場合,
     * このメソッドは以下のコードを出力する {@link Code} オブジェクトを返します.
     *
     * <?xml version="1.0" encoding="UTF-8"?>
     *
     *
     * それ以外は {@link None} オブジェクトを返します.
     *
     * @return Component XML 宣言をあらわす Code オブジェクトまたは None
     */
    public function xmlDec()
    {
        return $this->isXhtml ? new Code('<?xml version="1.0" encoding="UTF-8"?>') : None::getInstance();
    }

    /**
     * 文書型宣言をあらわす Code オブジェクトを返します.
     * 返り値の Component が出力するコードは, このオブジェクトの初期化時に指定されたモード
     * (あるいはコンストラクタの第 2 引数) に応じて異なる文書型宣言となります.
     *
     * @return Component 文書型宣言をあらわす Code オブジェクト. ただし初期化時に指定されていない場合は None
     */
    public function docType()
    {
        return strlen($this->docType) ? new Code($this->docType) : None::getInstance();
    }

    /**
     * 指定された内容のコメントノードを作成します.
     * 引数にノードを指定した場合, そのノードの内容をコメントアウトします.
     *
     * 第 2, 第 3 引数にコメントの接頭辞・接尾辞を含めることが出来ます.
     *
     * @param  string|Component $contents コメントにしたいテキストまたはノード
     * @param  string $prefix コメントの接頭辞
     * @param  string $suffix コメントの接尾辞
     * @return HelperObject
     */
    public function comment($contents = null, $prefix = "", $suffix = "")
    {
        $comment = new Comment($prefix, $suffix);
        return $this->tag($comment)->append($contents);
    }

    /**
     * IE 9 以前の Internet Explorer で採用されている条件付きコメントを生成します.
     * 以下にサンプルを挙げます.
     *
     * echo $htmlHelper->conditionalComment("lt IE 7", "He died on April 9, 2014.")->write();
     *
     * このコードは次の文字列を出力します.
     *
     *
     *
     * 第 2 引数を省略した場合は空の条件付きコメントを生成します.
     *
     * @param  string                        $cond     条件文 ("lt IE 7" など)
     * @param  string|Component $contents 条件付きコメントで囲みたいテキストまたはノード
     * @return HelperObject 条件付きコメントを表現する HelperObject
     */
    public function conditionalComment($cond, $contents = null)
    {
        return $this->comment($contents, "[if {$cond}]>", "<![endif]");
    }

    /**
     * HTML の select 要素を生成します.
     * 第 1 引数にはデフォルトで選択されている値,
     * 第 2 引数には選択肢を配列で指定します.
     * キーがラベル, 値がそのラベルに割り当てられたデータとなります.
     *
     * 引数を二次元配列にすることで, 一次元目のキーを optgroup にすることが出来ます.
     * 以下にサンプルを挙げます.
     *
     * $candidates = array(
     *     "Fruit"   => array(
     *         "Apple"  => 1,
     *         "Orange" => 2,
     *         "Pear"   => 3,
     *         "Peach"  => 4,
     *     ),
     *     "Dessert" => array(
     *         "Chocolate" => 5,
     *         "Doughnut"  => 6,
     *         "Ice cream" => 7,
     *     ),
     *     "Others" => 8,
     * );
     * $select = Html::createSelectElement("6", $candidates, array("class" => "sample", "name" => "favorite"));
     *
     * この要素を出力すると以下の結果が得られます.
     *
     * <select class="sample" name="favorite">
     *     <optgroup label="Fruit">
     *         <option value="1">Apple</option>
     *         <option value="2">Orange</option>
     *         <option value="3">Pear</option>
     *         <option value="4">Peach</option>
     *     </optgroup>
     *     <optgroup label="Dessert">
     *         <option value="5">Chocolate</option>
     *         <option value="6" selected>Doughnut</option>
     *         <option value="7">Ice cream</option>
     *     </optgroup>
     *     <option value="8">Others</option>
     * </select>
     *
     *
     * @param  string $current    デフォルト値
     * @param  array  $candidates 選択肢の一覧
     * @param  array  $attr       追加で指定する属性 (class, id, style など)
     * @return ContainerElement HTML の select 要素
     */
    public function createSelectElement($current, array $candidates, array $attr = array())
    {
        $currentText = Values::stringValue($current);
        $select      = new ContainerElement("select");
        $select->setAttributes($attr);
        $select->appendNode(self::createOptions($currentText, $candidates));
        return $select;
    }

    /**
     * select 要素に含まれる option の一覧を作成します.
     *
     * @param  string $current    デフォルト値
     * @param  array  $candidates 選択肢の一覧
     * @return NodeList option 要素の一覧
     */
    private function createOptions($current, array $candidates)
    {
        $result = new NodeList();
        foreach ($candidates as $key => $value) {
            if (is_array($value)) {
                $optgroup = new ContainerElement("optgroup");
                $optgroup->setAttribute("label", $key);
                $optgroup->appendNode($this->createOptions($current, $value));
                $result->appendNode($optgroup);
                continue;
            }

            $option  = new ContainerElement("option");
            $option->setAttribute("value", $value);
            $value   = Values::stringValue($value);
            if ($current === $value) {
                $option->setAttribute("selected");
            }
            $option->appendNode($key);
            $result->appendNode($option);
        }
        return $result;
    }

    /**
     * HTML の select 要素を生成し, 結果を HelperObject として返します.
     * 引数および処理内容は
     * {@link Html::createSelectElement()}
     * と全く同じですが, 生成された要素を HelperObject でラップするところが異なります.
     *
     * @see    HtmlHelper::createSelectElement
     * @param  string $current    デフォルト値
     * @param  array  $candidates 選択肢の一覧
     * @param  array  $attr       追加で指定する属性 (class, id, style など)
     * @return HelperObject
     */
    public function select($current, array $candidates, array $attr = array())
    {
        return $this->tag(self::createSelectElement($current, $candidates, $attr));
    }

    /**
     * newInstance() の引数で指定された文字列のバリデーションを行います.
     * 未対応の文字列が指定された場合は InvalidArgumentException をスローします.
     *
     * @param  string $param
     * @return string
     * @throws InvalidArgumentException
     */
    private static function detectMode($param)
    {
        static $mapping = null;
        if ($mapping === null) {
            $keys = array(
                self::MODE_HTML4_TRANSITIONAL,
                self::MODE_HTML4_STRICT,
                self::MODE_XHTML1_STRICT,
                self::MODE_XHTML1_TRANSITIONAL,
                self::MODE_XHTML1_1,
                self::MODE_HTML5,
            );
            $altMapping = array(
                ""      => self::MODE_HTML5,
                "html"  => self::MODE_HTML5,
                "html4" => self::MODE_HTML4_TRANSITIONAL,
                "xhtml" => self::MODE_XHTML1_TRANSITIONAL,
            );
            $mapping = array_merge(array_combine($keys, $keys), $altMapping);
        }

        $key = strtolower($param);
        if (array_key_exists($key, $mapping)) {
            return $mapping[$key];
        } else {
            throw new InvalidArgumentException("Invalid mode name: {$param}");
        }
    }

    /**
     * 指定されたモードが XHTML かどうかを判定します.
     *
     * @param  string $mode モード (ただし detectMode() でバリデーション済み)
     * @return bool         XHTML と判定された場合のみ true
     */
    private static function checkXhtmlFromMode($mode)
    {
        static $xhtmlModes = array(
            self::MODE_XHTML1_STRICT,
            self::MODE_XHTML1_TRANSITIONAL,
            self::MODE_XHTML1_1,
        );
        return in_array($mode, $xhtmlModes);
    }

    /**
     * 指定されたモードに応じた DOCTYPE 宣言の文字列を返します.
     *
     * @param  string $mode モード (ただし detectMode() でバリデーション済み)
     * @return string       DOCTYPE 宣言
     */
    private static function getDocTypeFromMode($mode)
    {
        static $docTypeList = array(
            self::MODE_HTML4_STRICT        => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
            self::MODE_HTML4_TRANSITIONAL  => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
            self::MODE_XHTML1_STRICT       => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
            self::MODE_XHTML1_TRANSITIONAL => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
            self::MODE_XHTML1_1            => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
            self::MODE_HTML5               => '<!DOCTYPE html>',
        );
        return $docTypeList[$mode];
    }

    /**
     * 新しい DefaultBuilder を生成します.
     * @param  bool $isXhtml XHTML 形式の場合は true, HTML 形式の場合は false
     * @return DefaultBuilder
     */
    private static function createBuilder($isXhtml = false)
    {
        // @codeCoverageIgnoreStart
        static $breakControl = null;
        if (!isset($breakControl)) {
            $breakControl = new NameBreakControl(
                array("html", "head", "body", "ul", "ol", "dl", "table"),
                array("pre", "code", "textarea")
            );
        }
        // @codeCoverageIgnoreEnd

        $renderer = $isXhtml ? XmlRenderer::getInstance() : SgmlRenderer::getInstance();
        $builder  = new DefaultBuilder();
        $builder->setBreakControl($breakControl);
        $builder->setRenderer($renderer);
        return $builder;
    }

    /**
     * HTML4, XHTML1, HTML5 にて定義されている空要素タグまたは Void elements
     * の一覧を返します.
     * 返り値の配列は, 以下に挙げる要素名をマージしたものとなります.
     *
     * Empty elements by XHTML1 (https://www.w3.org/TR/xhtml1/)
     *     - area, base, basefont, br, col, frame, hr, img, input, isindex, link, meta, param
     * Void elements by HTML5 (https://www.w3.org/TR/html5/syntax.html#void-elements)
     *     - area, base, br, col, embed, hr, img, input, keygen, link, meta, param, source, track, wbr
     *
     * @return array
     * @ignore
     */
    private static function getEmptyNodeNames()
    {
        static $emptyList = null;
        if ($emptyList === null) {
            $emptyList = array("area", "base", "basefont", "br", "col", "embed", "frame", "hr", "img", "input", "isindex", "keygen", "link", "meta", "param", "source", "track", "wbr");
        }
        return $emptyList;
    }
}


<?php
/*
 * Copyright (c) 2014 @trashtoy
 * https://github.com/trashtoy/
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
 * Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/**
 * PHP class file.
 * @auhtor trashtoy
 * @since  2.0.0
 */
namespace Peach\Util;

/**
 * デフォルトのコンパレータです.
 * {@link Arrays::sort} などのメソッドで,
 * 引数にコンパレータが指定されなかった場合に適用されます.
 */
class DefaultComparator implements Comparator
{
    /**
     * 二つの値を比較します.
     * $var1 === $var2 の場合は 0 を返します.
     *
     * @param mixed $var1
     * @param mixed $var2
     */
    public function compare($var1, $var2)
    {
        if ($var1 === $var2) {
            return 0;
        }
        if (is_object($var1) || is_object($var2)) {
            return self::compareObjects($var1, $var2);
        }
        if (is_array($var1) || is_array($var2)) {
            return self::compareArrays($var1, $var2);
        }
        if (is_string($var1) || is_string($var2)) {
            return strcmp($var1, $var2);
        }
        if ($var1 == $var2) {
            return self::compareTypes($var1, $var2);
        }

        return ($var1 > $var2) ? 1 : -1;
    }

    /**
     * var_dump の出力から, 冒頭の object(#x) 部分を除いた文字列を返します.
     *
     * @param  mixed  $var
     * @return string オブジェクトの文字列表現
     */
    private static function dump($var)
    {
        ob_start();
        var_dump($var);
        $data = ob_get_contents();
        ob_end_clean();

        $classNamePattern = "([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9\\\\_\\x7f-\\xff]*)";

        // xdebug の設定によって処理が変わるため, コードカバレージの対象外とします
        // @codeCoverageIgnoreStart
        if (preg_match("/^object\\({$classNamePattern}\\)#(\\d+)/", $data)) {
            return preg_replace("/^object\\({$classNamePattern}\\)#(\\d+)/", "$1", $data);
        }
        if (preg_match("/^class {$classNamePattern}#(\\d+)/", $data)) {
            return preg_replace("/^class {$classNamePattern}#(\\d+)/", "$1", $data);
        }
        return $data;
        // @codeCoverageIgnoreEnd
    }

    /**
     * 引数のオブジェクトを比較します.
     * 引数のうち, 少なくとも一方が {@link Comparable} を実装していた場合,
     * そのオブジェクトの compareTo の結果を返します.
     * それ以外の場合は var_dump の結果を文字列比較して大小を判定します.
     *
     * @param  mixed $var1
     * @param  mixed $var2
     * @return int
     */
    private static function compareObjects($var1, $var2)
    {
        if ($var1 instanceof Comparable) {
            return $var1->compareTo($var2);
        }
        if ($var2 instanceof Comparable) {
            return $var2->compareTo($var1) * (-1);
        }
        $str1 = self::dump($var1);
        $str2 = self::dump($var2);
        return strcmp($str1, $str2);
    }

    /**
     * 引数 $var1 と $var2 を比較し, 結果を 0, 1, -1 のいずれかで返します.
     *
     * @param  mixed $var1 比較対象の値
     * @param  mixed $var2 比較対象の値
     * @return int         引数 $var1 のほうが大きい場合は 1, $var2 のほうが大きい場合は -1, 等しい場合は 0
     */
    private static function compareArrays($var1, $var2)
    {
        if ($var1 == $var2) {
            return 0;
        }

        return ($var1 > $var2) ? 1 : -1;
    }

    /**
     * 異なる値 $var1, $var2 について,
     * 一方が NULL や FALSE などの値だった場合の大小関係を定義します.
     * この実装は NULL < FALSE < その他 という順序付けをします.
     *
     * @param  mixed $var1
     * @param  mixed $var2
     * @return int   比較結果が等しい場合は 0, $var1 > $var2 の場合は 1, それ以外は -1
     */
    private static function compareTypes($var1, $var2)
    {
        if ($var1 === null) {
            return -1;
        }
        if ($var2 === null) {
            return 1;
        }
        if (is_bool($var1)) {
            return -1;
        }
        if (is_bool($var2)) {
            return 1;
        }

        return 0;
    }

    /**
     * このクラスはインスタンス化できません.
     */
    private function __construct() {}

    /**
     * 唯一のインスタンスを返します.
     *
     * @return DefaultComparator
     * @codeCoverageIgnore
     */
    public static function getInstance()
    {
        static $instance = null;
        if (!isset($instance)) {
            $instance = new self();
        }
        return $instance;
    }
}


<?php
/**
 * AnimeDb package.
 *
 * @author    Peter Gribanov <info@peter-gribanov.ru>
 * @copyright Copyright (c) 2014, Peter Gribanov
 * @license   http://opensource.org/licenses/MIT
 */

namespace AnimeDb\Bundle\CacheTimeKeeperBundle\Service\CacheKeyBuilder;

use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;

class EtagHasher implements EtagHasherInterface
{
    /**
     * @var string
     */
    const ETAG_SEPARATOR = '|';

    /**
     * @var RequestStack
     */
    protected $request_stack;

    /**
     * @var string
     */
    protected $algorithm = '';

    /**
     * @param RequestStack $request_stack
     * @param string $algorithm
     */
    public function __construct(RequestStack $request_stack, $algorithm)
    {
        $this->request_stack = $request_stack;
        $this->algorithm = $algorithm;
    }

    /**
     * @param Response $response
     *
     * @return string
     */
    public function hash(Response $response)
    {
        $params = [
            $response->getLastModified()->format(\DateTime::ISO8601),
        ];

        // add cookies to ETag
        if ($this->request_stack->getMasterRequest()) {
            $params[] = http_build_query($this->request_stack->getMasterRequest()->cookies->all());
        }

        return hash($this->algorithm, implode(self::ETAG_SEPARATOR, $params));
    }
}


<?php
/*
 * Copyright (c) 2014 @trashtoy
 * https://github.com/trashtoy/
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
 * Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/**
 * PHP class file.
 * @auhtor trashtoy
 * @since  2.0.0
 */
namespace Peach\DT;
use Peach\Util\ArrayMap;

/**
 * 既存の時間オブジェクトを機能拡張するためのラッパークラスです.
 * このクラスは Decorator パターンで設計されています.
 */
class TimeWrapper implements Time
{
    /**
     * ラップする時間オブジェクトです.
     * @var Time
     */
    private $original;

    /**
     * 指定された時間オブジェクトをラップする TimeWrapper を構築します.
     *
     * @param Time $original ラップ対象の時間オブジェクト
     */
    public function __construct(Time $original)
    {
        $this->original = $original;
    }

    /**
     * ラップ対象の時間オブジェクトを返します.
     * @return Time
     */
    public function getOriginal()
    {
        return $this->original;
    }

    /**
     * ラップ対象のオブジェクトのタイプを返します.
     * @return int
     */
    public function getType()
    {
        return $this->original->getType();
    }

    /**
     * ラップ対象のオブジェクトの before メソッドを実行します.
     * @param  Time $time
     * @return bool
     */
    public function before(Time $time)
    {
        return $this->original->before($time);
    }

    /**
     * ラップ対象のオブジェクトの after メソッドを実行します.
     * @param  Time $time
     * @return bool
     */
    public function after(Time $time)
    {
        return $this->original->after($time);
    }

    /**
     * ラップ対象のオブジェクトの compareTo メソッドを実行します.
     * @param  Time $subject
     * @return bool
     */
    public function compareTo($subject)
    {
        return $this->original->compareTo($subject);
    }

    /**
     * 指定されたインスタンスをラップする新しい TimeWrapper を構築します.
     *
     * @param  Time $instance ラップ対象のオブジェクト
     * @return TimeWrapper
     * @codeCoverageIgnore
     */
    protected function newInstance(Time $instance)
    {
        return new self($instance);
    }

    /**
     * ラップ対象のオブジェクトの add メソッドを実行し, その返り値をこのクラスでラップします.
     *
     * @param  string $field
     * @param  int    $amount
     * @return TimeWrapper
     */
    public function add($field, $amount)
    {
        return $this->newInstance($this->original->add($field, $amount));
    }

    /**
     * ラップ対象のオブジェクトの set メソッドを実行し, その返り値をこのクラスでラップします.
     *
     * @param  string $field
     * @param  int    $value
     * @return TimeWrapper
     */
    public function set($field, $value)
    {
        return $this->newInstance($this->original->set($field, $value));
    }

    /**
     * ラップ対象のオブジェクトの setAll メソッドを実行し, その返り値をこのクラスでラップします.
     * @param  array|ArrayMap $subject
     * @return TimeWrapper
     */
    public function setAll($subject)
    {
        return $this->newInstance($this->original->setAll($subject));
    }

    /**
     * ラップ対象のオブジェクトの get メソッドを実行します.
     *
     * @param  string $field
     * @return int
     */
    public function get($field)
    {
        return $this->original->get($field);
    }

    /**
     * ラップ対象のオブジェクトの format メソッドを実行します.
     * @param  Format $format
     * @return string
     */
    public function format(Format $format = null)
    {
        return $this->original->format($format);
    }

    /**
     * ラップ対象のオブジェクトの formatTime メソッドを実行します.
     * @return string
     */
    public function formatTime()
    {
        return $this->original->formatTime();
    }

    /**
     * 指定されたオブジェクトとこのオブジェクトを比較します.
     * compareTo による比較結果が 0 を返し, かつクラスが同じ場合に TRUE を返します.
     *
     * @param  mixed   $obj 比較対象のオブジェクト
     * @return boolean      二つのオブジェクトが等しい場合に TRUE, それ以外は FALSE
     */
    public function equals($obj)
    {
        if (get_class($this) != get_class($obj)) {
            return false;
        }
        return $this->compareTo($obj) === 0;
    }

    /**
     * ラップ対象のオブジェクトの getDateCount メソッドを実行します.
     * @return int
     */
    public function getDateCount()
    {
        return $this->original->getDateCount();
    }

    /**
     * ラップ対象のオブジェクトの getDay メソッドを実行します.
     * @return int
     */
    public function getDay()
    {
        return $this->original->getDay();
    }

    /**
     * ラップ対象のオブジェクトの isLeapYear メソッドを実行します.
     * @return bool
     */
    public function isLeapYear()
    {
        return $this->original->isLeapYear();
    }

    /**
     * ラップ対象のオブジェクトの toDate メソッドを実行します.
     * @return Time
     */
    public function toDate()
    {
        return $this->original->toDate();
    }

    /**
     * ラップ対象のオブジェクトの toDatetime メソッドを実行します.
     * @return Time
     */
    public function toDatetime()
    {
        return $this->original->toDatetime();
    }

    /**
     * ラップ対象のオブジェクトの toTimestamp メソッドを実行します.
     * @return Time
     */
    public function toTimestamp()
    {
        return $this->original->toTimestamp();
    }

    /**
     * ラップ対象のオブジェクトの __toString メソッドを実行します.
     * @param  Time $time
     * @return string
     */
    public function __toString()
    {
        return $this->original->__toString();
    }
}


<?php
/**
 * This file is part of CMSGears Framework. Please view License file distributed
 * with the source code for license details.
 *
 * @link https://www.cmsgears.org/
 * @copyright Copyright (c) 2015 VulpineCode Technologies Pvt. Ltd.
 */

namespace cmsgears\widgets\block;

// Yii Imports
use yii\helpers\Html;

// CMG Imports
use cmsgears\core\common\base\Widget;

/**
 * Block forms a part of page either vertically or horizontally.
 *
 * @since 1.0.0
 */
class BasicBlock extends Widget {

	// Variables ---------------------------------------------------

	// Globals -------------------------------

	// Constants --------------

	// Public -----------------

	// Protected --------------

	// Variables -----------------------------

	// Public -----------------

	public $wrap	= true;
	public $options = [ 'class' => 'block' ];
	public $buffer	= true;

	// Background
	public $bkg			= false;
	public $fixedBkg	= false;
	public $scrollBkg	= false;
	public $parallaxBkg	= false;
	public $bkgUrl		= null;
	public $bkgClass	= null;

	// Texture
	public $texture			= false;
	public $textureClass	= null;

	// Max cover on top of block content
	public $maxCover		= false;
	public $maxCoverContent	= null;
	public $maxCoverClass	= null;

	// Block Header
	public $header			= false;
	public $headerIcon		= false;
	public $headerIconClass	= null;
	public $headerIconUrl	= null;
	public $headerTitle		= null;
	public $headerInfo		= null;
	public $headerContent	= null;

	// Block Content
	public $content				= false;
	public $contentTitle		= null;
	public $contentInfo			= null;
	public $contentSummary		= null;
	public $contentData			= null;

	public $contentClass		= null;
	public $contentDataClass	= null;
	public $contentBufferClass	= null;
	public $boxWrapClass		= null;

	// Block Footer
	public $footer			= false;
	public $footerIcon		= false;
	public $footerIconClass	= null;
	public $footerIconUrl	= null;
	public $footerTitle		= null;
	public $footerInfo		= null;
	public $footerContent	= null;

	// Protected --------------

	// Private ----------------

	// Traits ------------------------------------------------------

	// Constructor and Initialisation ------------------------------

    public function init() {

        parent::init();

		// Required in case content is generated manually
        ob_start();

        ob_implicit_flush( false );
    }

	// Instance methods --------------------------------------------

	// Yii interfaces ------------------------

	// Yii parent classes --------------------

	// CMG interfaces ------------------------

	// CMG parent classes --------------------

    public function renderWidget( $config = [] ) {

		// Default background class defined in css as - .bkg-block { background-image: url(<image url>) }
		if( $this->bkg && !isset( $this->bkgUrl ) && !isset( $this->bkgClass ) ) {

			$this->bkgClass	= 'bkg-block';
		}

		$widgetHtml = $this->render( $this->template, [ 'widget' => $this ] );

		// Wrap the view
		if( $this->wrap ) {

			return Html::tag( $this->wrapper, $widgetHtml, $this->options );
		}

		return $widgetHtml;
    }

	public function renderAutoload( $config = [] ) {

		return $this->render( "$this->template/autoload", [ 'widget' => $this, 'content' => $config[ 'content' ] ] );
	}

	// BasicBlock ----------------------------

}


<?php

namespace HD\Api\Client\Http;

use Zend\Http\Client as HttpClient;
use Zend\Http\Client\Adapter\Curl;

use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\EventManager;
use Zend\EventManager\EventManagerAwareInterface;

class Client implements EventManagerAwareInterface, ClientInterface
{
    /*
     * EventManager
     */
    protected $events;

    /**
     * @var ServiceManager
     */
    protected $serviceManager;

    /**
     * @var HttpClient
     */
    protected $httpClient;

    /**
     * @var Curl
     */
    protected $httpAdapter;

    /**
     * @var Response
     */
    protected $response;

    /**
     * @var Request
     */
    protected $request;

    /**
     * @var array
     */
    private $options;

    public function setOptions($options)
    {
        $this->options = $options;
    }

    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Get Request
     *
     * @param  string $path
     * @param  array  $parameters
     * @param  array  $headers
     * @return Zend\Http\Response
     */
    public function get($path, array $parameters = array(), array $headers = array())
    {
        return $this->request($path, $parameters, 'GET', $headers);
    }

    /**
     * Send POST Request
     *
     * @param  string $path
     * @param  array  $parameters
     * @param  array  $headers
     * @return Zend\Http\Response
     */
    public function post($path, array $parameters = array(), array $headers = array())
    {
        return $this->request($path, $parameters, 'POST', $headers);
    }

    /**
     * Send Delete Request
     *
     * @param  string $path
     * @param  array  $parameters
     * @param  array  $headers
     * @return Zend\Http\Response
     */
    public function delete($path, array $parameters = array(), array $headers = array())
    {
        return $this->request($path, $parameters, 'DELETE', $headers);
    }

    /**
     * Send Put Request
     *
     * @param  string $path
     * @param  array  $headers
     * @return Zend\Http\Response
     */
    public function put($path, array $headers = array())
    {
        return $this->request($path, array(), 'PUT', $headers);
    }

    /**
     * Send Request
     *
     * @param  string $path
     * @param  array  $parameters
     * @param  string $httpMethod
     * @param  array  $headers
     * @return Zend\Http\Response
     */
    public function request($path, array $parameters = array(), $httpMethod = 'GET', array $headers = array())
    {
        $client = $this->getHttpClient();
        $client->reset();

        $client->setUri($this->options['base_url'] . $this->options['api_version'] . '/' . $path);
        $request = $client->getRequest();

        if ($httpMethod == 'GET') {
            $query = $request->getQuery();

            foreach ($parameters as $key => $value) {
                $query->set($key, $value);
            }
        } elseif ($httpMethod == 'POST') {
            $client->setEncType('multipart/form-data');
            $request->setMethod('POST');
            $post = $request->getPost();

            foreach ($parameters as $key => $value) {
                $post->set($key, $value);
            }
        }

        //Trigger Pre Send Event to modify Request Object
        $this->getEventManager()->trigger('pre.send', $request);

        $response = $client->dispatch($request);

        //Trigger Post Send to Modify/Validate Response object
        $result = $this->getEventManager()->trigger('post.send', $response);
        if ($result->stopped()) {
            $response = $result->last();
        }

        $this->request = $request;

        $result = json_decode($response->getBody());

        return $response;
    }

    /**
     * Get Http Client
     *
     * @param  string $path
     * @return HttpClient
     */
    public function getHttpClient()
    {
        if (null === $this->httpClient) {
            $this->httpClient = new HttpClient();
            $this->httpClient->setAdapter($this->getHttpAdapter());
        }

        return $this->httpClient;
    }

    /**
     * Get Http Adapter
     * @return
     */
    public function getHttpAdapter()
    {
        if (null === $this->httpAdapter) {
            $this->httpAdapter = new Curl();
            $this->httpAdapter->setOptions(array(
                'sslverifypeer' =>false,
            ));
        }
        return $this->httpAdapter;
    }

    public function setHttpAdapter($adapter)
    {
        $this->httpAdapter = $adapter;
    }

    /**
     * Set Event Manager
     *
     * @param  EventManagerInterface $events
     * @return HybridAuth
     */
    public function setEventManager(EventManagerInterface $events)
    {
        $events->setIdentifiers(array(
            __CLASS__,
            get_called_class(),
        ));
        $this->events = $events;
        return $this;
    }

    /**
     * Get Event Manager
     *
     * Lazy-loads an EventManager instance if none registered.
     *
     * @return EventManagerInterface
     */
    public function getEventManager()
    {
        return $this->events;
    }

    public function getResponse()
    {
        return $this->response;
    }

    public function getRequest()
    {
        return $this->request;
    }
}


<?php
/**
 * Phossa Project
 *
 * PHP version 5.4
 *
 * @category  Library
 * @package   Phossa\Query
 * @author    Hong Zhang <phossa@126.com>
 * @copyright 2015 phossa.com
 * @license   http://mit-license.org/ MIT License
 * @link      http://www.phossa.com/
 */
/*# declare(strict_types=1); */

namespace Phossa\Query\Clause;

use Phossa\Query\Statement\ExpressionInterface;
use Phossa\Query\Dialect\Common\SelectStatementInterface;

/**
 * WhereTrait
 *
 * @trait
 * @package Phossa\Query
 * @author  Hong Zhang <phossa@126.com>
 * @see     OtherWhereInterface
 * @version 1.0.0
 * @since   1.0.0 added
 */
trait WhereTrait
{
    use ValueTrait;

    /**
     * WHEREs
     *
     * @var    array
     * @access protected
     */
    protected $clause_where = [];

    /**
     * {@inheritDoc}
     */
    public function where(
        $col,
        $operator = ClauseInterface::NO_OPERATOR,
        $value    = ClauseInterface::NO_VALUE,
        /*# bool */ $logicAnd = true,
        /*# bool */ $whereNot = false,
        /*# bool */ $rawMode  = false,
        /*# string */ $clause = 'where'
    ) {
        // $col is an array
        if (is_array($col)) {
            foreach ($col as $fld => $val) {
                $this->where(
                    $fld, $val, ClauseInterface::NO_VALUE, $logicAnd,
                    $whereNot, $rawMode);
            }

        // $col is string or object
        } else {
            // grouped where
            if (is_object($col) && $col instanceof ExpressionInterface) {
                $operator = ClauseInterface::NO_OPERATOR;
                $value    = ClauseInterface::NO_VALUE;

            // 1 param provided, raw where provided
            } elseif (ClauseInterface::NO_OPERATOR === $operator) {
                $rawMode  = true;
                $value    = ClauseInterface::NO_VALUE;

            //  2 params provided
            } elseif (ClauseInterface::NO_VALUE === $value) {
                if (is_array($operator)) {
                    $value    = $operator[1];
                    $operator = $operator[0];
                } else {
                    $value    = $operator;
                    $operator = '=';
                }

            // short version provided
            } elseif (WhereInterface::SHORT_FORM === $value) {
                $value = ClauseInterface::NO_VALUE;
            }

            $this->clause_{$clause}[] = [
                $rawMode, $whereNot, $logicAnd, $col, $operator, $value
            ];
        }

        return $this;
    }

    /**
     * {@inheritDoc}
     */
    public function andWhere(
        $col,
        $operator = ClauseInterface::NO_OPERATOR,
        $value    = ClauseInterface::NO_VALUE
    ) {
        return $this->where($col, $operator, $value);
    }

    /**
     * {@inheritDoc}
     */
    public function orWhere(
        $col,
        $operator = ClauseInterface::NO_OPERATOR,
        $value    = ClauseInterface::NO_VALUE
    ) {
        return $this->where($col, $operator, $value, false);
    }

    /**
     * {@inheritDoc}
     */
    public function whereRaw($where)
    {
        return $this->where($where, ClauseInterface::NO_OPERATOR,
            ClauseInterface::NO_VALUE, true, false, true);
    }

    /**
     * {@inheritDoc}
     */
    public function orWhereRaw($where)
    {
        return $this->where($where, ClauseInterface::NO_OPERATOR,
            ClauseInterface::NO_VALUE, false, false, true
        );
    }

    /**
     * {@inheritDoc}
     */
    public function whereNot(
        $col,
        $operator = ClauseInterface::NO_OPERATOR,
        $value    = ClauseInterface::NO_VALUE
    ) {
        return $this->where($col, $operator, $value, true, true);
    }

    /**
     * {@inheritDoc}
     */
    public function orWhereNot(
        $col,
        $operator = ClauseInterface::NO_OPERATOR,
        $value    = ClauseInterface::NO_VALUE
    ) {
        return $this->where($col, $operator, $value, false, true);
    }

    /**
     * {@inheritDoc
     */
    public function whereIn($col, array $value, $and = true, $not = false)
    {
        $in = ($not ? 'NOT IN (' : 'IN (') . join(',', $value) . ')';
        return $this->where($col, $in, WhereInterface::SHORT_FORM, $and);
    }

    /**
     * {@inheritDoc
     */
    public function orWhereIn($col, array $value)
    {
        return $this->whereIn($col, $value, false);
    }

    /**
     * {@inheritDoc
     */
    public function whereNotIn($col, array $value)
    {
        return $this->whereIn($col, $value, true, true);
    }

    /**
     * {@inheritDoc
     */
    public function orWhereNotIn($col, array $value)
    {
        return $this->whereIn($col, $value, false, true);
    }

    /**
     * {@inheritDoc}
     */
    public function whereBetween($col, $val1, $val2, $and = true, $not = false)
    {
        $bet = sprintf('%sBETWEEN %s AND %s', $not ? 'NOT ' : '', $val1, $val2);
        return $this->where($col, $bet, WhereInterface::SHORT_FORM, $and);
    }

    /**
     * {@inheritDoc}
     */
    public function orWhereBetween($col, $value1, $value2)
    {
        return $this->whereBetween($col, $value1, $value2, false);
    }

    /**
     * {@inheritDoc}
     */
    public function whereNotBetween($col, $value1, $value2)
    {
        return $this->whereBetween($col, $value1, $value2, true, true);
    }

    /**
     * {@inheritDoc}
     */
    public function orWhereNotBetween($col, $val1, $val2)
    {
        return $this->whereBetween($col, $val1, $val2, false, true);
    }

    /**
     * {@inheritDoc}
     */
    public function whereNull($col, $and = true, $not = false)
    {
        return $this->where($col, $not ? 'IS NOT NULL' : 'IS NULL',
            WhereInterface::SHORT_FORM, $and);
    }

    /**
     * {@inheritDoc}
     */
    public function orWhereNull($col)
    {
        return $this->whereNull($col, false);
    }

    /**
     * {@inheritDoc}
     */
    public function whereNotNull($col)
    {
        return $this->whereNull($col, true, true);
    }

    /**
     * {@inheritDoc}
     */
    public function orWhereNotNull($col)
    {
        return $this->whereNull($col, false, true);
    }

    /**
     * {@inheritDoc}
     */
    public function whereExists(
        SelectStatementInterface $sel, $and = true, $not = false
    ) {
        $ext = $not ? 'NOT EXISTS' : 'EXISTS';
        return $this->where(null, $ext, $sel, $and);
    }

    /**
     * {@inheritDoc}
     */
    public function orWhereExists(SelectStatementInterface $sel)
    {
        return $this->whereExists($sel, false);
    }

    /**
     * {@inheritDoc}
     */
    public function whereNotExists(SelectStatementInterface $sel)
    {
        return $this->whereExists($sel, true, true);
    }

    /**
     * {@inheritDoc}
     */
    public function orWhereNotExists(SelectStatementInterface $sel)
    {
        return $this->whereExists($sel, false, true);
    }

    /**
     * Build WHERE
     *
     * @param  string $clause 'where|having'
     * @return array
     * @access protected
     */
    protected function buildWhere(/*# string */ $clause = 'where')/*# : array */
    {
        $result = [];
        if (!empty($this->clause_{$clause})) {
            foreach ($this->clause_{$clause} as $idx => $where) {
                $cls = [];

                // logic
                if ($idx) {
                    $cls[] = $where[2] ? 'AND' : 'OR';
                }

                // NOT
                if ($where[1]) {
                    $cls[] = 'NOT';
                }

                // grouped where
                if (is_object($where[3])) {
                    $cls[] = $where[3]->getStatement([], false);

                } elseif (!is_null($where[3])) {
                    $cls[] = $where[0] ? $where[3] : $this->quote($where[3]);
                }

                // operator
                if (ClauseInterface::NO_OPERATOR !== $where[4]) {
                    $cls[] = (empty($col) ? '' : ' ') . $where[4];
                }

                // val part
                if (ClauseInterface::NO_VALUE !== $where[5]) {
                    $cls[] = $this->processValue($where[5]);
                }

                $result[] = join(' ', $cls);
            }
        }
        return $result;
    }

    /* utilities from UtilityTrait */
    abstract protected function quote(/*# string */ $str)/*# : string */;
}


<?php

namespace futuretek\shared;

use DateTime;
use RuntimeException;

/**
 * Class Tools
 *
 * @package futuretek\shared
 * @author  Lukas Cerny <lukas.cerny@futuretek.cz>, Petr Compel <petr.compel@futuretek.cz>
 * @license Apache-2.0
 * @link    http://www.futuretek.cz
 */
class Tools
{
    /** Format phone number in international format for displaying */
    const PHONE_NUMBER_FORMAT_INTERNATIONAL_NICE = 1;

    /** Format phone number in international format as number */
    const PHONE_NUMBER_FORMAT_INTERNATIONAL = 2;

    /** Format phone number for displaying */
    const PHONE_NUMBER_FORMAT_NICE = 3;

    /** Format phone number as pure number (if the phone number is international, + sign may occur) */
    const PHONE_NUMBER_FORMAT_NUMBER = 4;

    /** Format phone number for use with Smstools3 */
    const PHONE_NUMBER_FORMAT_SMSTOOLS = 5;

    /** Long date format - d. mmmm. yyyy */
    const DATE_LONG = 1;

    /** Short date format - d. m. yyyy */
    const DATE_SHORT = 2;

    /** Long date format - d. mmmm. yyyy h:m:s */
    const DATETIME_LONG = 3;

    /** Short date format - d. m. yyyy h:m:s */
    const DATETIME_SHORT = 4;

    /** Algorithm for the hashing function */
    const HASH_ALGORITHM = 'sha256';

    /** Monday */
    const DOW_MONDAY = 0;

    /** Tuesday */
    const DOW_TUESDAY = 1;

    /** Wednesday */
    const DOW_WEDNESDAY = 2;

    /** Thursday */
    const DOW_THURSDAY = 3;

    /** Friday */
    const DOW_FRIDAY = 4;

    /** Saturday */
    const DOW_SATURDAY = 5;

    /** Sunday */
    const DOW_SUNDAY = 6;

    private static $_countryCodes = [
        'AND' => 'AD', 'ARE' => 'AE', 'AFG' => 'AF', 'ATG' => 'AG', 'AIA' => 'AI', 'ALB' => 'AL', 'ARM' => 'AM', 'ANT' => 'AN', 'AGO' => 'AO', 'ARG' => 'AR',
        'ASM' => 'AS', 'AUT' => 'AT', 'AUS' => 'AU', 'ABW' => 'AW', 'AZE' => 'AZ', 'BIH' => 'BA', 'BRB' => 'BB', 'BGD' => 'BD', 'BEL' => 'BE', 'BFA' => 'BF',
        'BGR' => 'BG', 'BHR' => 'BH', 'BDI' => 'BI', 'BEN' => 'BJ', 'BMU' => 'BM', 'BRN' => 'BN', 'BOL' => 'BO', 'BRA' => 'BR', 'BHS' => 'BS', 'BTN' => 'BT',
        'BWA' => 'BW', 'BLR' => 'BY', 'BLZ' => 'BZ', 'CAN' => 'CA', 'COD' => 'CD', 'CAF' => 'CF', 'COG' => 'CG', 'CIV' => 'CI', 'COK' => 'CK', 'CHL' => 'CL',
        'CMR' => 'CM', 'CHN' => 'CN', 'COL' => 'CO', 'CRI' => 'CR', 'CUB' => 'CU', 'CPV' => 'CV', 'CYP' => 'CY', 'CZE' => 'CZ', 'DEU' => 'DE', 'DJI' => 'DJ',
        'DNK' => 'DK', 'DMA' => 'DM', 'DOM' => 'DO', 'DZA' => 'DZ', 'ECU' => 'EC', 'EST' => 'EE', 'EGY' => 'EG', 'ESH' => 'EH', 'ERI' => 'ER', 'ESP' => 'ES',
        'ETH' => 'ET', 'FIN' => 'FI', 'FJI' => 'FJ', 'FLK' => 'FK', 'FSM' => 'FM', 'FRO' => 'FO', 'FRA' => 'FR', 'GAB' => 'GA', 'GBR' => 'GB', 'GRD' => 'GD',
        'GEO' => 'GE', 'GUF' => 'GF', 'GHA' => 'GH', 'GIB' => 'GI', 'GRL' => 'GL', 'GMB' => 'GM', 'GIN' => 'GN', 'GLP' => 'GP', 'GNQ' => 'GQ', 'GRC' => 'GR',
        'GTM' => 'GT', 'GUM' => 'GU', 'GNB' => 'GW', 'GUY' => 'GY', 'HKG' => 'HK', 'HND' => 'HN', 'HRV' => 'HR', 'HTI' => 'HT', 'HUN' => 'HU', 'CHE' => 'CH',
        'IDN' => 'ID', 'IRL' => 'IE', 'ISR' => 'IL', 'IND' => 'IN', 'IRQ' => 'IQ', 'IRN' => 'IR', 'ISL' => 'IS', 'ITA' => 'IT', 'JAM' => 'JM', 'JOR' => 'JO',
        'JPN' => 'JP', 'KEN' => 'KE', 'KGZ' => 'KG', 'KHM' => 'KH', 'KIR' => 'KI', 'COM' => 'KM', 'KNA' => 'KN', 'PRK' => 'KP', 'KOR' => 'KR', 'KWT' => 'KW',
        'CYM' => 'KY', 'KAZ' => 'KZ', 'LAO' => 'LA', 'LBN' => 'LB', 'LCA' => 'LC', 'LIE' => 'LI', 'LKA' => 'LK', 'LBR' => 'LR', 'LSO' => 'LS', 'LTU' => 'LT',
        'LUX' => 'LU', 'LVA' => 'LV', 'LBY' => 'LY', 'MAR' => 'MA', 'MCO' => 'MC', 'MDA' => 'MD', 'MDG' => 'MG', 'MHL' => 'MH', 'MKD' => 'MK', 'MLI' => 'ML',
        'MMR' => 'MM', 'MNG' => 'MN', 'MAC' => 'MO', 'MNP' => 'MP', 'MTQ' => 'MQ', 'MRT' => 'MR', 'MSR' => 'MS', 'MLT' => 'MT', 'MUS' => 'MU', 'MDV' => 'MV',
        'MWI' => 'MW', 'MEX' => 'MX', 'MYS' => 'MY', 'MOZ' => 'MZ', 'NAM' => 'NA', 'NCL' => 'NC', 'NER' => 'NE', 'NFK' => 'NF', 'NGA' => 'NG', 'NIC' => 'NI',
        'NLD' => 'NL', 'NOR' => 'NO', 'NPL' => 'NP', 'NRU' => 'NR', 'NIU' => 'NU', 'NZL' => 'NZ', 'OMN' => 'OM', 'PAN' => 'PA', 'PER' => 'PE', 'PYF' => 'PF',
        'PNG' => 'PG', 'PHL' => 'PH', 'PAK' => 'PK', 'POL' => 'PL', 'SPM' => 'PM', 'PCN' => 'PN', 'PRI' => 'PR', 'PRT' => 'PT', 'PLW' => 'PW', 'PRY' => 'PY',
        'QAT' => 'QA', 'REU' => 'RE', 'ROM' => 'RO', 'RUS' => 'RU', 'RWA' => 'RW', 'SAU' => 'SA', 'SLB' => 'SB', 'SYC' => 'SC', 'SDN' => 'SD', 'SWE' => 'SE',
        'SGP' => 'SG', 'SHN' => 'SH', 'SVN' => 'SI', 'SJM' => 'SJ', 'SVK' => 'SK', 'SLE' => 'SL', 'SMR' => 'SM', 'SEN' => 'SN', 'SOM' => 'SO', 'SUR' => 'SR',
        'STP' => 'ST', 'SLV' => 'SV', 'SYR' => 'SY', 'SWZ' => 'SZ', 'TCA' => 'TC', 'TCD' => 'TD', 'TGO' => 'TG', 'THA' => 'TH', 'TJK' => 'TJ', 'TKL' => 'TK',
        'TKM' => 'TM', 'TUN' => 'TN', 'TON' => 'TO', 'TUR' => 'TR', 'TTO' => 'TT', 'TUV' => 'TV', 'TWN' => 'TW', 'TZA' => 'TZ', 'UKR' => 'UA', 'UGA' => 'UG',
        'USA' => 'US', 'URY' => 'UY', 'UZB' => 'UZ', 'VAT' => 'VA', 'VCT' => 'VC', 'VEN' => 'VE', 'VGB' => 'VG', 'VIR' => 'VI', 'VNM' => 'VN', 'VUT' => 'VU',
        'WLF' => 'WF', 'WSM' => 'WS', 'YEM' => 'YE', 'ZAF' => 'ZA', 'ZMB' => 'ZM', 'ZWE' => 'ZW',
    ];

    /**
     * Remove all space characters in string
     *
     * @param string $str Input string
     *
     * @return string String with removed spaces
     * @static
     */
    public static function removeSpace($str)
    {
        return strtr($str, [' ' => '']);
    }

    /**
     * Format phone number
     *
     * @param string $phoneNumber Phone number
     * @param int $formatType Phone number format:
     *

     *
Tools::PHONE_NUMBER_FORMAT_INTERNATIONAL_NICE

     *
Tools::PHONE_NUMBER_FORMAT_INTERNATIONAL

     *
Tools::PHONE_NUMBER_FORMAT_NICE

     *
Tools::PHONE_NUMBER_FORMAT_NUMBER

     *
Tools::PHONE_NUMBER_FORMAT_SMSTOOLS

     *

     *
     * @return string|bool Formatted phone number or false when the phone number is invalid
     * @static
     */
    public static function formatPhoneNumber($phoneNumber, $formatType = Tools::PHONE_NUMBER_FORMAT_NUMBER)
    {
        $formatType = (int)$formatType;

        if ($formatType !== self::PHONE_NUMBER_FORMAT_INTERNATIONAL && $formatType !== self::PHONE_NUMBER_FORMAT_INTERNATIONAL_NICE &&
            $formatType !== self::PHONE_NUMBER_FORMAT_NUMBER && $formatType !== self::PHONE_NUMBER_FORMAT_NICE &&
            $formatType !== self::PHONE_NUMBER_FORMAT_SMSTOOLS
        ) {
            return false;
        }

        if (!Validate::isPhoneNumber($phoneNumber)) {
            return false;
        }

        $phoneNumber = self::removeSpace($phoneNumber);
        $phoneLen = \strlen($phoneNumber);

        if ($phoneLen > 9 && 0 !== strpos($phoneNumber, '+')) {
            $phoneNumber = '+' . $phoneNumber;
            $phoneLen++;
        }

        if ($phoneLen !== 9 && !($phoneLen >= 11 && $phoneLen <= 13 && 0 === strpos($phoneNumber, '+'))) {
            return false;
        }

        $international = ($phoneLen !== 9);

        switch ($formatType) {
            case self::PHONE_NUMBER_FORMAT_INTERNATIONAL_NICE:
                $formattedPhone = preg_replace(
                    '/^(\+\d{1,3})(\d{3})(\d{3})(\d{3})$/',
                    '$1 $2 $3 $4',
                    $international ? $phoneNumber : '+420' . $phoneNumber
                );
                break;
            case self::PHONE_NUMBER_FORMAT_INTERNATIONAL:
                $formattedPhone = $international ? $phoneNumber : '+420' . $phoneNumber;
                break;
            case self::PHONE_NUMBER_FORMAT_NICE:
                $formattedPhone = preg_replace(
                    '/^(\+\d{1,3})(\d{3})(\d{3})(\d{3})$/',
                    '$2 $3 $4',
                    $international ? $phoneNumber : '+420' . $phoneNumber
                );
                break;
            case self::PHONE_NUMBER_FORMAT_NUMBER:
                $formattedPhone = $international ? substr($phoneNumber, -9) : $phoneNumber;
                break;
            case self::PHONE_NUMBER_FORMAT_SMSTOOLS:
                $formattedPhone = $international ? trim($phoneNumber, '+') : '420' . $phoneNumber;
                break;
            default:
                $formattedPhone = false;
        }

        return $formattedPhone;
    }

    /**
     * Format date/datetime
     *
     * @param string|integer $value Timestamp or date accepted by strtotime()
     * @param integer $format Date format
     *
     * @return bool|string Formatted date
     * @static
     */
    public static function formatDate($value, $format)
    {
        if ($value === null) {
            return false;
        }

        //Make sure we have timestamp
        if (!\is_int($value)) {
            $value = strtotime($value);
        }
        switch ($format) {
            case self::DATE_LONG:
                $inflection = new Inflection();
                $date = date('j', $value) . '. ' . $inflection->inflect(self::getMonthName(date('n', $value)))[2] . ' ' . date('Y', $value);
                break;
            case self::DATE_SHORT:
                $date = date('j. n. Y', $value);
                break;
            case self::DATETIME_LONG:
                $inflection = new Inflection();
                $date =
                    date('j', $value) . '. ' . $inflection->inflect(self::getMonthName(date('n', $value)))[2] . ' ' .
                    date('Y G:i:s', $value);
                break;
            case self::DATETIME_SHORT:
                $date = date('j. n. Y G:i:s', $value);
                break;
            default:
                return false;
        }

        return $date;
    }

    /**
     * Get month name in current language
     *
     * @param int $month Month number
     *
     * @return string Localised month name
     * @static
     */
    public static function getMonthName($month)
    {
        if ($month < 1 || $month > 12) {
            return '';
        }

        $monthNames = [
            1 => self::poorManTranslate('fts-shared', 'January'),
            2 => self::poorManTranslate('fts-shared', 'February'),
            3 => self::poorManTranslate('fts-shared', 'March'),
            4 => self::poorManTranslate('fts-shared', 'April'),
            5 => self::poorManTranslate('fts-shared', 'May'),
            6 => self::poorManTranslate('fts-shared', 'June'),
            7 => self::poorManTranslate('fts-shared', 'July'),
            8 => self::poorManTranslate('fts-shared', 'August'),
            9 => self::poorManTranslate('fts-shared', 'September'),
            10 => self::poorManTranslate('fts-shared', 'October'),
            11 => self::poorManTranslate('fts-shared', 'November'),
            12 => self::poorManTranslate('fts-shared', 'December'),
        ];

        return $monthNames[$month];
    }

    /**
     * Get day name in current language
     *
     * @param int $day Day of week number according to Tools::dow()
     *
     * @return string Localised day name
     * @static
     */
    public static function getDayName($day)
    {
        if ($day < self::DOW_MONDAY || $day > self::DOW_SUNDAY) {
            return '';
        }

        $dayNames = [
            self::DOW_MONDAY => self::poorManTranslate('fts-shared', 'Monday'),
            self::DOW_TUESDAY => self::poorManTranslate('fts-shared', 'Tuesday'),
            self::DOW_WEDNESDAY => self::poorManTranslate('fts-shared', 'Wednesday'),
            self::DOW_THURSDAY => self::poorManTranslate('fts-shared', 'Thursday'),
            self::DOW_FRIDAY => self::poorManTranslate('fts-shared', 'Friday'),
            self::DOW_SATURDAY => self::poorManTranslate('fts-shared', 'Saturday'),
            self::DOW_SUNDAY => self::poorManTranslate('fts-shared', 'Sunday'),
        ];

        return $dayNames[$day];
    }

    /**
     * Get textual representation of time difference in style 'some time ago'
     *
     * @param int|string $value Time in any format accepted by strtotime()
     *
     * @return string Textual representation of time difference
     * @static
     */
    public static function getRelativeTime($value)
    {
        //Make sure we have timestamp
        if (!\is_int($value)) {
            $value = strtotime($value);
        }

        $diff = abs($value - time());
        if ($value < time()) {
            if ($diff < 60) {
                //Less than a minute
                return self::poorManTranslate('fts-shared', 'a few seconds ago');
            }

            if ($diff < 3600) {
                //Less than a hour
                return self::poorManTranslate('fts-shared', 'a {n, plural, =1{minute} other{# minutes}} ago', ['n' => floor($diff / 60)]);
            }

            if ($diff < 86400) {
                //Less than a day
                return self::poorManTranslate('fts-shared', 'a {n, plural, =1{hour} other{# hours}} ago', ['n' => floor($diff / 3600)]);
            }

            if ($diff < 172800) {
                //Less than two days
                return self::poorManTranslate('fts-shared', 'yesterday');
            }

            if ($diff < 604800) {
                //Less than a week
                return self::poorManTranslate('fts-shared', 'a {n} days ago', ['n' => floor($diff / 86400)]);
            }

            if ($diff < 2628000) {
                //Less than a month
                return self::poorManTranslate('fts-shared', 'a {n, plural, =1{week} other{# weeks}} ago', ['n' => floor($diff / 604800)]);
            }

            if ($diff < 31556926) {
                //Less than a year
                return self::poorManTranslate('fts-shared', 'a {n, plural, =1{month} other{# months}} ago', ['n' => floor($diff / 2628000)]);
            }

            //Less than eternity :-)
            return self::poorManTranslate('fts-shared', 'a {n, plural, =1{year} other{# years}} ago', ['n' => floor($diff / 31556926)]);
        }

        if ($diff < 60) {
            //Less than a minute
            return self::poorManTranslate('fts-shared', 'after a few seconds');
        }

        if ($diff < 3600) {
            //Less than a hour
            return self::poorManTranslate('fts-shared', 'after a {n, plural, =1{minute} other{# minutes}}', ['n' => floor($diff / 60)]);
        }

        if ($diff < 86400) {
            //Less than a day
            return self::poorManTranslate('fts-shared', 'after a {n, plural, =1{hour} other{# hours}}', ['n' => floor($diff / 3600)]);
        }

        if ($diff < 172800) {
            //Less than two days
            return self::poorManTranslate('fts-shared', 'tomorrow');
        }

        if ($diff < 604800) {
            //Less than a week
            return self::poorManTranslate('fts-shared', 'after a {n, plural, =1{day} other{# days}}', ['n' => floor($diff / 86400)]);
        }

        if ($diff < 2628000) {
            //Less than a month
            return self::poorManTranslate('fts-shared', 'after a {n, plural, =1{week} other{# weeks}}', ['n' => floor($diff / 604800)]);
        }

        if ($diff < 31556926) {
            //Less than a year
            return self::poorManTranslate('fts-shared', 'after a {n, plural, =1{month} other{# months}}', ['n' => floor($diff / 2628000)]);
        }

        //Less than eternity :-)
        return self::poorManTranslate('fts-shared', 'after a {n, plural, =1{year} other{# years}}', ['n' => floor($diff / 31556926)]);
    }

    /**
     * Truncate string to a maximal specific length with smart truncate on given character
     *
     * @param string $string Input string
     * @param integer $limit Maximal output string length
     * @param string $break Character on which the output string should be truncated.
     * @param string $pad String that will be appended at the end of truncated string
     *
     * @return string Truncated string
     * @static
     */
    public static function truncate($string, $limit, $break = ' ', $pad = '...')
    {
        if (\strlen($string) <= $limit) {
            return $string;
        }

        if (false !== ($breakpoint = strpos($string, $break, $limit)) && $breakpoint < \strlen($string) - 1) {
            $string = substr($string, 0, $breakpoint) . $pad;
        } else {
            $string = substr($string, 0, $limit) . $pad;
        }

        return $string;
    }

    /**
     * Generate v4 UUID
     *
     * Version 4 UUIDs are pseudo-random.
     *
     * @return string GUIDv4
     * @static
     */
    public static function GUIDv4()
    {
        return sprintf(
            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0x0fff) | 0x4000,
            mt_rand(0, 0x3fff) | 0x8000,
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff)
        );
    }

    /**
     * Generate v5 UUID
     *
     * Version 5 UUIDs are named based. They require a namespace (another
     * valid UUID) and a value (the name). Given the same namespace and
     * name, the output is always the same.
     *
     * @param string $namespace Valid namespace (GUID)
     * @param string $name Valid name (value)
     *
     * @return string|bool GUIDv5 or false when the namespace is not a valid GUID
     * @static
     */
    public static function GUIDv5($namespace, $name)
    {
        if (!Validate::isGuid($namespace)) {
            return false;
        }

        $nHex = str_replace(['-', '{', '}'], '', $namespace);
        $nStr = '';

        $nHexLen = \strlen($nHex);
        for ($i = 0; $i < $nHexLen; $i += 2) {
            $nStr .= \chr(\intval($nHex[$i] . $nHex[$i + 1], 16));
        }

        $hash = sha1($nStr . $name);

        return sprintf(
            '%08s-%04s-%04x-%04x-%12s',
            substr($hash, 0, 8),
            substr($hash, 8, 4),
            (\intval(substr($hash, 12, 4), 16) & 0x0fff) | 0x5000,
            (\intval(substr($hash, 16, 4), 16) & 0x3fff) | 0x8000,
            substr($hash, 20, 12)
        );
    }

    /**
     * Translates a string with underscores into camel case (e.g. first_name -> firstName)
     *
     * @param      $str
     * @param bool $catapitalise_first_char
     *
     * @return mixed
     */
    public static function toCamelCase($str, $catapitalise_first_char = false)
    {
        $str = strtolower($str);
        if ($catapitalise_first_char) {
            $str = ucfirst($str);
        }

        return preg_replace_callback('/_+([a-z])/', function ($c) {
            return strtoupper($c[1]);
        }, $str);
    }

    /**
     * Transform a CamelCase string to underscore_case string
     *
     * @param string $string
     *
     * @return string
     */
    public static function toUnderscoreCase($string)
    {
        return strtolower(trim(preg_replace('/([A-Z][a-z])/', '_$1', $string), '_'));
    }

    /**
     * Transform a CamelCase string to comma-case string
     *
     * @param string $string
     *
     * @return string
     */
    public static function toCommaCase($string)
    {
        return strtolower(trim(preg_replace('/([A-Z][a-z])/', '-$1', $string), '-'));
    }

    /**
     * Random password generator
     *
     * @param integer $length Desired length (optional)
     * @param string $flag Output type (NUMERIC, ALPHANUMERIC, ALPHA, ALPHA_LOWER)
     *
     * @return string Password
     */
    public static function passwdGen($length = 8, $flag = 'ALPHANUMERIC')
    {
        switch ($flag) {
            case 'NUMERIC':
                $str = '0123456789';
                break;
            case 'ALPHA':
                $str = 'abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
                break;
            case 'ALPHA_LOWER':
                $str = 'abcdefghijkmnopqrstuvwxyz';
                break;
            default:
                $str = 'abcdefghijkmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
                break;
        }

        for ($i = 0, $passwd = ''; $i < $length; $i++) {
            $passwd .= substr($str, mt_rand(0, \strlen($str) - 1), 1);
        }

        return $passwd;
    }

    /**
     * Get the server variable REMOTE_ADDR, or the first ip of HTTP_X_FORWARDED_FOR (when using proxy)
     *
     * @return string $remote_addr ip of client
     */
    public static function getRemoteAddr()
    {
        // This condition is necessary when using CDN, don't remove it.
        if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER) && $_SERVER['HTTP_X_FORWARDED_FOR'] &&
            (!array_key_exists('REMOTE_ADDR', $_SERVER) || preg_match('/^127\./', trim($_SERVER['REMOTE_ADDR'])) ||
                preg_match('/^172\.16\./', trim($_SERVER['REMOTE_ADDR'])) ||
                preg_match('/^192\.168\./', trim($_SERVER['REMOTE_ADDR'])) || preg_match('/^10\./', trim($_SERVER['REMOTE_ADDR'])))
        ) {
            if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',')) {
                $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);

                return $ips[0];
            }

            return $_SERVER['HTTP_X_FORWARDED_FOR'];
        }

        return $_SERVER['REMOTE_ADDR'];
    }

    /**
     * Delete directory and subdirectories
     *
     * @param string $dirName Directory name
     * @param bool $deleteSelf Delete also specified directory
     *
     * @return bool
     */
    public static function deleteDirectory($dirName, $deleteSelf = true)
    {
        $dirName = rtrim($dirName, '/') . '/';
        if (file_exists($dirName) && $files = scandir($dirName, SCANDIR_SORT_NONE)) {
            foreach ($files as $file) {
                if ($file !== '.' && $file !== '..' && $file !== '.svn') {
                    if (is_dir($dirName . $file)) {
                        self::deleteDirectory($dirName . $file, true);
                    } elseif (file_exists($dirName . $file)) {
                        @chmod($dirName . $file, 0777); // NT ?
                        unlink($dirName . $file);
                    }
                }
            }
            if ($deleteSelf && !rmdir($dirName)) {
                @chmod($dirName, 0777); // NT ?

                return false;
            }

            return true;
        }

        return false;
    }

    /**
     * Delete file
     *
     * @param string $file File path
     * @param array $excludeFiles Excluded files
     */
    public static function deleteFile($file, array $excludeFiles = [])
    {
        if (!\is_array($excludeFiles)) {
            $excludeFiles = [$excludeFiles];
        }

        if (file_exists($file) && is_file($file) && \in_array(basename($file), $excludeFiles, true) === false) {
            @chmod($file, 0777); // NT ?
            unlink($file);
        }
    }

    /**
     * Replace all accented chars by their equivalent non accented chars.
     *
     * @param string $str
     *
     * @return string
     */
    public static function replaceAccentedChars($str)
    {
        /* One source among others:
            http://www.tachyonsoft.com/uc0000.htm
            http://www.tachyonsoft.com/uc0001.htm
            http://www.tachyonsoft.com/uc0004.htm
        */
        $patterns = [

            /* Lowercase */
            /* a  */
            '/[\x{00E0}\x{00E1}\x{00E2}\x{00E3}\x{00E4}\x{00E5}\x{0101}\x{0103}\x{0105}\x{0430}]/u',
            /* b  */
            '/[\x{0431}]/u',
            /* c  */
            '/[\x{00E7}\x{0107}\x{0109}\x{010D}\x{0446}]/u',
            /* d  */
            '/[\x{010F}\x{0111}\x{0434}]/u',
            /* e  */
            '/[\x{00E8}\x{00E9}\x{00EA}\x{00EB}\x{0113}\x{0115}\x{0117}\x{0119}\x{011B}\x{0435}\x{044D}]/u',
            /* f  */
            '/[\x{0444}]/u',
            /* g  */
            '/[\x{011F}\x{0121}\x{0123}\x{0433}\x{0491}]/u',
            /* h  */
            '/[\x{0125}\x{0127}]/u',
            /* i  */
            '/[\x{00EC}\x{00ED}\x{00EE}\x{00EF}\x{0129}\x{012B}\x{012D}\x{012F}\x{0131}\x{0438}\x{0456}]/u',
            /* j  */
            '/[\x{0135}\x{0439}]/u',
            /* k  */
            '/[\x{0137}\x{0138}\x{043A}]/u',
            /* l  */
            '/[\x{013A}\x{013C}\x{013E}\x{0140}\x{0142}\x{043B}]/u',
            /* m  */
            '/[\x{043C}]/u',
            /* n  */
            '/[\x{00F1}\x{0144}\x{0146}\x{0148}\x{0149}\x{014B}\x{043D}]/u',
            /* o  */
            '/[\x{00F2}\x{00F3}\x{00F4}\x{00F5}\x{00F6}\x{00F8}\x{014D}\x{014F}\x{0151}\x{043E}]/u',
            /* p  */
            '/[\x{043F}]/u',
            /* r  */
            '/[\x{0155}\x{0157}\x{0159}\x{0440}]/u',
            /* s  */
            '/[\x{015B}\x{015D}\x{015F}\x{0161}\x{0441}]/u',
            /* ss */
            '/[\x{00DF}]/u',
            /* t  */
            '/[\x{0163}\x{0165}\x{0167}\x{0442}]/u',
            /* u  */
            '/[\x{00F9}\x{00FA}\x{00FB}\x{00FC}\x{0169}\x{016B}\x{016D}\x{016F}\x{0171}\x{0173}\x{0443}]/u',
            /* v  */
            '/[\x{0432}]/u',
            /* w  */
            '/[\x{0175}]/u',
            /* y  */
            '/[\x{00FF}\x{0177}\x{00FD}\x{044B}]/u',
            /* z  */
            '/[\x{017A}\x{017C}\x{017E}\x{0437}]/u',
            /* ae */
            '/[\x{00E6}]/u',
            /* ch */
            '/[\x{0447}]/u',
            /* kh */
            '/[\x{0445}]/u',
            /* oe */
            '/[\x{0153}]/u',
            /* sh */
            '/[\x{0448}]/u',
            /* shh*/
            '/[\x{0449}]/u',
            /* ya */
            '/[\x{044F}]/u',
            /* ye */
            '/[\x{0454}]/u',
            /* yi */
            '/[\x{0457}]/u',
            /* yo */
            '/[\x{0451}]/u',
            /* yu */
            '/[\x{044E}]/u',
            /* zh */
            '/[\x{0436}]/u',
            /* Uppercase */
            /* A  */
            '/[\x{0100}\x{0102}\x{0104}\x{00C0}\x{00C1}\x{00C2}\x{00C3}\x{00C4}\x{00C5}\x{0410}]/u',
            /* B  */
            '/[\x{0411}]]/u',
            /* C  */
            '/[\x{00C7}\x{0106}\x{0108}\x{010A}\x{010C}\x{0426}]/u',
            /* D  */
            '/[\x{010E}\x{0110}\x{0414}]/u',
            /* E  */
            '/[\x{00C8}\x{00C9}\x{00CA}\x{00CB}\x{0112}\x{0114}\x{0116}\x{0118}\x{011A}\x{0415}\x{042D}]/u',
            /* F  */
            '/[\x{0424}]/u',
            /* G  */
            '/[\x{011C}\x{011E}\x{0120}\x{0122}\x{0413}\x{0490}]/u',
            /* H  */
            '/[\x{0124}\x{0126}]/u',
            /* I  */
            '/[\x{0128}\x{012A}\x{012C}\x{012E}\x{0130}\x{0418}\x{0406}]/u',
            /* J  */
            '/[\x{0134}\x{0419}]/u',
            /* K  */
            '/[\x{0136}\x{041A}]/u',
            /* L  */
            '/[\x{0139}\x{013B}\x{013D}\x{0139}\x{0141}\x{041B}]/u',
            /* M  */
            '/[\x{041C}]/u',
            /* N  */
            '/[\x{00D1}\x{0143}\x{0145}\x{0147}\x{014A}\x{041D}]/u',
            /* O  */
            '/[\x{00D3}\x{014C}\x{014E}\x{0150}\x{041E}]/u',
            /* P  */
            '/[\x{041F}]/u',
            /* R  */
            '/[\x{0154}\x{0156}\x{0158}\x{0420}]/u',
            /* S  */
            '/[\x{015A}\x{015C}\x{015E}\x{0160}\x{0421}]/u',
            /* T  */
            '/[\x{0162}\x{0164}\x{0166}\x{0422}]/u',
            /* U  */
            '/[\x{00D9}\x{00DA}\x{00DB}\x{00DC}\x{0168}\x{016A}\x{016C}\x{016E}\x{0170}\x{0172}\x{0423}]/u',
            /* V  */
            '/[\x{0412}]/u',
            /* W  */
            '/[\x{0174}]/u',
            /* Y  */
            '/[\x{0176}\x{042B}]/u',
            /* Z  */
            '/[\x{0179}\x{017B}\x{017D}\x{0417}]/u',
            /* AE */
            '/[\x{00C6}]/u',
            /* CH */
            '/[\x{0427}]/u',
            /* KH */
            '/[\x{0425}]/u',
            /* OE */
            '/[\x{0152}]/u',
            /* SH */
            '/[\x{0428}]/u',
            /* SHH*/
            '/[\x{0429}]/u',
            /* YA */
            '/[\x{042F}]/u',
            /* YE */
            '/[\x{0404}]/u',
            /* YI */
            '/[\x{0407}]/u',
            /* YO */
            '/[\x{0401}]/u',
            /* YU */
            '/[\x{042E}]/u',
            /* ZH */
            '/[\x{0416}]/u',
        ];

        // ö to oe
        // å to aa
        // ä to ae

        $replacements = [
            'a',
            'b',
            'c',
            'd',
            'e',
            'f',
            'g',
            'h',
            'i',
            'j',
            'k',
            'l',
            'm',
            'n',
            'o',
            'p',
            'r',
            's',
            'ss',
            't',
            'u',
            'v',
            'w',
            'y',
            'z',
            'ae',
            'ch',
            'kh',
            'oe',
            'sh',
            'shh',
            'ya',
            'ye',
            'yi',
            'yo',
            'yu',
            'zh',
            'A',
            'B',
            'C',
            'D',
            'E',
            'F',
            'G',
            'H',
            'I',
            'J',
            'K',
            'L',
            'M',
            'N',
            'O',
            'P',
            'R',
            'S',
            'T',
            'U',
            'V',
            'W',
            'Y',
            'Z',
            'AE',
            'CH',
            'KH',
            'OE',
            'SH',
            'SHH',
            'YA',
            'YE',
            'YI',
            'YO',
            'YU',
            'ZH',
        ];

        return preg_replace($patterns, $replacements, $str);
    }

    /**
     * Format a number into a human readable format
     * e.g. 24962496 => 23.81M
     *
     * @param     $size
     * @param int $precision
     *
     * @return string
     */
    public static function formatBytes($size, $precision = 2)
    {
        if (!$size) {
            return '0';
        }
        $base = log($size) / log(1024);
        $suffixes = ['', 'k', 'M', 'G', 'T'];

        return round(1024 ** ($base - floor($base)), $precision) . $suffixes[(int)floor($base)];
    }

    /**
     * Convert a shorthand byte value from a PHP configuration directive to an integer value
     *
     * @param string $value value to convert
     *
     * @return int
     */
    public static function unformatBytes($value)
    {
        if (is_numeric($value)) {
            return $value;
        }

        $value_length = strlen($value);
        $qty = (int)substr($value, 0, $value_length - 1);
        $unit = strtolower(substr($value, $value_length - 1));
        switch ($unit) {
            case 'k':
                $qty *= 1024;
                break;
            case 'm':
                $qty *= 1048576;
                break;
            case 'g':
                $qty *= 1073741824;
                break;
        }

        return $qty;
    }

    /**
     * getOctet allow to gets the value of a configuration option in octet
     *
     * @param string $option
     *
     * @return int the value of a configuration option in octet
     */
    public static function getOctets($option)
    {
        if (preg_match('/\d+k/i', $option)) {
            return 1024 * (int)$option;
        }

        if (preg_match('/\d+m/i', $option)) {
            return 1024 * 1024 * (int)$option;
        }

        if (preg_match('/\d+g/i', $option)) {
            return 1024 * 1024 * 1024 * (int)$option;
        }

        return $option;
    }

    /**
     * Get user OS
     *
     * @return string User OS
     * @static
     */
    public static function getUserPlatform()
    {
        $user_agent = $_SERVER['HTTP_USER_AGENT'];
        $user_platform = 'unknown';

        if (false !== stripos($user_agent, 'linux')) {
            $user_platform = 'Linux';
        } elseif (preg_match('/macintosh|mac os x/i', $user_agent)) {
            $user_platform = 'Mac';
        } elseif (preg_match('/windows|win32/i', $user_agent)) {
            $user_platform = 'Windows';
        }

        return $user_platform;
    }

    /**
     * Get user browser
     *
     * @return string User browser
     * @static
     */
    public static function getUserBrowser()
    {
        $user_agent = $_SERVER['HTTP_USER_AGENT'];
        $user_browser = 'unknown';

        if (false !== stripos($user_agent, 'MSIE') && false === stripos($user_agent, 'Opera')) {
            $user_browser = 'Internet Explorer';
        } elseif (false !== stripos($user_agent, 'Firefox')) {
            $user_browser = 'Mozilla Firefox';
        } elseif (false !== stripos($user_agent, 'Chrome')) {
            $user_browser = 'Google Chrome';
        } elseif (false !== stripos($user_agent, 'Safari')) {
            $user_browser = 'Apple Safari';
        } elseif (false !== stripos($user_agent, 'Opera')) {
            $user_browser = 'Opera';
        } elseif (false !== stripos($user_agent, 'Netscape')) {
            $user_browser = 'Netscape';
        }

        return $user_browser;
    }

    /**
     * Convert wildcard IP address range notation (172.16.*.*) to CIDR netmask notation (172.16.0.0/16)
     *
     * @param string $wildcardIp Wildcard IP address range notation
     *
     * @return string CIDR netmask notation
     * @static
     */
    public static function ipWildcardToCIDR($wildcardIp)
    {
        $wildcardIp = str_replace('*', '0', $wildcardIp, $count);

        return $wildcardIp . '/' . (32 - ($count * 8));
    }

    /**
     * Test if IP address is in specified range. Range can be specified using IP1-IP2, wildcard 172.16.*.* or CIDR netmask 172.16.0.0/16
     *
     * @param string $ip Tested IP address
     * @param string $range Range
     *
     * @return bool Is IP in range
     * @static
     */
    public static function isIpInRange($ip, $range)
    {
        if (($pos = strpos($range, '-')) !== false) {
            $from = ip2long(substr($range, 0, $pos));
            $to = ip2long(substr($range, $pos + 1));
            $ipInt = ip2long($ip);

            return ($from < $to and $ipInt >= $from and $ipInt <= $to);
        }

        if (strpos($range, '*') !== false) {
            $range = self::ipWildcardToCIDR($range);
        }

        @list($net, $bits) = explode('/', $range);
        $bits = null !== $bits ? $bits : 32;
        $bitMask = -2 ** (32 - $bits) & 0x00000000FFFFFFFF;
        $netMask = ip2long($net) & $bitMask;
        $ip_bits = ip2long($ip) & $bitMask;

        return (($netMask ^ $ip_bits) === 0);
    }

    /**
     * Hash string
     *
     * @param string $data Data to hash
     *
     * @return string Hashed data
     * @static
     */
    public static function hash($data)
    {
        return hash(self::HASH_ALGORITHM, $data);
    }

    /**
     * Get files count in specified directory
     *
     * @param string $directory Directory name
     *
     * @return int Directory files count
     * @static
     */
    public static function getDirectoryFilesCount($directory)
    {
        return \count(scandir($directory, SCANDIR_SORT_NONE)) - 2;
    }

    /**
     * Whether the specified date is Czech holiday
     *
     * @param string|DateTime|int $date Any valid date or datetime
     *
     * @return bool Is holiday
     * @throws \Exception
     * @throws RuntimeException
     * @static
     */
    public static function isCzechHoliday($date)
    {
        if (!$date instanceof DateTime) {
            if (\is_int($date)) {
                $date = new DateTime('@' . $date);
            } elseif (\is_string($date)) {
                $date = new DateTime($date);
            } else {
                throw new RuntimeException(self::poorManTranslate('fts-shared', 'Invalid date format'));
            }
        }

        $holidays = ['01-01', '05-01', '05-08', '07-05', '07-06', '09-28', '10-28', '11-17', '12-24', '12-25', '12-26'];

        if (\in_array($date->format('m-d'), $holidays, true)) {
            return true;
        }

        //Easter
        $easterDays = easter_days($date->format('Y')); //Return number of days from base to easter sunday
        $easter = new DateTime($date->format('Y') . '-03-21');
        $easter->add(new \DateInterval('P' . $easterDays . 'D')); //Sunday
        $easter->sub(new \DateInterval('P2D')); //Friday
        if ($date->format('Y-m-d') === $easter->format('Y-m-d')) {
            return true;
        }
        $easter->add(new \DateInterval('P3D')); //Monday

        return ($easter->format('Y-m-d') === $date->format('Y-m-d'));
    }

    /**
     * Return greeting based on time of the day
     *
     * @param string|int|null $time Time in string format, timestamp or null for current time
     *
     * @return string Greeting
     * @static
     */
    public static function getGreeting($time = null)
    {
        if ($time === null) {
            $time = time();
        } elseif (\is_string($time)) {
            $time = strtotime($time);
        }

        switch (date('G', $time)) {
            case 0:
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
                return self::poorManTranslate('fts-shared', 'Good morning');
            case 8:
            case 9:
            case 10:
            case 11:
                //Underscore to better translate something between morning and noon
                return trim(self::poorManTranslate('fts-shared', '_Good morning'), '_');
            case 12:
                return self::poorManTranslate('fts-shared', 'Good noon');
            case 13:
            case 14:
            case 15:
            case 16:
                return self::poorManTranslate('fts-shared', 'Good afternoon');
            case 17:
            case 18:
            case 19:
                //Underscore to better translate something between noon and evening
                return trim(self::poorManTranslate('fts-shared', '_Good afternoon'), '_');
            case 20:
            case 21:
            case 22:
            case 23:
                return self::poorManTranslate('fts-shared', 'Good evening');
            default:
                return '';
        }
    }

    /**
     * Calculates distance in kilometers between two GPS coordinates
     *
     * @param float $lat1 GPS latitude 1
     * @param float $lon1 GPS longitude 1
     * @param float $lat2 GPS latitude 2
     * @param float $lon2 GPS longitude 2
     *
     * @return float Distance between points in kilometers
     * @static
     */
    public static function gpsDistance($lat1, $lon1, $lat2, $lon2)
    {
        $lat1 = deg2rad($lat1);
        $lon1 = deg2rad($lon1);
        $lat2 = deg2rad($lat2);
        $lon2 = deg2rad($lon2);

        $lonDelta = $lon2 - $lon1;
        $a = ((cos($lat2) * sin($lonDelta)) ** 2) + ((cos($lat1) * sin($lat2) - sin($lat1) * cos($lat2) * cos($lonDelta)) ** 2);
        $b = sin($lat1) * sin($lat2) + cos($lat1) * cos($lat2) * cos($lonDelta);

        return atan2(sqrt($a), $b) * 6371.0088;
    }

    /**
     * Get day of week
     * Function is compatible with SQL function WEEKDAY()
     *
     * @param DateTime|int|string|null $date Datetime object, timestamp, date in valid format or null for current date
     * @return int Day index (0 = Monday, 6 = Sunday)
     * @throws RuntimeException
     */
    public static function dow($date = null)
    {
        if ($date === null) {
            $date = new DateTime();
        } elseif (!$date instanceof DateTime) {
            if (\is_int($date)) {
                $date = new DateTime('@' . $date);
            } elseif (\is_string($date)) {
                $date = new DateTime($date);
            } else {
                throw new RuntimeException(self::poorManTranslate('fts-shared', 'Invalid date format'));
            }
        }

        return (int)$date->format('N') - 1;
    }

    /**
     * Translate function with automatic Yii detection
     *
     * @param $category
     * @param $text
     * @param array $params
     * @return string
     */
    public static function poorManTranslate($category, $text, array $params = [])
    {
        if (class_exists('Yii')) {
            return \Yii::t($category, $text, $params);
        }

        $pos = strrpos($category, '/');
        $category = $pos === false ? $category : substr($category, $pos + 1);
        $translation = @include 'messages/cs/' . $category . '.php';
        if ($translation !== null && \is_array($translation) && array_key_exists($text, $translation)) {
            $keys = array_keys($params);
            array_walk($keys, function (&$v) {
                $v = '{' . $v . '}';
            });

            return str_replace(array_values($params), $keys, $translation[$text]);
        }

        return $text;
    }

    /**
     * Return a friendly url made from the provided string
     *
     * @param string $str Input string
     * @param bool $allowUnicodeChars If unicode characters are allowed in URL
     * @return string Link rewrite
     */
    public static function linkRewrite($str, $allowUnicodeChars = false)
    {
        if (!\is_string($str)) {
            return false;
        }

        $str = trim($str);

        if (\function_exists('mb_strtolower')) {
            $str = mb_strtolower($str, 'utf-8');
        }
        if (!$allowUnicodeChars) {
            $str = self::replaceAccentedChars($str);
        }

        // Remove all non-whitelist chars.
        if ($allowUnicodeChars) {
            $str = preg_replace('/[^a-zA-Z0-9\s\'\:\/\[\]\-\pL]/u', '', $str);
        } else {
            $str = preg_replace('/[^a-zA-Z0-9\s\'\:\/\[\]\-]/', '', $str);
        }

        $str = preg_replace('/[\s\'\:\/\[\]\-]+/', ' ', $str);
        $str = str_replace([' ', '/'], '-', $str);

        // If it was not possible to lowercase the string with mb_strtolower, we do it after the transformations.
        // This way we lose fewer special chars.
        if (!\function_exists('mb_strtolower')) {
            $str = strtolower($str);
        }

        return $str;
    }

    /**
     * Get birth date from the birth number
     *
     * @param string $no Birth number
     * @return DateTime Birth date or null
     */
    public static function getDateFromBirthNumber($no)
    {
        if (!preg_match('#^\s*(\d\d)(\d\d)(\d\d)[ /]*(\d\d\d)(\d?)\s*$#', $no, $matches)) {
            return null;
        }

        list(, $year, $month, $day, $ext, $c) = $matches;

        if ($c === '') {
            $year += $year < 54 ? 1900 : 1800;
        } else {
            $mod = ($year . $month . $day . $ext) % 11;
            if ($mod === 10) {
                $mod = 0;
            }
            if ($mod !== (int)$c) {
                return null;
            }
            $year += $year < 54 ? 2000 : 1900;
        }
        if ($year > 2003) {
            if ($month > 70) {
                $month -= 70;
            }
            if ($month > 20 && $month < 50) {
                $month -= 20;
            }
        }
        if ($month > 50) {
            $month -= 50;
        }

        return new DateTime(sprintf('%04d-%02d-%02d', $year, $month, $day));
    }

    /**
     * Generate random PIN based on
     *
     * @param string $salt Salt
     * @param int $length PIN length
     * @param bool $useMinutes Generate different PIN each minute (default is each hour)
     * @return int Pin
     */
    public static function generatePin($salt, $length = 6, $useMinutes = false)
    {
        $seed = sha1($salt . (new \DateTime('now', new \DateTimeZone('Europe/Prague')))->format('Ymd' . ($useMinutes ? 'i' : '')), true);
        for ($i = 0; $i <= (new \DateTime('now', new \DateTimeZone('Europe/Prague')))->format('G'); $i++) {
            $seed = sha1($seed . $i);
        }

        $data = unpack('V1/V2', $seed);
        $data[1] = $data[1] < 0 ? $data[1] * -1 : $data[1];
        $data[2] = $data[2] < 0 ? $data[2] * -1 : $data[2];
        $mask = $data[1] ^ $data[2];

        if ($mask % 1000000 === 0 || $mask % 1000000 === 999999) {
            return self::generatePin($salt . $seed, $length, $useMinutes);
        }

        return round((((float)($mask % 1000000) - 0.5 + ((float)($mask % 200) / 199)) / 999999) * (((10 ** $length) - 1) - (10 ** ($length - 1))) + (10 ** ($length - 1)));
    }

    /**
     * Send message to HipChat room
     *
     * @param string $room Room name or ID
     * @param string $token Token
     * @param string $text Message body
     * @param bool $notify Determine if should notify users
     * @param string $format Format of message
     */
    public static function sendHipChatMessage($room, $token, $text, $notify = true, $format = 'text')
    {
        $session = curl_init();
        curl_setopt($session, CURLOPT_URL, 'https://api.hipchat.com/v2/room/' . $room . '/notification?auth_token=' . $token);
        curl_setopt($session, CURLOPT_POST, 1);
        curl_setopt(
            $session,
            CURLOPT_POSTFIELDS,
            http_build_query(
                [
                    'message' => $text,
                    'message_format' => $format,
                    'notify' => $notify,
                ]
            )
        );

        curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
        curl_exec($session);
        curl_close($session);
    }

    /**
     * Converts pretty time format (2w 1d 5h 30m) to seconds
     *
     * @param string $time Pretty time format
     * @param int $dayLength Length of the work day in seconds
     * @param int $weekLength Length of the work week is seconds
     * @return int Time in seconds
     */
    public static function prettyTimeToSeconds($time, $dayLength = 28800, $weekLength = 144000)
    {
        $minus = 0 === strpos($time, '-');
        if ($minus) {
            $time = substr($time, 1);
        }
        if (preg_match('/^(?:(?<weeks>\d+)w\s*)?(?:(?<days>\d+)d\s*)?(?:(?<hours>\d+)h\s*)?(?:(?<minutes>\d+)m\s*)?(?:(?<seconds>\d+)s\s*)?$/', $time, $matches)) {
            return
                ($minus ? -1 : 1) *
                (!empty($matches['weeks']) ? (int)$matches['weeks'] * $weekLength : 0) +
                (!empty($matches['days']) ? (int)$matches['days'] * $dayLength : 0) +
                (!empty($matches['hours']) ? (int)$matches['hours'] * 3600 : 0) +
                (!empty($matches['minutes']) ? (int)$matches['minutes'] * 60 : 0) +
                (!empty($matches['seconds']) ? (int)$matches['seconds'] : 0);
        }

        return 0;
    }

    /**
     * Converts seconds to pretty time format (2w 1d 5h 30m)
     *
     * @param int $seconds Time in seconds
     * @param int $dayLength Length of the work day in seconds
     * @param int $weekLength Length of the work week is seconds
     * @return string Pretty time format
     */
    public static function secondsToPrettyTime($seconds, $dayLength = 28800, $weekLength = 144000)
    {
        $minus = $seconds < 0;
        $seconds = (int)abs($seconds);
        if ($seconds === 0) {
            return '0s';
        }

        $out = [];
        $units = ['w' => $weekLength, 'd' => $dayLength, 'h' => 3600, 'm' => 60, 's' => 1];
        foreach ($units as $sign => $value) {
            if ($seconds < $value) {
                continue;
            }

            $i = floor($seconds / $value);
            $out[] = $i . $sign;
            $seconds -= ($i * $value);
        }

        return ($minus ? '-' : '') . implode(' ', $out);
    }

    /**
     * Get difference between two dates in seconds
     *
     * @param string $start Start date in DateTime compatible syntax
     * @param string $end End date in DateTime compatible syntax
     * @param bool $absolute Return absolute (positive) number
     * @param string $timezone Timezone @see http://php.net/manual/en/timezones.php
     * @return int Difference in seconds
     */
    public static function secondsBetweenDates($start, $end, $absolute = true, $timezone = 'Europe/Prague')
    {
        $timezoneObj = new \DateTimeZone($timezone);
        $date = new DateTime($end, $timezoneObj);
        $diff = $date->diff(new DateTime($start, $timezoneObj), $absolute);

        return ($diff->invert ? -1 : 1) * (($diff->days * 86400) + ($diff->h * 3600) + ($diff->i * 60) + $diff->s);
    }

    /**
     * Counts number of seconds between two dates taking into account only working days
     *
     * @param string $dateFrom Start date in DateTime compatible syntax
     * @param string $dateTo End date in DateTime compatible syntax
     * @param string $workDayFrom Working hours start time in DateTime compatible syntax
     * @param string $workDayTo Working hours end time in DateTime compatible syntax
     * @param bool $weekends Whether to count weekends as a working days
     * @param bool $holidays Whether to count holidays as a working days
     * @param string $timeZone Timezone @see http://php.net/manual/en/timezones.php
     * @return int Difference in seconds
     *
     * @throws \Exception
     * @throws RuntimeException
     */
    public static function secondsBetweenWorkingDays($dateFrom, $dateTo, $workDayFrom, $workDayTo, $weekends = false, $holidays = false, $timeZone = 'Europe/Prague')
    {
        $timeZoneObj = new \DateTimeZone($timeZone);
        $dateFromObj = new DateTime($dateFrom, $timeZoneObj);
        $dateToObj = new DateTime($dateTo, $timeZoneObj);
        $workDayFromObj = new DateTime($workDayFrom, $timeZoneObj);
        $workDayToObj = new DateTime($workDayTo, $timeZoneObj);
        $workDayLength = self::secondsBetweenDates($workDayFrom, $workDayTo, true, $timeZone);

        $period = new \DatePeriod(
            new DateTime($dateFromObj->format('Y-m-d 00:00:00'), $timeZoneObj),
            new \DateInterval('P1D'),
            new DateTime($dateToObj->format('Y-m-d 23:59:59'), $timeZoneObj)
        );

        $workedTime = 0;
        foreach ($period as $date) {
            /** @var DateTime $date */
            if ((!$weekends && (int)$date->format('N') > 5) || (!$holidays && self::isCzechHoliday($date))) {
                continue;
            }

            if ($date->format('Y-m-d') === $dateFromObj->format('Y-m-d')) {
                //First day
                $endOfDay = new DateTime($date->format('Y-m-d ' . $workDayToObj->format('H:i:s')), $timeZoneObj);

                if ($dateFromObj < $endOfDay && $dateFromObj->format('Y-m-d') === $dateToObj->format('Y-m-d')) {
                    //Only one day - before workday end
                    $diff = $dateToObj->diff($dateFromObj)->format('%H:%I:%S');
                } else {
                    $diff = $endOfDay->diff($dateFromObj)->format('%H:%I:%S');
                }
                $diff = explode(':', $diff);
                $diff = $diff[0] * 3600 + $diff[1] * 60 + $diff[0];
                $workedTime += $diff;
            } elseif ($date->format('Y-m-d') === $dateToObj->format('Y-m-d')) {
                //Last day
                $startOfDay = new DateTime($date->format('Y-m-d ' . $workDayFromObj->format('H:i:s')), $timeZoneObj);
                if ($dateToObj > $startOfDay) {
                    $diff = $startOfDay->diff($dateToObj)->format('%H:%I:%S');
                    $diff = explode(':', $diff);
                    $diff = $diff[0] * 3600 + $diff[1] * 60 + $diff[0];
                    $workedTime += $diff;
                }
            } else {
                //Full day
                $workedTime += $workDayLength;
            }
        }

        return $workedTime;
    }

    /**
     * Transpose an array
     *
     * @param array $array Array to transpose
     * @return array
     */
    public static function transpose($array)
    {
        array_unshift($array, null);

        return \call_user_func_array('array_map', $array);
    }

    /**
     * Get max count of multiple arrays
     *
     * @param array ...$array Array of arrays to count
     * @return int Max item count
     */
    public static function maxCount()
    {
        $array = \func_get_args();
        if (!\is_array($array)) {
            return 0;
        }

        $maxCnt = 0;
        foreach ($array as $item) {
            if (!\is_array($item)) {
                continue;
            }
            $cnt = \count($item);
            $maxCnt = $cnt > $maxCnt ? $cnt : $maxCnt;
        }

        return $maxCnt;
    }

    /**
     * Extend array to desired size by filling it with fill data
     *
     * @param array $array Array to fill
     * @param int $size Desired size
     * @param mixed $fill Fill data
     */
    public static function fillToSize(&$array, $size, $fill)
    {
        $cnt = \count($array);
        if ($cnt >= $size) {
            return;
        }
        $array = array_merge($array, array_fill($cnt + 1, $size - $cnt, $fill));
    }

    /**
     * Converts two digit country code to three digit one or return false if code does not exists
     *
     * @param string $code Two digit country code
     * @return string|false
     */
    public static function countryCodeTwoToThree($code)
    {
        $codes = array_flip(self::$_countryCodes);
        if (!array_key_exists($code, $codes)) {
            return false;
        }

        return $codes[$code];
    }

    /**
     * Converts three digit country code to two digit one or return false if code does not exists
     *
     * @param string $code Three digit country code
     * @return string|false
     */
    public static function countryCodeThreeToTwo($code)
    {
        if (!array_key_exists($code, self::$_countryCodes)) {
            return false;
        }

        return self::$_countryCodes[$code];
    }
}


<?php

namespace Havvg\Component\Search\Result;

class ResultCollection implements \Iterator, \Countable
{
    /**
     * @var ResultInterface[]
     */
    protected $results = array();

    /**
     * Add another result to this collection.
     *
     * @param ResultInterface $result
     *
     * @return ResultCollection
     */
    public function add(ResultInterface $result)
    {
        $this->results[] = $result;

        return $this;
    }

    /**
     * Filter the currect set of results by the given scope.
     *
     * @param ScopeInterface $scope
     *
     * @return ResultCollection|ResultInterface[]
     */
    public function filter(ScopeInterface $scope)
    {
        $filtered = new self;

        foreach ($this as $eachResult) {
            /* @var $eachResult ResultInterface */
            if ($eachResult->getScope()->isEqualTo($scope)) {
                $filtered->add($eachResult);
            }
        }

        return $filtered;
    }

    // Iterator implementation

    public function current()
    {
        return current($this->results);
    }

    public function next()
    {
        next($this->results);
    }

    public function key()
    {
        return key($this->results);
    }

    public function valid()
    {
        return isset($this->results[$this->key()]);
    }

    public function rewind()
    {
        reset($this->results);
    }

    // Countable implementation

    public function count()
    {
        return count($this->results);
    }
}