<?php
declare(strict_types = 1);

/**
 * An advanced PHP download wrapper.
 *
 * This class provides a PHP wrapper between the client requests and the files on the server.
 *
 * It handles most mime types intelligently and properly responds to client requests for
 * partial content as well as requests to see if the client cached copy of the file is still
 * valid.
 *
 * @package AWonderPHP\FileWrapper
 * @author  Alice Wonder <paypal@domblogger.net>
 * @license https://opensource.org/licenses/MIT MIT
 * @link    https://github.com/AliceWonderMiscreations/FileWrapper
 */
/*
 +-----------------------------------------------------------------------+
 |                                                                       |
 | Copyright (c) 2017-2018 Alice Wonder Miscreations                     |
 |  May be used under terms of MIT license                               |
 |                                                                       |
 | Dedicated to Empress Andi of www.phonesexprincessblog.com             |
 |                                                                       |
 +-----------------------------------------------------------------------+

*/

namespace AWonderPHP\FileWrapper;

/**
 * This class provides a PHP wrapper between the client requests and the files on the server.
 *
 * It handles most mime types intelligently and properly responds to client requests for
 * partial content as well as requests to see if the client cached copy of the file is still
 * valid.
 */
class FileWrapper
{
    /**
     * When sent to true, an internal error header will be sent.
     *
     * @var bool
     */
    protected $internalError = false;

    /**
     * Array of known valid MIME types.
     *
     * If one of these mime types is specified to the constructor, the class will assume it
     * to be valid and will not waste time trying to detect the mime type.
     *
     * If extending this class and adding to the array, have the constructor of your class
     * add to the array *before* running the parent constructor.
     *
     * Mime types not listed here are supported, this list just lets the class know what it
     * can skip the mime sniff for.
     *
     * @var array
     */
    protected $validMime = array('application/x-bzip',
                                 'application/x-bzip2',
                                 'application/java-archive',
                                 'application/javascript',
                                 'application/json',
                                 'application/json-p',
                                 'application/xml',
                                 'application/msword',
                                 'application/ogg',
                                 'application/pdf',
                                 'application/vnd.ms-fontobject',
                                 'application/zip',
                                 'application/x-7z-compressed',
                                 'application/x-rar-compressed',
                                 'application/x-tar',
                                 'audio/3gpp',
                                 'audio/3gpp2',
                                 'audio/aiff',
                                 'audio/flac',
                                 'audio/mp4',
                                 'audio/mpeg',
                                 'audio/ogg',
                                 'audio/x-wav',
                                 'audio/webm',
                                 'audio/x-matroska',
                                 'font/otf',
                                 'font/sfnt',
                                 'font/ttf',
                                 'font/woff',
                                 'font/woff2',
                                 'image/bmp',
                                 'image/gif',
                                 'image/jpeg',
                                 'image/png',
                                 'image/svg+xml',
                                 'image/tiff',
                                 'image/webp',
                                 'text/css',
                                 'text/plain',
                                 'text/vtt',
                                 'video/3gpp',
                                 'video/3gpp2',
                                 'video/mp4',
                                 'video/ogg',
                                 'video/x-matroska',
                                 'video/webm');

    /**
     * MIME types we know are text files but do not start with "text/" or end with "+xml".
     *
     * @var array
     */
    protected $textMIME = array('application/javascript',
                                'application/json',
                                'application/json-p',
                                'application/xml');

    /**
     * The chunk size to read files in bytes.
     *
     * @var int
     */
    protected $chunksize = 1024;

    /**
     * This gets set to 'bytes' by checkFullFile() when the file is larger than the chunksize.
     *
     * @var string
     */
    protected $ranges = 'none';

    /**
     * Set to true by checkFullFile() if range request is bad.
     *
     * @var bool
     */
    protected $badrange = false;

    /**
     * The request headers sent by the client. Set by constructor.
     *
     * @var array
     */
    protected $REQHEADERS = array();

    /**
     * Full path on file system to static file being served. When null, 404 is sent.
     *
     * @var null|string
     */
    protected $path = null;

    /**
     * The name of the requested file as the user would see it. Only really
     * matters for files that are downloaded. Set by constructor.
     *
     * @var null|string
     */
    protected $request = null;

    /**
     * MIME type to send with the file. When null, the class will attempt to figure out the
     * correct MIME to send. Set by validMimeType() method.
     *
     * @var null|string
     */
    protected $mime = null;

    /**
     * If the class detects a text file, it is served differently.
     *
     * @var bool
     */
    protected $istext = false;

    /**
     * In some cases we need to specify access-control-allow-origin header.
     *
     * @var null|string
     */
    protected $allowOrigin = null;

    /**
     * When set to true by cacheCheck() a 304 Not Modified is sent
     *
     * @var bool
     */
    protected $cacheok = false;

    /**
     * Gets set to false by checkFullFile() for partial content response.
     *
     * @var bool
     */
    protected $fullfile = true;

    /**
     * Set by the setFileProperties() method
     *
     * @var null|int
     */
    protected $filesize = null;

    /**
     * First byte to read when sending content. Set by the checkFullFile() method.
     *
     * @var int
     */
    protected $start = 0;

    /**
     * Last byte to read when sending content. Set by the checkFullFile() method.
     *
     * @var int
     */
    protected $end = 0;

    /**
     * Total bytes to read when sending content. Set by the checkFullFile() method.
     *
     * @var int
     */
    protected $total = 0;

    /**
     * UNIX timestamp for last time file was modified. Set by setFileProperties() method.
     *
     * @var int
     */
    protected $timestamp = 0;

    /**
     * String representation of last time file was modified. Set by setFileProperties() method.
     *
     * @var string
     */
    protected $lastmod = 'Thu, 01 Jan 1970 00:00:00 GMT';

    /**
     * Unique identifier for the current version of the file. Set by the setFileProperties() method.
     *
     * @var null|string
     */
    protected $etag = null;

    /**
     * Whether or not to send as attachment. Set by constructor.
     *
     * @bool
     */
    protected $attachment = false;

    /**
     * Number of seconds the client should cache the file for. Set by the constructor.
     *
     * @var int
     */
    protected $maxage = 0;

// Text file specific - set these in an extended class

    /**
     * Should we convert non-utf8 to utf8? Set by extending class.
     *
     * @var bool
     */
    protected $toUTF8 = false;

    /**
     * Should we minify JS/CSS? Set by extending class.
     *
     * @var bool
     */
    protected $minify = false;

    /**
     * Charset list - set by extending class.
     *
     * @var string
     */
    protected $charsetlist = 'auto';

    /**
     * Character encoding. Set by the cleanSource() method.
     *
     * @var null|string
     */
    protected $charEnc='';

    /**
     * Does not seem to be used, will be removed if I can not find why it is here.
     *
     * @var null
     */
    protected $charset = null;

// Protected Methods.

    /**
     * Sends a 500 internal server error.
     *
     * @return void
     */
    protected function sendInternalError(): void
    {
        header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
        exit;
    }//end sendInternalError()

    /**
     * Set the request property
     *
     * @param string|null $request The name of file as user sees it, only matter for file
     *                             download.
     *
     * @psalm-suppress RedundantConditionGivenDocblockType
     *
     * @return void
     */
    protected function setRequest($request): void
    {
        if (is_null($this->path)) {
            throw \AWonderPHP\FileWrapper\NullPropertyException::propertyIsNull('path');
        }
        if (! is_null($request)) {
            if (! is_string($request)) {
                throw \AWonderPHP\FileWrapper\TypeErrorException::requestWrongType($request);
            }
            $request = trim(basename($request));
            if (strlen($request) === 0) {
                $request = null;
            }
        }
        if (is_null($request)) {
            $request = trim(basename($this->path));
        }
        $this->request = $request;
    }//end setRequest()

    /**
     * Sets the maxage property
     *
     * @param int|string|\DateInterval $maxage The maximum age the client should cache the
     *                                         file.
     *
     * @psalm-suppress RedundantConditionGivenDocblockType
     *
     * @return void
     */
    protected function setMaxAge($maxage): void
    {
        $now = time();
        if (is_int($maxage)) {
            if ($maxage > $now) {
                $this->maxage = $maxage - $now;
                return;
            }
            if ($maxage >= 0) {
                $this->maxage = $maxage;
                return;
            }
            throw \AWonderPHP\FileWrapper\InvalidArgumentException::negativeMaxAge();
        }
        if ($maxage instanceof \DateInterval) {
            $dt = new \DateTime();
            $dt->add($maxage);
            $ts = $dt->getTimestamp();
            $seconds = $ts - $now;
            if ($seconds >= 0) {
                $this->maxage = $seconds;
                return;
            }
            throw \AWonderPHP\FileWrapper\InvalidArgumentException::negativeMaxAge();
        }
        if (! is_string($maxage)) {
            throw \AWonderPHP\FileWrapper\TypeErrorException::maxageWrongType($maxage);
        }
        if ($tstamp = strtotime($maxage, time())) {
            $seconds = $tstamp - $now;
            if ($seconds >= 0) {
                $this->maxage = $seconds;
                return;
            }
            throw \AWonderPHP\FileWrapper\InvalidArgumentException::negativeMaxAge();
        }
        throw \AWonderPHP\FileWrapper\InvalidArgumentException::invalidDateString();
    }//end setMaxAge()

    /**
     * Fixes common programmer typos and less than precise mime types from fileinfo.so.
     *
     * @param string $input The MIME type to be checked and possibly fixed.
     *
     * @return string Either the input MIME type or a correction of it.
     */
    protected function mimeTypoFix($input): string
    {
        if (is_null($this->path)) {
            throw \AWonderPHP\FileWrapper\NullPropertyException::propertyIsNull('path');
        }
        $input = trim($input);
        $mime = $input;
        switch ($input) {
            case 'application/font-woff':
                $mime = 'font/woff';
                break;
            case 'audio/m4a':
                $mime = 'audio/mp4';
                break;
            case 'audio/matroska':
                $mime = 'audio/x-matroska';
                break;
            case 'audio/mp3':
                $mime = 'audio/mpeg';
                break;
            case 'audio/x-aiff':
                $mime = 'audio/aiff';
                break;
            case 'audio/wav':
                $mime = 'audio/x-wav';
                break;
            case 'audio/x-m4a':
                $mime = 'audio/mp4';
                break;
            case 'audio/x-matroska':
                $arr = explode('.', $this->path);
                $ext = strtolower(end($arr));
                switch ($ext) {
                    case 'weba':
                        $mime = 'audio/webm';
                        break;
                }
                break;
            case 'image/jpg':
                $mime = 'image/jpeg';
                break;
            case 'image/tif':
                $mime = 'image/tiff';
                break;
            case 'video/matroska':
                $mime = 'video/x-matroska';
                // test the extension too so no break
            case 'video/x-matroska':
                $arr = explode('.', $this->path);
                $ext = strtolower(end($arr));
                switch ($ext) {
                    case 'mka':
                        $mime = 'audio/x-matroska';
                        break;
                    case 'weba':
                        $mime = 'audio/webm';
                        break;
                    case 'webm':
                        $mime = 'video/webm';
                        break;
                    case 'webm2':
                        $mime = 'video/webm';
                        break;
                }
                break;
            case 'application/octet-stream':
                $arr = explode('.', $this->path);
                $ext = strtolower(end($arr));
                switch ($ext) {
                    case 'opus':
                        $mime = 'audio/ogg';
                        break;
                }
                break;
            case 'text/plain':
                $arr = explode('.', $this->path);
                $ext = strtolower(end($arr));
                switch ($ext) {
                    case 'js':
                        $mime = 'application/javascript';
                        break;
                    case 'css':
                        $mime = 'text/css';
                        break;
                    case 'vtt':
                        $mime = 'text/vtt';
                        break;
                }
                break;
        }
        return $mime;
    }//end mimeTypoFix()

    /**
     * Sets the MIME type the file will be served with. Does not compensate for all errors in
     * declared MIME types but will compensate for some.
     *
     * @param null|string $input A MIME type.
     *
     * @psalm-suppress RedundantConditionGivenDocblockType
     *
     * @return void
     */
    protected function validMimeType($input): void
    {
        if (is_null($this->path)) {
            throw \AWonderPHP\FileWrapper\NullPropertyException::propertyIsNull('path');
        }
        if (! is_null($input)) {
            if (! is_string($input)) {
                throw \AWonderPHP\FileWrapper\TypeErrorException::mimeWrongType($input);
            }
            $input = trim(strtolower($input));
            try {
                $input = $this->mimeTypoFix($input);
            } catch (\ErrorException $e) {
                error_log($e->getMessage());
                $this->internalError = true;
                return;
            }
            if (in_array($input, $this->validMime)) {
                $this->mime = $input;
                return;
            }
        }
        // do what we can
        if ($input == "application/octet-stream") {
            $input = null;
        }
        $error = false;
        if (! is_null($input)) {
            $arr = explode('/', $input);
            if (count($arr) != 2) {
                $error = true;
            } elseif (! in_array($arr[0], array('application',
                                                'audio',
                                                'font',
                                                'image',
                                                'multipart',
                                                'text',
                                                'video'))) {
                $error = true;
            }
            if ($error) {
                if (function_exists('finfo_open')) {
                    if ($finfo = finfo_open(FILEINFO_MIME_TYPE)) {
                        if ($mime = finfo_file($finfo, $this->path)) {
                            try {
                                $this->mime = $this->mimeTypoFix($mime);
                            } catch (\ErrorException $e) {
                                error_log($e->getMessage());
                                $this->internalError = true;
                            }
                            finfo_close($finfo);
                            return;
                        }
                        finfo_close($finfo);
                    }
                }
            }
        }
        if (is_null($input)) {
            $input = 'application/octet-stream';
        }
        $this->mime = $input;
    }//end validMimeType()

    /**
     * Attempts to detect if serving a text file.
     *
     * @return void
     */
    protected function textCheck(): void
    {
        if (is_null($this->mime)) {
            throw \AWonderPHP\FileWrapper\NullPropertyException::propertyIsNull('mime');
        }
        $test = substr($this->mime, 0, 5);
        if ($test === 'text/') {
            $this->istext = true;
            return;
        }
        $test = substr($this->mime, -4);
        if ($test === "+xml") {
            $this->istext = true;
            return;
        }
        if (in_array($this->mime, $this->textMIME)) {
            $this->istext = true;
        }
    }//end textCheck()

    /**
     * If serving a font, sets the allowOrigin to * which browsers need.
     *
     * You can use the PHP wrapper calling this class to send a 403 if the referer is not an
     * approved domain.
     *
     * @return void
     */
    protected function fontCheck(): void
    {
        if (is_null($this->mime)) {
            throw \AWonderPHP\FileWrapper\NullPropertyException::propertyIsNull('mime');
        }
        $test = substr($this->mime, 0, 5);
        if ($test == 'font/') {
            $this->allowOrigin = "*";
            return;
        }
        if (strcmp($this->mime, 'application/vnd.ms-fontobject') == 0) {
            $this->allowOrigin = "*";
        }
    }//end fontCheck()

    /**
     * Are we serving the full file or just part of it?
     *
     * @return void
     */
    protected function checkFullFile(): void
    {
        if (is_null($this->filesize)) {
            throw \AWonderPHP\FileWrapper\NullPropertyException::propertyIsNull('filesize');
        }
        $this->end = $this->filesize - 1;
        $this->total = $this->filesize;
        if ($this->istext) {
            return;
        }
        if ($this->filesize > $this->chunksize) {
            $this->ranges = 'bytes';
            if (isset($_SERVER['HTTP_RANGE'])) {
                list($size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2);
                if ($size_unit == 'bytes') {
                    $this->fullfile = false;
                    //below causes noise in log, so do w/ if then instead. Not using extra_ranges anyway
                    //list($range, $extra_ranges) = explode(',', $range_orig, 2);
                    $arr = explode(',', $range_orig, 2);
                    if (isset($arr[1])) {
                        $extra_ranges = $arr[1];
                    } else {
                        $extra_ranges = null;
                    }
                    $range = $arr[0];
                    list($start, $end) = explode('-', $range, 2);
                } else {
                    $this->badrange = true;
                }
                $end = (empty($end)) ? ($this->filesize - 1) : min(abs(intval($end)), ($this->filesize - 1));
                $start = (empty($start) || $end < abs(intval($start))) ? 0 : max(abs(intval($start)), 0);
                if ($start > 0 || $end < ($this->filesize - 1)) {
                    $this->start = $start;
                    $this->end = $end;
                    $this->total = $this->end - $this->start + 1;
                }
            }
        }
    }//end checkFullFile()

    /**
     * Sets class properties needed to generate a unique Etag and to serve the file.
     *
     * @return void
     */
    protected function setFileProperties(): void
    {
        if (is_null($this->path)) {
            throw \AWonderPHP\FileWrapper\NullPropertyException::propertyIsNull('path');
        }
        date_default_timezone_set('UTC');
        $this->filesize = filesize($this->path);
        $this->timestamp = filemtime($this->path);
        $this->lastmod = preg_replace('/\+0000$/', 'GMT', date('r', $this->timestamp));
        $inode = fileinode($this->path);
        // The f4a24ef etc strings - my understand in that
        //  because of caching proxies, the Etag needs to be
        //  different for different types of transfer encoding
        //  (e.g. brotli, gzip, deflate) or content could be
        //  delivered to a client that the client can not
        //  decompress.
        // This class assumes only text files will potentially
        //  be further compressed when serving.
        $etagEnd = 'f4a24ef';
        if ($this->istext) {
            if (ini_get('zlib.output_compression')) {
                $accept = 'identity';
                if ($this->minify) {
                    $etagEnd = '3d';
                } else {
                    $etagEnd = '4c';
                }
                if (isset($this->REQHEADERS['accept-encoding'])) {
                    $T = trim(strtolower($this->REQHEADERS['accept-encoding']));
                    if (strpos($T, 'gzip') !== false) {
                        $accept = 'gzip';
                    } elseif (strpos($T, 'deflate') !== false) {
                        $accept = 'deflate';
                    }
                }
                switch ($accept) {
                    case 'gzip':
                        $etagEnd .= '7aa23';
                        break;
                    case 'deflate':
                        $etagEnd .= '98db4';
                        break;
                    default:
                        $etagEnd .= 'c41ca';
                }
            }
        }
        $this->etag = sprintf("%x-%x-%x-%s", $inode, $this->filesize, $this->timestamp, $etagEnd);
        try {
            $this->checkFullFile();
        } catch (\ErrorException $e) {
            error_log($e->getMessage());
            $this->internalError = true;
            return;
        }
    }//end setFileProperties()

    /**
     * Is the browser cached version okay?
     *
     * @return void
     */
    protected function cacheCheck()
    {
        if (is_null($this->etag)) {
            throw \AWonderPHP\FileWrapper\NullPropertyException::propertyIsNull('etag');
        }
        if (isset($this->REQHEADERS['if-none-match'])) {
            $reqETAG=trim($this->REQHEADERS['if-none-match'], '\'"');
            if (strcmp($reqETAG, $this->etag) == 0) {
                $this->cacheok=true;
            }
        } elseif (isset($this->REQHEADERS['if-modified-since'])) {
            $reqLMOD=strtotime(trim($this->REQHEADERS['if-modified-since']));
            if ($reqLMOD == $this->timestamp) {
                $this->cacheok=true;
            }
        }
    }//end cacheCheck()

    /**
     * Reads the file or portion of binary file from the filesystem and sends it to the
     * client.
     *
     * @return void
     */
    protected function readFromFilesystem(): void
    {
        if (is_null($this->path)) {
            throw \AWonderPHP\FileWrapper\NullPropertyException::propertyIsNull('path');
        }
        $chunk = $this->chunksize;
        $sent = 0;
        $fp = fopen($this->path, 'rb');
        fseek($fp, $this->start);
        while (!feof($fp)) {
            if (($this->total - $sent) < $chunk) {
                $chunk = $this->total - $sent;
            }
            print(fread($fp, $chunk));
            flush();
            ob_flush();
            $sent = $sent + $chunk;
            if ($sent >= $this->total) {
                break;
            }
        }
        fclose($fp);
    }//end readFromFilesystem()

    /**
     * Send headers and then the file, for binary file.
     *
     * @return bool True on success, False on failure.
     */
    protected function sendContent(): bool
    {
        if (is_null($this->path)) {
            $this->sendInternalError();
            return false;
        }
        if (is_null($this->request)) {
            $this->sendInternalError();
            return false;
        }
        if (is_null($this->etag)) {
            $this->sendInternalError();
            return false;
        }
        if (is_null($this->mime)) {
            $this->sendInternalError();
            return false;
        }
        if (is_null($this->filesize)) {
            $this->sendInternalError();
            return false;
        }
        //make sure zlib output compression turned off
        ini_set("zlib.output_compression", "Off");

        if ($this->attachment) {
            header('Content-Description: File Transfer');
            header('Content-Disposition: attachment; filename="' . $this->request . '"');
        }
        if ($this->maxage == 0) {
            header('Cache-Control: must-revalidate');
        } else {
            header('Cache-Control: max-age=' . $this->maxage);
        }
        header('Last-Modified: ' . $this->lastmod);
        header('ETag: "' . $this->etag . '"');
        header('Accept-Ranges: ' . $this->ranges);
        if ($this->fullfile) {
            header('Content-Length: ' . $this->filesize);
        } else {
            header('HTTP/1.1 206 Partial Content');
            header('Content-Length: ' . $this->total);
            header('Content-Range: bytes ' . $this->start . '-' . $this->end . '/' . $this->filesize);
        }
        if (! is_null($this->allowOrigin)) {
            header('access-control-allow-origin: ' . $this->allowOrigin);
        }
        header('Content-Type: ' . $this->mime);
        header_remove('X-Powered-By');
        try {
            $this->readFromFilesystem();
        } catch (\Error $e) {
            error_log($e->getMessage());
            $this->sendInternalError();
            return false;
        }
        return true;
    }//end sendContent()

// Text file specific Protected Methods

    /**
     * Cleans up line breaks and attempts to convert files to UTF8.
     *
     * By default this is turned off, extend class to use.
     *
     * @param string $content The text to be cleaned and converted.
     *
     * @return string The cleaned content.
     */
    protected function cleanSource($content): string
    {
        if (is_null($this->path)) {
            throw \AWonderPHP\FileWrapper\NullPropertyException::propertyIsNull('path');
        }
        //nuke BOM when we definitely have UTF8
        $bom = pack('H*', 'EFBBBF');
        //DOS to UNIX
        $content = str_replace("\r\n", "\n", $content);
        //Classic Mac to UNIX
        $content = str_replace("\r", "\n", $content);
        if (function_exists('mb_detect_encoding')) {
            if (mb_detect_encoding($content, 'UTF-8', true)) {
                $this->charEnc="UTF-8";
                $content = preg_replace("/^$bom/", '', $content);
            } elseif ($ENC = mb_detect_encoding($content, $this->charsetlist, true)) {
                $this->charEnc=$ENC;
                if (function_exists('iconv')) {
                    if ($new = iconv($ENC, 'UTF-8', $content)) {
                        $this->charEnc="UTF-8";
                        $content = preg_replace("/^$bom/", '', $new);
                    } else {
                      //conversion failed
                        error_log('Could not convert ' . $this->path . ' to UTF-8');
                    }
                }
            } else {
              //we could not detect character encoding
                error_log('Could not identify character encoding for ' . $this->path);
            }
        }
        return($content);
    }//end cleanSource()

    /**
     * Minify JavaScript. By default turned off, extend class to enable.
     *
     * @param string $content The content to be minified.
     *
     * @return string The minified content.
     */
    protected function jsminify($content): string
    {
        $JSqueeze = new \Patchwork\JSqueeze();
        return($JSqueeze->squeeze($content, true, false));
    }//end jsminify()

    /**
     * Minify CSS. By default turned off, extend class to enable.
     *
     * @author Manas Tungare  https://github.com/manastungare
     * @author Brenton String https://gist.github.com/brentonstrine
     *
     * @link https://gist.github.com/brentonstrine/5f56a24c7d34bb2d4655
     *
     * @param string $content The content to be minified.
     *
     * @return string The minified content.
     */
    protected function cssminify($content): string
    {
        $content = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $content);
        $content = str_replace(': ', ':', $content);
        $content = str_replace(array("\r\n", "\r", "\n", "\t"), '', $content);
        $content = preg_replace("/ {2,}/", ' ', $content);
        $content = str_replace(array('} '), '}', $content);
        $content = str_replace(array('{ '), '{', $content);
        $content = str_replace(array('; '), ';', $content);
        $content = str_replace(array(', '), ',', $content);
        $content = str_replace(array(' }'), '}', $content);
        $content = str_replace(array(' {'), '{', $content);
        $content = str_replace(array(' ;'), ';', $content);
        $content = str_replace(array(' ,'), ',', $content);
        return $content;
    }//end cssminify()

    /**
     * MultiByte safe Word-Wrap, needed because PHP does not have it native.
     *
     * @author Fosfor
     *
     * @link https://stackoverflow.com/users/615627/fosfor
     *
     * @param string $str   The content to be wrapped.
     * @param int    $width The line wrap width to use.
     * @param string $break The separator between lines.
     * @param bool   $cut   Whether to hard cut.
     *
     * @return string The Word-Wrapped content
     */
    protected function mbWordWrap($str, $width = 75, $break = "\n", $cut = false): string
    {
        $lines = explode($break, $str);
        foreach ($lines as &$line) {
            $line = rtrim($line);
            if (mb_strlen($line) <= $width) {
                continue;
            }
            $words = explode(' ', $line);
            $line = '';
            $actual = '';
            foreach ($words as $word) {
                if (mb_strlen($actual.$word) <= $width) {
                    $actual .= $word.' ';
                } else {
                    if ($actual != '') {
                        $line .= rtrim($actual).$break;
                    }
                    $actual = $word;
                    if ($cut) {
                        while (mb_strlen($actual) > $width) {
                            $line .= mb_substr($actual, 0, $width).$break;
                            $actual = mb_substr($actual, $width);
                        }
                    }
                    $actual .= ' ';
                }
            }
            $line .= trim($actual);
        }
        return implode($break, $lines);
    }//end mbWordWrap()

    /**
     * Word Wrap text files. Turned off by default, extend class to use.
     *
     * @param string $content The content to be word wrapped.
     *
     * @return string The word wrapped content.
     */
    protected function textwordwrap($content): string
    {
        $tmp = explode("\n", $content);
        $currmax = 0;
        foreach ($tmp as $line) {
            if (function_exists('mb_strlen')) {
                $lw = mb_strlen($line);
                if ($lw === false) {
                    $lw = strlen($line);
                }
            } else {
                $lw = strlen($line);
            }
            if ($lw > $currmax) {
                $currmax = $lw;
            }
        }
        if ($currmax > 120) {
            $n = count($tmp);
            for ($i=0; $i<$n; $i++) {
                if (function_exists('mbWordWrap')) {
                    $tmp[$i] = mbWordWrap($tmp[$i], 80, "\n", true);
                } elseif (function_exists('mb_strlen')) {
                    $tmp[$i] = $this->mbWordWrap($tmp[$i], 80, "\n", true);
                } else {
                    $tmp[$i] = wordwrap($tmp[$i], 80, "\n", true);
                }
            }
            $content = implode("\n", $tmp);
        }
        return $content;
    }//end textwordwrap()

    /**
     * Reads text file from filesystem and sends to browser.
     *
     * @return bool True on success, False on failure.
     */
    protected function getTextContent(): bool
    {
        if (is_null($this->path)) {
            $this->sendInternalError();
            return false;
        }
        if (is_null($this->mime)) {
            $this->sendInternalError();
            return false;
        }
        $content = file_get_contents($this->path);
        if ($this->toUTF8) {
            try {
                $content = $this->cleanSource($content);
            } catch (\ErrorException $e) {
                error_log($e->getMessage());
                $this->sendInternalError();
                return false;
            }
        }
        if ($this->minify) {
            switch ($this->mime) {
                case "application/javascript":
                    $content=$this->jsminify($content);
                    break;
                case "text/css":
                    $content=$this->cssminify($content);
                    break;
                default:
                    $content=$this->textwordwrap($content);
                    break;
            }
        }
        $charset='';
        if (! is_null($this->charEnc)) {
            $charset='; charset=' . $this->charEnc;
        } elseif (function_exists('mb_detect_encoding')) {
            if ($ENC = mb_detect_encoding($content, $this->charsetlist, true)) {
                $charset='; charset=' . $ENC;
            }
        }
        header('Content-Type: ' . $this->mime . $charset);
        header_remove('X-Powered-By');
        print($content);
        return true;
    }//end getTextContent()

    /**
     * Serves text content.
     *
     * @return bool True on success, False on failure.
     */
    protected function serveText(): bool
    {
        // move this to a class property in future FIXME
        $vary = array();
        if (is_null($this->request)) {
            $this->sendInternalError();
            return false;
        }
        if (is_null($this->etag)) {
            $this->sendInternalError();
            return false;
        }
        if ($this->attachment) {
            header('Content-Description: File Transfer');
            header('Content-Disposition: attachment; filename="' . $this->request . '"');
        }
        if ($this->maxage == 0) {
            header('Cache-Control: must-revalidate');
        } else {
            header('Cache-Control: max-age=' . $this->maxage);
        }
        header('Last-Modified: ' . $this->lastmod);
        header('ETag: "' . $this->etag . '"');
        if (ini_get('zlib.output_compression')) {
            $vary[] = 'Accept-Encoding';
        }
        if (! is_null($this->allowOrigin)) {
            if ($this->allowOrigin !== '*') {
                $vary[] = 'Origin';
            }
        }
        if (count($vary) > 0) {
            $string = 'Vary: ' . implode(',', $vary);
            header($string);
        }
        if (! is_null($this->allowOrigin)) {
            header('access-control-allow-origin: ' . $this->allowOrigin);
        }
        return $this->getTextContent();
    }//end serveText()

// Public Methods

    /**
     * Set the access-control-allow-origin header.
     *
     * @param string $origin The origin to allow.
     *
     * @return void
     */
    public function setAllowOrigin($origin): void
    {
        $origin = trim($origin);
        // fixme - send catchable error if origin isn't valid
        //  possibly could check with filter_var
        if (strlen($origin) > 0) {
            $this->allowOrigin = $origin;
        }
    }//end setAllowOrigin()

    /**
     * Serve the file.
     *
     * @return bool True on success, False on failure.
     */
    public function sendfile(): bool
    {
        if ($this->internalError) {
            $this->sendInternalError();
            return false;
        }
        if ($this->cacheok) {
            header("HTTP/1.1 304 Not Modified");
            return true;
        }
        if (is_null($this->path)) {
            header("HTTP/1.0 404 Not Found");
            return false;
        }
        if ($this->badrange) {
            header("HTTP/1.1 416 Range Not Satisfiable");
            return false;
        }
        if ($this->istext) {
            return $this->serveText();
        }
        return $this->sendContent();
    }//end sendfile()

    /**
     * Constructor function.
     *
     * @param string                   $path The path on the filesystem to file being served.
     * @param null|string              $request The file being requested.
     * @param null|string              $mime The mime type of file being requested.
     * @param int|string|\DateInterval $maxage How long the file should be cached for.
     * @param bool                     $attachment Whether or not to serve file as attachment.
     */
    public function __construct(string $path, $request = null, $mime = null, $maxage = 604800, bool $attachment = false)
    {
        // validate input types

        if (file_exists($path)) {
            $this->REQHEADERS=array_change_key_case(getallheaders(), CASE_LOWER);
            $this->path = $path;
            try {
                $this->setRequest($request);
            } catch (\ErrorException $e) {
                error_log($e->getMessage());
                $this->internalError = true;
                return;
            }
            try {
                $this->validMimeType($mime);
            } catch (\ErrorException $e) {
                error_log($e->getMessage());
                $this->internalError = true;
                return;
            }
            try {
                $this->textCheck();
            } catch (\ErrorException $e) {
                error_log($e->getMessage());
                $this->internalError = true;
                return;
            }
            try {
                $this->fontCheck();
            } catch (\ErrorException $e) {
                error_log($e->getMessage());
                $this->internalError = true;
                return;
            }
            try {
                $this->setFileProperties();
            } catch (\ErrorException $e) {
                error_log($e->getMessage());
                $this->internalError = true;
                return;
            }
            try {
                $this->cacheCheck();
            } catch (\ErrorException $e) {
                error_log($e->getMessage());
                $this->internalError = true;
                return;
            }
            if ($attachment) {
                $this->attachment=true;
            } else {
                $this->setMaxAge($maxage);
            }
        }
    }//end __construct()
}//end class

?>


<?php

namespace CatLab\Charon\Laravel\Middleware;

use CatLab\Charon\Models\Routing\Parameters\Base\Parameter;
use CatLab\Charon\Laravel\Exceptions\InputValidatorException;
use CatLab\Requirements\Collections\RequirementCollection;
use CatLab\Requirements\Exceptions\PropertyValidationException;
use CatLab\Requirements\Exceptions\RequirementException;
use Closure;

/**
 * Class InputTransformer
 *
 * Check if requirements of input is set.
 * Validators are always run after transformers.
 *
 * @package CatLab\Charon\Laravel\Middleware
 */
class InputValidator extends AbstractMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure $next
     * @param string $in What container should contain the input?
     * @param string $type Container of parameter that should be transformed (query, header, ...)
     * @param string $name Name of parameter that should be transformed
     * @param string $validator Serialized validator collection
     * @return mixed
     * @throws \CatLab\Charon\Exceptions\InvalidTransformer
     * @throws RequirementException
     */
    public function handle($request, Closure $next, $in, $type, $name, $validator)
    {
        $this->validateParameter($request, $in, $type, $name, $validator);
        return $next($request);
    }

    /**
     * @param \Illuminate\Http\Request $request
     * @param $in
     * @param $type
     * @param $name
     * @param $requirementCollection
     * @throws \CatLab\Charon\Exceptions\InvalidTransformer
     * @throws RequirementException
     */
    protected function validateParameter($request, $in, $type, $name, $requirementCollection)
    {
        $value = $this->getInput($request, $in, $name);

        $validators = RequirementCollection::make($requirementCollection);
        if (!$validators instanceof RequirementCollection) {
            abort(500, 'Invalid input validator provided; validator needs to be instance of ValidatorCollection');
        }

        try {

            if (!is_array($value)) {
                $value = [ $value ];
            }

            foreach ($value as $v) {
                $parameter = new Parameter($name, $type);
                $validators->validate($parameter, $v);
            }
        } catch (PropertyValidationException $e) {
            throw InputValidatorException::make($e);
        }
    }
}


<?php
/**
 * @author    Antoine Hedgecock <antoine.hedgecock@gmail.com>
 *
 * @copyright Interactive Solutions
 */

declare(strict_types = 1);

namespace InteractiveSolutions\Bernard\Normalizer;

use InteractiveSolutions\Bernard\Message\AbstractExplicitMessage;
use ReflectionClass;
use InvalidArgumentException;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

class ExplicitNormalizer implements NormalizerInterface, DenormalizerInterface
{
    /**
     * Denormalizes data back into an object of the given class.
     *
     * @param mixed  $data    data to restore
     * @param string $class   the expected class to instantiate
     * @param string $format  format the given data was extracted from
     * @param array  $context options available to the denormalizer
     *
     * @return object
     */
    public function denormalize($data, $class, $format = null, array $context = [])
    {
        $reflection = new ReflectionClass($class);
        $instance = $reflection->newInstanceWithoutConstructor();

        foreach ($data as $key => $value) {
            $property = $reflection->getProperty($key);
            $property->setAccessible(true);
            $property->setValue($instance, $value);
        }

        return $instance;
    }

    /**
     * Checks whether the given class is supported for denormalization by this normalizer.
     *
     * @param mixed  $data   Data to denormalize from
     * @param string $type   The class to which the data should be denormalized
     * @param string $format The format being deserialized from
     *
     * @return bool
     */
    public function supportsDenormalization($data, $type, $format = null)
    {
        return in_array(AbstractExplicitMessage::class, class_parents($type, true), true);
    }

    /**
     * {@inheritdoc}
     *
     * @throws \InvalidArgumentException
     */
    public function normalize($object, $format = null, array $context = [])
    {
        if (!$object instanceof AbstractExplicitMessage) {
            throw new InvalidArgumentException();
        }

        $reflection = new ReflectionClass($object);

        $args = [];

        foreach ($reflection->getProperties() as $property) {
            $property->setAccessible(true);

            $args[$property->getName()] = $property->getValue($object);
        }

        return $args;
    }

    /**
     * Checks whether the given class is supported for normalization by this normalizer.
     *
     * @param mixed  $data   Data to normalize
     * @param string $format The format being (de-)serialized from or into
     *
     * @return bool
     */
    public function supportsNormalization($data, $format = null)
    {
        return $data instanceof AbstractExplicitMessage;
    }
}


<?php
/**
 * Openbizx Framework
 *
 * LICENSE
 *
 * This source file is subject to the BSD license that is bundled
 * with this package in the file LICENSE.txt.
 *
 * @package   openbiz.bin.easy.element
 * @copyright Copyright (c) 2005-2011, Rocky Swen
 * @license   http://www.opensource.org/licenses/bsd-license.php
 * @link      http://www.phpopenbiz.org/
 * @version   $Id: ColumnBool.php 3687 2011-04-12 19:58:36Z jixian2003 $
 */

namespace Openbizx\Easy\Element;

use Openbizx\Openbizx;
use Openbizx\Core\Expression;

/**
 * ColumnBool class is element for ColumnBool
 * show boolean on data list (table)
 *
 * @package openbiz.bin.easy.element
 * @author wangdong1984
 * @copyright Copyright (c) 2005-2009
 * @access public
 */
class ColumnBool extends ColumnText
{
    public $trueImg;
    public $falseImg;
    public $trueValue;
    public $falseValue;

    /**
     * Read array meta data, and store to meta object
     *
     * @param array $xmlArr
     * @return void
     */
    protected function readMetaData(&$xmlArr)
    {
        parent::readMetaData($xmlArr);
        $this->trueImg=isset($xmlArr["ATTRIBUTES"]["TRUEIMG"])?$xmlArr["ATTRIBUTES"]["TRUEIMG"]:"flag_y.gif";
        $this->falseImg=isset($xmlArr["ATTRIBUTES"]["FALSEIMG"])?$xmlArr["ATTRIBUTES"]["FALSEIMG"]:"flag_n.gif";
        $this->trueValue=isset($xmlArr["ATTRIBUTES"]["TRUEVALUE"])?$xmlArr["ATTRIBUTES"]["TRUEVALUE"]:true;
        $this->falseValue=isset($xmlArr["ATTRIBUTES"]["FLASEVALUE"])?$xmlArr["ATTRIBUTES"]["FLASEVALUE"]:false;
    }

    /**
     * Render element, according to the mode
     *
     * @return string HTML text
     */
    public function render()
    {
        $val=$this->getText()?$this->getText():$this->getValue();
        $style = $this->getStyle();
        $text = $this->getText();
        $id = $this->objectName;
        $func = $this->getFunction();

        if($val==='1' || $val==='true' || strtoupper($val) == 'Y' || $val>0 || $val==$this->trueValue)
        {
        	$image_url  = $this->trueImg;
        }
        else
        {
        	$image_url  = $this->falseImg;
        }
        if(preg_match("/\{.*\}/si",$image_url))
        {
        	$formobj = $this->getFormObj();
        	$image_url =  Expression::evaluateExpression($image_url, $formobj);
        }else{
        	$image_url = Openbizx::$app->getImageUrl()."/".$image_url;
        }

    	if ($this->link)
        {
            $link = $this->getLink();
            $target = $this->getTarget();
            $sHTML = "";
        }else{
        	$sHTML = "";
        }
        return $sHTML;
    }
}


<?php
/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the MIT license. For more information, see
 * <http://www.doctrine-project.org>.
 */

namespace DoctrineModule\Form\Element;

use DoctrineModule\Form\Element\Proxy;
use Zend\Form\Element\Select as SelectElement;
use Zend\Form\Form;
use Zend\Stdlib\ArrayUtils;

class ObjectSelect extends SelectElement
{
    /**
     * @var Proxy
     */
    protected $proxy;

    /**
     * @return Proxy
     */
    public function getProxy()
    {
        if (null === $this->proxy) {
            $this->proxy = new Proxy();
        }
        return $this->proxy;
    }

    /**
     * @param  array|\Traversable $options
     * @return self
     */
    public function setOptions($options)
    {
        $this->getProxy()->setOptions($options);
        return parent::setOptions($options);
    }

    /**
     * @param string $key
     * @param mixed $value
     * @return self
     */
    public function setOption($key, $value)
    {
        $this->getProxy()->setOptions(array($key => $value));
        return parent::setOption($key, $value);
    }

    /**
     * {@inheritDoc}
     */
    public function setValue($value)
    {
        $multiple = $this->getAttribute('multiple');

        if (true === $multiple || 'multiple' === $multiple) {
            if ($value instanceof \Traversable) {
                $value = ArrayUtils::iteratorToArray($value);
            } elseif ($value == null) {
                return parent::setValue(array());
            } elseif (!is_array($value)) {
                $value = (array) $value;
            }

            return parent::setValue(array_map(array($this->getProxy(), 'getValue'), $value));
        }

        return parent::setValue($this->getProxy()->getValue($value));
    }

    /**
     * {@inheritDoc}
     */
    public function getValueOptions()
    {
        if (! empty($this->valueOptions)) {
            return $this->valueOptions;
        }

        $proxyValueOptions = $this->getProxy()->getValueOptions();

        if (! empty($proxyValueOptions)) {
            $this->setValueOptions($proxyValueOptions);
        }

        return $this->valueOptions;
    }
}


<?PHP
/**
 * Openbizx Framework
 *
 * LICENSE
 *
 * This source file is subject to the BSD license that is bundled
 * with this package in the file LICENSE.txt.
 *
 * @package   openbiz.bin.easy.element
 * @copyright Copyright (c) 2005-2011, Rocky Swen
 * @license   http://www.opensource.org/licenses/bsd-license.php
 * @link      http://www.phpopenbiz.org/
 * @version   $Id: Hidden.php 2912 2010-12-17 18:30:31Z jixian2003 $
 */

namespace Openbizx\Easy\Element;

use Openbizx\Easy\Element\Element;
/**
 * Button class is hidden element
 *
 * @package openbiz.bin.easy.element
 * @author Rocky Swen
 * @copyright Copyright (c) 2005-2009
 * @access public
 */
class Hidden extends Element
{
    /**
     * Read array meta data, and store to meta object
     *
     * @param array $xmlArr
     * @return void
     */
    protected function readMetaData(&$xmlArr)
    {
        parent::readMetaData($xmlArr);
        $this->fieldName = isset($xmlArr["ATTRIBUTES"]["FIELDNAME"]) ? $xmlArr["ATTRIBUTES"]["FIELDNAME"] : null;
    }

    /**
     * Draw the element according to the mode
     *
     * @return string HTML text
     */
    public function render()
    {
        if($this->value!=null){
    		$value = $this->value;
    	}else{
    		$value = $this->getText();
    	}

    	$sHTML = "<INPUT TYPE=HIDDEN NAME='$this->objectName' ID=\"" . $this->objectName ."\" VALUE='$value' $this->htmlAttr />";
        return $sHTML;
    }

}

?>


<?php namespace FelipeeDev\Utilities;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Validation\ValidationException;
use Illuminate\Validation\Validator;

class RulesValidator
{
    /**
     * Custom messages array.
     *
     * @var array
     */
    protected $messages = [];

    /**
     * Custom attributes array.
     *
     * @var array
     */
    protected $customAttributes = [];

    /**
     * Validates data.
     *
     * @param array $data
     * @param Validation\CustomAttributes|Validation\Messages|Validation\Rules|Validation\Validating $rules
     * @param string $type
     * @throws ValidationException
     */
    final public function validate(array $data, $rules, string $type = null)
    {
        try {
            $this->setMessages($rules);
        } catch (\TypeError $e) {
        }
        try {
            $this->setCustomAttributes($rules);
        } catch (\TypeError $e) {
        }

        try {
            $validator = app('validator')->make(
                array_filter($data),
                $rules->getRules($type),
                $this->messages,
                $this->customAttributes
            );

            $this->validating($validator, $rules, $type);
        } catch (\TypeError $e) {
        }

        if (isset($validator) && $validator->fails()) {
            throw new ValidationException($validator);
        }
    }

    /**
     * Validates model's data.
     *
     * @param Model $model
     * @param Validation\Rules|Eloquent\ModelAccess $rules
     * @param string $type
     */
    public function validateModel(Model $model, Validation\Rules $rules, string $type = null)
    {
        try {
            $this->setModel($rules, $model);
        } catch (\TypeError $e) {
        }

        $this->validate($model->getAttributes(), $rules, $type);
    }

    private function setMessages(Validation\Messages $rules, string $type = null)
    {
        $this->messages = $rules->getMessages($type);
    }

    private function setCustomAttributes(Validation\CustomAttributes $rules, string $type = null)
    {
        $this->customAttributes = $rules->getCustomAttributes($type);
    }

    private function validating(Validator $validator, Validation\Validating $rules, string $type = null)
    {
        $rules->onValidate($validator, $type);
    }

    private function setModel(Eloquent\ModelAccess $rules, Model $model)
    {
        $rules->setModel($model);
    }
}


<?php
/**
 * OAuth 2.0 Refresh token grant
 *
 */

namespace OAuth2\Server\Grant;

use OAuth2\Server\Entity\AccessTokenEntity;
use OAuth2\Server\Entity\ClientEntity;
use OAuth2\Server\Entity\RefreshTokenEntity;
use OAuth2\Server\Exception;
use OAuth2\Server\Util\SecureKey;

/**
 * Referesh token grant
 */
class RefreshTokenGrant extends AbstractGrant
{
    /**
     * {@inheritdoc}
     */
    protected $identifier = 'refresh_token';

    /**
     * Refresh token TTL (default = 604800 | 1 week)
     *
     * @var integer
     */
    protected $refreshTokenTTL = 604800;

    /**
     * Rotate token (default = false)
     *
     * @var integer
     */
    protected $rotateToken = false;

    /**
     * Set the TTL of the refresh token
     *
     * @param int $refreshTokenTTL
     *
     * @return void
     */
    public function setRefreshTokenTTL($refreshTokenTTL)
    {
        $this->refreshTokenTTL = $refreshTokenTTL;
    }

    /**
     * Get the TTL of the refresh token
     *
     * @return int
     */
    public function getRefreshTokenTTL()
    {
        return $this->refreshTokenTTL;
    }

    /**
     * Set the rotation boolean of the refresh token
     * @param bool $refreshTokenRotate
     */
    public function setRefreshTokenRotation($refreshTokenRotate = false)
    {
        $this->rotateToken = $refreshTokenRotate;
    }

    /**
     * Get rotation boolean of the refresh token
     *
     * @return bool
     */
    public function shouldRotateRefreshTokens()
    {
        return $this->rotateToken;
    }

    /**
     * {@inheritdoc}
     */
    public function completeFlow(ClientEntity $client)
    {
        $oldRefreshTokenParam = $this->server->getRequestHandler()->getParam('refresh_token');
        if ($oldRefreshTokenParam === null) {
            throw new Exception\InvalidRequestException('refresh_token');
        }

        // Validate refresh token
        $oldRefreshToken = $this->server->getRefreshTokenStorage()->get($oldRefreshTokenParam);

        if (($oldRefreshToken instanceof RefreshTokenEntity) === false) {
            throw new Exception\InvalidRefreshException();
        }

        // Ensure the old refresh token hasn't expired
        if ($oldRefreshToken->isExpired() === true) {
            throw new Exception\InvalidRefreshException();
        }

        $oldAccessToken = $oldRefreshToken->getAccessToken();

        // Get the scopes for the original session
        $session = $oldAccessToken->getSession();
        $scopes = $this->formatScopes($session->getScopes());

        // Get and validate any requested scopes
        $requestedScopesString = $this->server->getRequestHandler()->getParam('scope');
        $requestedScopes = $this->validateScopes($requestedScopesString, $client);

        // If no new scopes are requested then give the access token the original session scopes
        if (count($requestedScopes) === 0) {
            $newScopes = $scopes;
        } else {
            // The OAuth spec says that a refreshed access token can have the original scopes or fewer so ensure
            //  the request doesn't include any new scopes
            foreach ($requestedScopes as $requestedScope) {
                if (!isset($scopes[$requestedScope->getId()])) {
                    throw new Exception\InvalidScopeException($requestedScope->getId());
                }
            }

            $newScopes = $requestedScopes;
        }

        // Generate a new access token and assign it the correct sessions
        $newAccessToken = new AccessTokenEntity($this->server);
        $newAccessToken->setId();
        $newAccessToken->setExpireTime($this->getAccessTokenTTL() + time());
        $newAccessToken->setSession($session);

        foreach ($newScopes as $newScope) {
            $newAccessToken->associateScope($newScope);
        }

		//Save new access token
        $newAccessToken->save();
		// Expire the old token
		$oldAccessToken->expire();

        $this->server->getTokenType()->setSession($session);
        $this->server->getTokenType()->setParam('access_token', $newAccessToken->getId());
        $this->server->getTokenType()->setParam('expires_in', $this->getAccessTokenTTL());

        if ($this->shouldRotateRefreshTokens()) {
            // Expire the old refresh token
            $oldRefreshToken->expire();

            // Generate a new refresh token
            $newRefreshToken = new RefreshTokenEntity($this->server);
            $newRefreshToken->setId();
            $newRefreshToken->setExpireTime($this->getRefreshTokenTTL() + time());
            $newRefreshToken->setAccessToken($newAccessToken);
            $newRefreshToken->save();

            $this->server->getTokenType()->setParam('refresh_token', $newRefreshToken->getId());
        } else {
			$oldRefreshToken->setAccessToken($newAccessToken);
			$oldRefreshToken->save();
            $this->server->getTokenType()->setParam('refresh_token', $oldRefreshToken->getId());
        }

        return $this->server->getTokenType()->generateResponse();
    }
}


<?php

namespace Modules\Menu\Traits;

use Exception;
use Modules\Menu\Contracts\MenuItemInterface as MenuItemContract;

trait MenuItemTrait
{
    /**
     * Boolean value whenever the menu item has
     * children or not.
     *
     * @return bool
     */
    public function hasChildren()
    {
        return $this->children()->count() > 0;
    }

    /**
     * Boolean value whenever the menu item
     * contains inconsistend or wrong data.
     * i.e. a wrong route name.
     *
     * @return bool
     */
    public function isFaulty()
    {
        return $this->url === null;
    }

    /**
     * @param $value
     *
     * @return array
     */
    public function getParametersAttribute($value)
    {
        return json_decode($value, true);
    }

    /**
     * @param $value
     */
    public function setParametersAttribute($value)
    {
        $this->attributes['parameters'] = json_encode($value);
    }

    /**
     * Localized string which represents the
     * type of the menu item.
     *
     * @param $value
     *
     * @return string
     */
    public function getActionAttribute($value)
    {
        switch ($this->type) {
            case MenuItemContract::TYPE_ROUTE:
                return trans('menu::menu.type.route');

            case MenuItemContract::TYPE_URL:
                return trans('menu::menu.type.url');

            default:
                return trans('menu::menu.type.unknown');
        }
    }

    /**
     * Builds the targeting url based upon the given
     * type if the current item.
     *
     * @param $value
     *
     * @return string
     */
    public function getUrlAttribute($value)
    {
        if ($this->hasChildren()) {
            return MenuItemContract::URL_EMPTY;
        }

        switch ($this->type) {
            case MenuItemContract::TYPE_ROUTE:
                return $this->routeGracefulOnError($this->target, $this->parameters);

            case MenuItemContract::TYPE_URL:
                return $this->target;

            default:
                return;
        }
    }

    /**
     * @param $name
     * @param $parameters
     *
     * @throws Exception
     *
     * @return null|string
     */
    private function routeGracefulOnError($name, $parameters)
    {
        try {
            return route($name, $parameters);
        } catch (Exception $exception) {
            return;
        }
    }
}


<?php
/**
 * Openbizx Framework
 *
 * LICENSE
 *
 * This source file is subject to the BSD license that is bundled
 * with this package in the file LICENSE.txt.
 *
 * @package   openbiz.bin.easy
 * @copyright Copyright (c) 2005-2011, Rocky Swen
 * @license   http://www.opensource.org/licenses/bsd-license.php
 * @link      http://www.phpopenbiz.org/
 * @version   $Id: EasyFormWizard.php 3037 2010-12-30 04:00:30Z iceve $
 */

namespace Openbizx\Easy;

use Openbizx\Data\DataRecord;
use Openbizx\Easy\EasyForm;

/**
 * EasyFormWizard class, extension of EasyForm to support wizard form
 *
 * @package openbiz.bin.easy
 * @author Rocky Swen
 * @copyright Copyright (c) 2005-2009
 * @access public
 */
class EasyFormWizard extends EasyForm
{
    protected $dropSession = false;

    /**
     * Wizard Navigation Panel object
     *
     * @var Panel
     */
    public $wizardPanel;

    /**
     * Get/Retrieve Session data of this object
     *
     * @param \Openbizx\Web\SessionContext $sessionContext
     * @return void
     */
    public function loadStatefullVars($sessionContext)
    {
    	parent::loadStatefullVars($sessionContext);
        $sessionContext->loadObjVar($this->objectName, "ActiveRecord", $this->activeRecord, true);
        $sessionContext->loadObjVar($this->objectName, "FormInputs", $this->formInputs, true);
        $this->setActiveRecord($this->activeRecord);
    }

 	protected function readMetadata(&$xmlArr)
    {
        parent::readMetaData($xmlArr);
        $this->wizardPanel = new Panel($xmlArr["EASYFORM"]["WIZARDPANEL"]["ELEMENT"],"",$this);
    }

    /**
     * Save object variable to session context
     *
     * @param \Openbizx\Web\SessionContext $sessionContext
     * @return void
     */
    public function saveStatefullVars($sessionContext)
    {
        if ($this->dropSession)
            $sessionContext->cleanObj($this->objectName, true);
        else {
        	parent::saveStatefullVars($sessionContext);
            $sessionContext->saveObjVar($this->objectName, "ActiveRecord", $this->activeRecord, true);
            $sessionContext->saveObjVar($this->objectName, "FormInputs", $this->formInputs, true);
        }
    }

    /**
     * Go to next wizard page
     *
     * @param boolean $commit true if need to commit current form data
     * @return void
     * @access remote
     */
    public function goNext($commit=false)
    {
        // call ValidateForm()
        $recArr = $this->readInputRecord();
        $this->setActiveRecord($recArr);

   		 try
        {
             if ($this->ValidateForm() == false)
            return;
        }catch (Openbizx\Validation\Exception $e)
        {
            $this->processFormObjError($e->errors);
            return;
        }

        $this->activeRecord = $this->readInputRecord();
		$viewObj = $this->getWebpageObject();
        // get the step
    	if($viewObj->getCurrentStep()){
        	$step = $viewObj->getCurrentStep();
        }else{
        	$step = $_GET['step'];
        }
        if (!$step || $step=="")
            $step=1;

        // redirect the prev step
        /* @var $viewObj WebPageWizard */

        $viewObj->renderStep($step+1);
    }

/**
     * Skip current wizard page
     *
     * @return void
     * @access remote
     */
    public function skip()
    {
		$viewObj = $this->getWebpageObject();
        // get the step
    	if($viewObj->getCurrentStep()){
        	$step = $viewObj->getCurrentStep();
        }else{
        	$step = $_GET['step'];
        }
        if (!$step || $step=="")
            $step=1;

        $viewObj->renderStep($step+2);
    }

    /**
     * Go to previous wizard page
     *
     * @return void
     * @access remote
     */
    public function goBack()
    {
        $recArr = $this->readInputRecord();
        $this->setActiveRecord($recArr);
		$this->activeRecord = $this->readInputRecord();

        $viewObj = $this->getWebpageObject();

        // get the step
        if($viewObj->getCurrentStep()){
        	$step = $viewObj->getCurrentStep();
        }else{
        	$step = $_GET['step'];
        }

        // redirect the prev step
        /* @var $viewObj WebPageWizard */

        $viewObj->renderStep($step-1);
    }

    /**
     * Finish the wizard process
     *
     * @return void
     * @access remote
     */
    public function doFinish() //- call FinishWizard() by default

    {
        // call ValidateForm()
        $recArr = $this->readInputRecord();
        $this->setActiveRecord($recArr);
    	$this->setFormInputs($this->formInputs);

   		 try
        {
             if ($this->ValidateForm() == false)
            return;
        }catch (Openbizx\Validation\Exception $e)
        {
        	$this->processFormObjError($e->errors);
            return;
        }

        $this->activeRecord = $this->readInputRecord();

        /* @var $viewObj WebPageWizard */
        $viewObj = $this->getWebpageObject();

        $r = $viewObj->commit();
        if (!$r)
            return;

        $this->processPostAction();
    }

    /**
     * Cancel the wizard process
     *
     * @return void
     * @access remote
     */
    public function doCancel() //- call CancelWizard() by default

    {
        /* @var $viewObj WebPageWizard */
        $viewObj = $this->getWebpageObject();
        $viewObj->cancel();

        $this->processPostAction();
    }

    /**
     * Save wizard data of current+previous pages into database or other storage
     *
     * @return void
     */
    public function commit()
    {
		if(!$this->getDataObj()){
			return true;
		}
    	// commit the form input. call SaveRecord()
        $recArr = $this->activeRecord;

        if (strtoupper($this->formType) == "NEW")
            $dataRec = new DataRecord(null, $this->getDataObj());
        else
        {
            //$currentRec = $this->fetchData(); // wrong way to get current data. need to query the old one
            $currentRec = array(); // to get record with "" values
            $dataRec = new DataRecord($currentRec, $this->getDataObj());
        }

        foreach ($recArr as $k => $v)
            $dataRec[$k] = $v; // or $dataRec->$k = $v;
        try
        {
            $dataRec->save();
        } catch (Openbizx\Data\Exception $e)
        {
            $this->processDataException($e);
            return false;
        }

        return true;
    }

    public function dropSession(){
    	// clean the session record
        $this->dropSession = true;
        return true;
    }

    /**
     * Clean up the sessions of view and all forms
     *
     * @return void
     */
    public function cancel()
    {
        // clean the session record
        $this->dropSession = true;
        Openbizx::$app->getSessionContext()->cleanObj($this->objectName, true);
    }

    /**
     * Render this form
     *
     * @return @return string - HTML text of this form's read mode
     */
    public function render()
    {
        $viewobj = $this->getWebpageObject();
        $viewobj->setFormState($this->objectName, 'visited', 1);

        return parent::render();
    }
    public function outputAttrs()
    {
        $output = parent::outputAttrs();
        $viewobj = $this->getWebpageObject();
        $forms = array();
        $viewobj->formRefs->rewind();
        while($viewobj->formRefs->valid()){
        	$form=$viewobj->formRefs->current();
        	$forms[$form->objectName] = $form;
        	$viewobj->formRefs->next();
        }
        $output['forms'] = $forms;
        $output['step'] = $viewobj->getCurrentStep();
        return $output;
    }
}
?>


<?php

/**
 * MIT License
 * For full license information, please view the LICENSE file that was distributed with this source code.
 */

namespace Spryker\Zed\SalesOrderThresholdDataImport\Business;

use Generated\Shared\Transfer\DataImporterConfigurationTransfer;
use Generated\Shared\Transfer\DataImporterReportTransfer;
use Spryker\Zed\Kernel\Business\AbstractFacade;

/**
 * @method \Spryker\Zed\SalesOrderThresholdDataImport\Business\SalesOrderThresholdDataImportBusinessFactory getFactory()
 */
class SalesOrderThresholdDataImportFacade extends AbstractFacade implements SalesOrderThresholdDataImportFacadeInterface
{
    /**
     * {@inheritdoc}
     *
     * @api
     *
     * @param \Generated\Shared\Transfer\DataImporterConfigurationTransfer|null $dataImporterConfigurationTransfer
     *
     * @return \Generated\Shared\Transfer\DataImporterReportTransfer
     */
    public function importSalesOrderThresholds(?DataImporterConfigurationTransfer $dataImporterConfigurationTransfer = null): DataImporterReportTransfer
    {
        return $this->getFactory()->createSalesOrderThresholdDataImport()->import($dataImporterConfigurationTransfer);
    }
}


<?php
/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the MIT license. For more information, see
 * <http://www.doctrine-project.org>.
 */

namespace DoctrineModule\Service;

use DoctrineModule\Version;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper\HelperSet;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\ServiceManager;

/**
 * CLI Application ServiceManager factory responsible for instantiating a Symfony CLI application
 *
 * @license MIT
 * @link    http://www.doctrine-project.org/
 * @author  Kyle Spraggs <theman@spiffyjr.me>
 */
class CliFactory implements FactoryInterface
{
    /**
     * @var \Zend\EventManager\EventManagerInterface
     */
    protected $events;

    /**
     * @var \Symfony\Component\Console\Helper\HelperSet
     */
    protected $helperSet;

    /**
     * @var array
     */
    protected $commands = array();

    /**
     * @param  ServiceLocatorInterface                  $sm
     * @return \Zend\EventManager\EventManagerInterface
     */
    public function getEventManager(ServiceLocatorInterface $sm)
    {
        if (null === $this->events) {
            /* @var $events \Zend\EventManager\EventManagerInterface */
            $events = $sm->get('EventManager');

            $events->addIdentifiers(array(__CLASS__, 'doctrine'));

            $this->events = $events;
        }

        return $this->events;
    }

    /**
     * {@inheritDoc}
     * @return Application
     */
    public function createService(ServiceLocatorInterface $sl)
    {
        $cli = new Application;
        $cli->setName('DoctrineModule Command Line Interface');
        $cli->setVersion(Version::VERSION);
        $cli->setHelperSet(new HelperSet);
        $cli->setCatchExceptions(true);
        $cli->setAutoExit(false);

        // Load commands using event
        $this->getEventManager($sl)->trigger('loadCli.post', $cli, array('ServiceManager' => $sl));

        return $cli;
    }
}


<?php

/*
 * This file is part of the Security package.
 *
 * (c) EXSyst
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace EXSyst\Component\Security\TOTP;

/**
 * TOTP key generator.
 */
class TOTPKeyGenerator
{
    const DEFAULT_LENGTH = 16;

    /**
     * Generates a TOTP key.
     *
     * @param int|null $length Key length (default 16)
     *
     * @return string TOTP key
     */
    public static function generate($length = null)
    {
        if ($length === null) {
            $length = self::DEFAULT_LENGTH;
        }
        $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
        $randomString = '';
        for ($i = 0; $i < $length; ++$i) {
            $randomString .= $characters[rand(0, strlen($characters) - 1)];
        }

        return $randomString;
    }
}


<?php

/**
 * Openbizx Framework
 *
 * LICENSE
 *
 * This source file is subject to the BSD license that is bundled
 * with this package in the file LICENSE.txt.
 *
 * @package   openbiz.bin
 * @copyright Copyright (c) 2005-2011, Rocky Swen
 * @license   http://www.opensource.org/licenses/bsd-license.php
 * @link      http://www.phpopenbiz.org/
 * @version   $Id$
 */

namespace Openbizx\Object;

use Openbizx\Openbizx;
use Openbizx\ClassLoader;

/**
 * MetaIterator class
 * MetaIterator is the base class of all derived metadata-driven classes who support iteration
 *
 * @package   openbiz.bin
 * @author    Rocky Swen
 * @copyright Copyright (c) 2005-2009, Rocky Swen
 * @access    public
 * @todo please check MetaObjExport.
 */
class MetaIterator implements \Iterator
{

    /**
     * Parent object
     * @var object
     */
    protected $parentObj = null;

    /**
     * Store value
     * @var array
     */
    protected $varValue = array();

    /**
     * Contructor of class
     *
     * @param array $xmlArr
     * @param string $childClassName
     * @param object $parentObj
     * @return void
     */
    public function __construct(&$xmlArr, $childClassName, $parentObj = null)
    {
        $this->parentObj = $parentObj;

        if (!$xmlArr) {
            return;
        }

        if (isset($xmlArr["ATTRIBUTES"])) {
            $className = isset($xmlArr["ATTRIBUTES"]['CLASS']) ? $xmlArr["ATTRIBUTES"]['CLASS'] : $childClassName;
            if ((bool) strpos($className, ".")) {
                $a_package_name = explode(".", $className);
                $className = array_pop($a_package_name);
                $clsLoaded = ClassLoader::loadMetadataClass($className, implode(".", $a_package_name));
            }
            //if (!$clsLoaded) trigger_error("Cannot find the load class $className", E_USER_ERROR);

            $className = Openbizx::objectFactory()->getClassNameFromAlias($className);

            $obj = new $className($xmlArr, $parentObj);
            $this->varValue[$obj->objectName] = $obj;
        } else {
            foreach ($xmlArr as $child) {
                $className = isset($child["ATTRIBUTES"]['CLASS']) ? $child["ATTRIBUTES"]['CLASS'] : $childClassName;

                /**
                 * If a '.' is found within className we need to require such class
                 * and then get the className after the last dot
                 * ex. shared.dataobjs.FieldName, in this case FieldName is the class, shared/dataobjs the path
                 *
                 * The best solution to this is enable object factory to specify its resulting object constructor parameters
                 */
                if ($className) { //bug fixed by jixian for resolve load an empty classname
                    if ((bool) strpos($className, ".")) {
                        $a_package_name = explode(".", $className);
                        $className = array_pop($a_package_name);
                        $clsLoaded = ClassLoader::loadMetadataClass($className, implode(".", $a_package_name));
                    } elseif ($parentObj->package) {
                        $clsLoaded = ClassLoader::loadMetadataClass($className, $parentObj->package);
                    }

                    $className = Openbizx::objectFactory()->getClassNameFromAlias($className);

                    $obj = new $className($child, $parentObj);
                    $this->varValue[$obj->objectName] = $obj;
                }
            }
        }
    }

    /**
     * Merge to another MetaIterator object
     *
     * @param MetaIterator $anotherMIObj another MetaIterator object
     * @return void
     */
    public function merge(&$anotherMIObj)
    {
        $old_varValue = $this->varValue;
        $this->varValue = array();
        foreach ($anotherMIObj as $key => $value) {
            if (!$old_varValue[$key]) {
                $this->varValue[$key] = $value;
            } else {
                $this->varValue[$key] = $old_varValue[$key];
            }
        }
        foreach ($old_varValue as $key => $value) {
            if (!isset($this->varValue[$key])) {
                $this->varValue[$key] = $value;
            }
        }
    }

    /**
     * Get value
     *
     * @param mixed $key
     * @return mixed
     */
    public function get($key)
    {
        return isset($this->varValue[$key]) ? $this->varValue[$key] : null;
    }

    /**
     * Set value
     *
     * @param mixed $key
     * @param mixed $val
     */
    public function set($key, $val)
    {
        $this->varValue[$key] = $val;
    }

    /**
     * Clear value
     *
     * @param mixed $key
     * @param mixed $val
     */
    public function clear($key)
    {
        unset($this->varValue[$key]);
    }

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

    /**
     * Rewind
     *
     * @return void
     */
    public function rewind()
    {
        reset($this->varValue);
    }

    /**
     * Current item
     *
     * @return mixed
     */
    public function current()
    {
        return current($this->varValue);
    }

    /**
     *
     * @return mixed
     */
    public function key()
    {
        return key($this->varValue);
    }

    /**
     *
     * @return mixed
     */
    public function next()
    {
        return next($this->varValue);
    }

    /**
     *
     * @return boolean
     */
    public function valid()
    {
        return $this->current() !== false;
    }

}


<?php

/*
 * This file is part of the Security package.
 *
 * (c) EXSyst
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace EXSyst\Component\Security\TOTP;

use EXSyst\Component\Security\Exception\InvalidArgumentException;

class TOTPTimeManager
{
    const DEFAULT_STAMP_LENGTH = 30;

    /**
     * @var int
     */
    private $stampLength;

    /**
     * @param int|null $stampLength Length of a unit time (default 30)
     */
    public function __construct($stampLength = null)
    {
        $this->setStampLength($stampLength);
    }

    /**
     * @param int|null $stampLength Length of a unit time (default 30)
     *
     * @return TOTPTimeManager
     */
    public function setStampLength($stampLength = null)
    {
        if ($stampLength === null) {
            $stampLength = self::DEFAULT_STAMP_LENGTH;
        }
        $stampLength = intval($stampLength);
        if (empty($stampLength) || $stampLength < 0) {
            throw new InvalidArgumentException('Stamp length must be greater than 0 and positive.');
        }
        $this->stampLength = $stampLength;

        return $this;
    }

    /**
     * @return int Stamp length
     */
    public function getStampLength()
    {
        return $this->stampLength;
    }

    /**
     * @return int Current stamp
     */
    public function getCurrentStamp()
    {
        return $this->getStamp(time());
    }

    /**
     * Returns the stamp corresponding to the specified time.
     *
     * @param int $time timestamp
     *
     * @return int stamp corresponding to the timestamp provided
     */
    public function getStamp($time)
    {
        return floor($time / $this->stampLength);
    }
}


<?php

namespace WobbleCode\ManagerBundle\Manager;

use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\Validator\ValidatorInterface;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Query\Builder;
use Knp\Component\Pager\Paginator;

/**
 * @class CollectionItemManager
 */
class GenericDocumentManager
{
    /**
     * MongoDB DocumentManager
     *
     * @var DocumentManager
     */
    protected $dm;

    /**
     * EventDispatcher
     *
     * @var EventDispatcher
     */
    protected $eventDispatcher;

    /**
     * Validator
     *
     * @var ValidatorInterface
     */
    protected $paginator;

    /**
     * Key namespace used for events
     *
     * @var string key
     */
    protected $key;

    /**
     * The main object to manage
     *
     * @var string document
     */
    protected $document;

    /**
     * Define which parameters can be set from the request
     *
     * @var array
     */
    protected $acceptedFromRequest = ['page', 'query'];

    /**
     * Mapping from request, this mapping array determines what parameter should
     * get from request
     *
     * @var array
     */
    protected $mappingFromRequest = [
        'itemsPerPage' => 'per_page',
        'page'         => 'page',
        'query'        => 'q',
        'sortBy'       => 'sort_by',
        'sortDir'      => 'order'
    ];

    /**
     * Items per page to load
     *
     * @var integer
     */
    protected $itemsPerPage = 10;

    /**
     * Current page to load
     *
     * @var integer
     */
    protected $page = 1;

    /**
     * Default Sort by
     *
     * @var mixed
     */
    protected $sortBy = false;

    /**
     * Default Sort Dir
     *
     * @var mixed
     */
    protected $sortDir = false;

    /**
     * Use this fields to create search query
     *
     * @var array queryFields
     */
    protected $queryFields = [];

    /**
     * Value for filter query
     *
     * @var string query to apply
     */
    protected $query;

    /**
     * Generic Document constructor
     *
     * @param RequestStack             $requestStack    Symfony Request stack
     * @param EventDispatcherInterface $eventDispatcher Event Dispatcher
     * @param DocumentManager          $documentManager Object Manager
     * @param Paginator                $paginator       Knp Paginator
     */
    public function __construct(
        RequestStack $requestStack,
        EventDispatcherInterface $eventDispatcher,
        DocumentManager $documentManager,
        Paginator $paginator
    ) {
        $this->requestStack    = $requestStack;
        $this->eventDispatcher = $eventDispatcher;
        $this->dm              = $documentManager;
        $this->paginator       = $paginator;
    }

    public function getDocumentManager()
    {
        return $this->dm;
    }

    public function setKey($key)
    {
        $this->key = $key;

        return $this;
    }

    public function setDocument($document)
    {
        $this->document = $document;

        return $this;
    }

    public function setItemsPerPage($itemsPerPage)
    {
        $this->itemsPerPage = $itemsPerPage;

        return $this;
    }

    public function setPage($page)
    {
        $this->page = $page;

        return $this;
    }

    public function setQuery($query)
    {
        $this->query = $query;

        return $this;
    }

    public function setSortBy($sortBy)
    {
        $this->sortBy = $sortBy;

        return $this;
    }

    public function setSortDir($sortDir)
    {
        $this->sortDir = $sortDir;

        return $this;
    }

    public function setQueryFields(array $queryFields)
    {
        $this->queryFields = $queryFields;

        return $this;
    }

    public function setAcceptFromRequest(array $acceptedFromRequest)
    {
        $this->acceptedFromRequest = $acceptedFromRequest;

        return $this;
    }

    public function getDefault($parameter, $value = null)
    {
        if ($value) {
            return $value;
        }

        if (in_array($parameter, $this->acceptedFromRequest)) {
            $request = $this->requestStack->getCurrentRequest();
            $queryParameter = $this->mappingFromRequest[$parameter];

            return $request->query->get($queryParameter, $this->{$parameter});
        }

        return $this->{$parameter};
    }

    /**
     * Notify Helper Create a generic event and dispatch the event
     *
     * @todo move to WobbleCodeNotificationBundle
     *
     * @param string $key       Event key name
     * @param array  $arguments Arguments with Organization included
     *
     * @example of arguments
     *
     *     $arguments = [
     *         'notifyLanguage' => 'es',
     *         'notifyUserTrigger' => $user,
     *         'notifyUsers' => [],
     *         'notifyExternal' => 'notify@email.com',
     *         'data' => [
     *             'invitation' => $invitation
     *         ]
     *     ];
     *
     * @return GenericEvent Dispatched event
     */
    public function dispatch($key, $arguments)
    {
        $event = new GenericEvent(
            $key,
            $arguments
        );

        $this->eventDispatcher->dispatch($key, $event);

        return $event;
    }

    public function addSort($qb, $field, $dir)
    {
        $field = $this->dashesToCamelCase($field);
        $qb->sort($field, $dir);

        return $qb;
    }

    public function addFilters($qb, $filters)
    {
        foreach ($filters as $filter) {
            $qb->field($filter['field'])->{$filter['selector']}($filter['value']);
        }

        return $qb;
    }

    public function addQuery($qb, $queryFields, $query)
    {
        if (!$query) {
            return $qb;
        }

        foreach ($queryFields as $field) {
            $qb->addOr($qb->expr()->field($field)->equals(new \MongoRegex('/.*'.$query.'.*/i')));
        }

        return $qb;
    }

    public function addNativeQuery($queryFields, $query, $operator = '$or')
    {
        $qb = [];

        foreach ($queryFields as $field) {
            $qb[$operator][] = [$field => new \MongoRegex('/.*'.$query.'.*/i')];
        }

        return $qb;
    }

    /**
     * Get a list documents
     *
     * @param array $filters
     *
     * List or conditional selectors http://bit.ly/1efirww
     *
     * @example filters
     *
     * [
     *     [
     *         'field' => 'id',
     *         'selector' => 'equals',
     *         'value' => 12
     *     ],
     *     …
     * ]
     *
     * @return MongoCursor
     */
    public function getDocuments(array $filters = [], $primes = [], $query = null)
    {
        $query = $this->getDefault('query', $query);
        $page = $this->getDefault('page');
        $itemsPerPage = $this->getDefault('itemsPerPage');
        $sortBy = $this->getDefault('sortBy');
        $sortDir = $this->getDefault('sortDir');

        $qb = $this->dm->createQueryBuilder($this->document);
        $qb = $this->addFilters($qb, $filters);
        $qb = $this->addQuery($qb, $this->queryFields, $query);

        foreach ($primes as $field) {
            $qb->field($field)->prime(true);
        }

        if ($sortBy) {
            $qb = $this->addSort($qb, $sortBy, $sortDir);
        }

        return $this->paginator->paginate($qb, $page, $itemsPerPage);
    }

    /**
     * Get a list documents
     *
     * @param array $filters
     *
     * List or conditional selectors http://bit.ly/1efirww
     *
     * @example filters
     *
     * [
     *     [
     *         'field' => 'id',
     *         'selector' => 'equals',
     *         'value' => 12
     *     ],
     *     …
     * ]
     *
     * @return MongoCursor
     */
    public function count(array $filters = [], $query = false)
    {
        $qb = $this->dm->createQueryBuilder($this->document);
        $qb = $this->addFilters($qb, $filters);

        if ($query) {
            $query = $this->getDefault('query', $query);
            $qb = $this->addQuery($qb, $this->queryFields, $query);
        }

        $qb->count();
        $query   = $qb->getQuery();
        $results = $query->execute();

        return $results;
    }

    /**
     * Count items by group
     *
     * @param mixed $collection
     * @param mixed $fieldGroup
     * @param mixed $match
     *
     * @return array Multidimensional array with key => count per group
     */
    public function countByGroup($fieldGroup, $match = [], $query = null, $sort = null, $limit = null)
    {
        $group = [
            '_id' => '$'.$fieldGroup,
            'count' => [ '$sum' => 1]
        ];

        return $this->aggregateGroup($group, $match, $query, $sort, $limit);
    }

    /**
     * Aggregate with custom group
     *
     * @param mixed $collection
     * @param mixed $fieldGroup
     * @param mixed $match
     *
     * @return array Multidimensional array with key => sum per item
     */
    public function aggregateGroup($group, $match = [], $query = null, $sort = null, $limit = null)
    {
        $query = $this->getDefault('query', $query);

        $collection = $this->dm->getDocumentCollection($this->document);

        $pipeline = [];

        if ($query) {
            $query = $this->addNativeQuery($this->queryFields, $query);
            $match = array_merge($match, $query);
        }

        if (count($match)) {
            $pipeline[] = [
                '$match' => $match
            ];
        }

        $pipeline[] = [
            '$group' => $group
        ];

        if ($sort) {
            $pipeline[] = [
                '$sort' => $sort
            ];
        }

        if ($limit) {
            $pipeline[] = [
                '$limit' => $limit
            ];
        }

        $return = $collection->aggregate($pipeline);

        return iterator_to_array($return);
    }

    /**
     * Find a document with support for conditions
     *
     * @param array $filters List or conditional selectors http://bit.ly/1efirww
     *
     * @example filters
     *
     * [
     *     [
     *         'field' => 'id',
     *         'selector' => 'equals',
     *         'value' => 12
     *     ],
     *     …
     * ]
     *
     * @return MongoCursor
     */
    public function find($id, array $filters = [])
    {
        $qb = $this->dm->createQueryBuilder($this->document);

        if (count($filters)) {
            $qb = $this->addFilters($qb, $filters);
        }

        $qb->field('_id')->equals(new \MongoId($id));

        return $qb->getQuery()->getSingleResult();
    }

    /**
     * FindBy
     *
     * @param array $filters List or conditional selectors http://bit.ly/1efirww
     *
     * @return MongoCursor
     */
    public function findBy($filters = [], $sort = null)
    {
        return $this->dm->getRepository($this->document)->findBy($filters, $sort);
    }

    /**
     * FindOnBy
     *
     * @param array $filters List or conditional selectors http://bit.ly/1efirww
     *
     * @return MongoCursor
     */
    public function findOneBy($filters = [], $sort = null)
    {
        return $this->dm->getRepository($this->document)->findOneBy($filters, $sort);
    }

    /**
     * save
     *
     * @param array Array of objects or object
     */
    public function save($documents)
    {
        foreach ($documents as $document) {
            $this->dm->persist($document);
        }

        return $this->dm->flush();
    }

    /**
     * Get array of MongoIds of this objects
     *
     * @param array $objects
     *
     * @return array MongoIds array
     */
    public function getMongoIds($objects)
    {
        $ids = [];

        foreach ($objects as $object) {
            $ids[] = new \MongoId($object->getId());
        }

        return $ids;
    }

    /**
     * save
     *
     * @param Iterable Array/Iterable of objects or object
     */
    public function remove($documents)
    {
        if (!count($documents)) {
            return false;
        }

        foreach ($documents as $document) {
            $this->dm->remove($document);
        }

        return $this->dm->flush();
    }

    private function dashesToCamelCase($string, $capitalizeFirstCharacter = false)
    {
        $str = str_replace(' ', '', ucwords(str_replace('-', ' ', $string)));

        if (!$capitalizeFirstCharacter) {
            $str[0] = strtolower($str[0]);
        }

        return $str;
    }

    /**
     * Normalize date from ISO8601, if is string it returns a DateTime object
     *
     * @param {DateTime|string|bool} $date
     *
     * @return {DateTime|bool} Return DateTime or false for Invalid Dates
     */
    public function normalizeDate($date)
    {
        if ($date instanceof \DateTime) {
            return $date;
        }

        $date = preg_replace('/\.[0-9]+/', '', $date);
        return \DateTime::createFromFormat(\DateTime::ISO8601, $date);
    }

    /**
     * @param {DateTime|string|bool} $date
     *
     * @return {DateTime|bool} Return MongoDate or false for Invalid Dates
     */
    public function normalizeDateToMongo($date)
    {
        if ($date instanceof \DateTime) {
            return new \MongoDate($date->getTimestamp());
        }

        $date = preg_replace('/\.[0-9]+/', '', $date);
        $date = \DateTime::createFromFormat(\DateTime::ISO8601, $date);

        return new \MongoDate($date->getTimestamp());
    }
}