<?php

namespace Doctrine\ODM\CouchDB\Types;

abstract class Type
{
    const MIXED = 'mixed';
    const TARRAY = 'array';
    const BOOLEAN = 'boolean';
    const DATETIME = 'datetime';
    const FLOAT = 'float';
    const INTEGER = 'integer';
    const OBJECT = 'object';
    const STRING = 'string';

    /** Map of already instantiated type objects. One instance per type (flyweight). */
    private static $_typeObjects = array();

    /** The map of supported doctrine mapping types. */
    private static $_typesMap = array(
        self::MIXED => 'Doctrine\ODM\CouchDB\Types\MixedType',
        self::BOOLEAN => 'Doctrine\ODM\CouchDB\Types\BooleanType',
        self::INTEGER => 'Doctrine\ODM\CouchDB\Types\IntegerType',
        self::STRING => 'Doctrine\ODM\CouchDB\Types\StringType',
        self::FLOAT => 'Doctrine\ODM\CouchDB\Types\FloatType',
        self::DATETIME => 'Doctrine\ODM\CouchDB\Types\DateTimeType',
    );

    protected function __construct() {}

    /**
     * @param mixed $value
     * @return mixed
     */
    abstract public function convertToCouchDBValue($value);

    /**
     * @param mixed $value
     * @return mixed
     */
    abstract public function convertToPHPValue($value);

    /**
     * Factory method to create type instances.
     * Type instances are implemented as flyweights.
     *
     * @static
     * @throws TypeException
     * @param string $name The name of the type (as returned by getName()).
     * @return \Doctrine\ODM\CouchDB\Types\Type
     */
    public static function getType($name)
    {
        if ( ! isset(self::$_typeObjects[$name])) {
            if ( ! isset(self::$_typesMap[$name])) {
                throw TypeException::unknownType($name);
            }
            self::$_typeObjects[$name] = new self::$_typesMap[$name]();
        }

        return self::$_typeObjects[$name];
    }

    /**
     * Adds a custom type to the type map.
     *
     * @static
     * @param string $name Name of the type. This should correspond to what getName() returns.
     * @param string $className The class name of the custom type.
     * @throws \Doctrine\ODM\CouchDB\CouchDBException
     */
    public static function addType($name, $className)
    {
        if (isset(self::$_typesMap[$name])) {
            throw TypeException::typeExists($name);
        }

        self::$_typesMap[$name] = $className;
    }

    /**
     * Checks if exists support for a type.
     *
     * @static
     * @param string $name Name of the type
     * @return boolean TRUE if type is supported; FALSE otherwise
     */
    public static function hasType($name)
    {
        return isset(self::$_typesMap[$name]);
    }

    /**
     * Overrides an already defined type to use a different implementation.
     *
     * @static
     * @param string $name
     * @param string $className
     * @throws \Doctrine\ODM\CouchDB\CouchDBException
     */
    public static function overrideType($name, $className)
    {
        if ( ! isset(self::$_typesMap[$name])) {
            throw TypeException::typeNotFound($name);
        }

        self::$_typesMap[$name] = $className;
    }

    /**
     * Get the types array map which holds all registered types and the corresponding
     * type class
     *
     * @return array $typesMap
     */
    public static function getTypesMap()
    {
        return self::$_typesMap;
    }

    public function __toString()
    {
        $e = explode('\\', get_class($this));
        return str_replace('Type', '', end($e));
    }
}


<?php

namespace Doctrine\ODM\CouchDB\Proxy;

use Doctrine\ODM\CouchDB\DocumentManager;
use Doctrine\ODM\CouchDB\Mapping\ClassMetadata;
use Doctrine\Common\Util\ClassUtils;

/**
 * This factory is used to create proxy objects for entities at runtime.
 *
 * @author Roman Borschel <roman@code-factory.org>
 * @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
 * @author Nils Adermann <naderman@naderman.de>
 *
 * This whole thing is copy & pasted from ORM - should really be slightly
 * refactored to generate
 */
class ProxyFactory
{
    /** The DocumentManager this factory is bound to. */
    private $dm;
    /** Whether to automatically (re)generate proxy classes. */
    private $autoGenerate;
    /** The namespace that contains all proxy classes. */
    private $proxyNamespace;
    /** The directory that contains all proxy classes. */
    private $proxyDir;

    /**
     * Initializes a new instance of the <tt>ProxyFactory</tt> class that is
     * connected to the given <tt>DocumentManager</tt>.
     *
     * @param DocumentManager $dm The DocumentManager the new factory works for.
     * @param string $proxyDir The directory to use for the proxy classes. It must exist.
     * @param string $proxyNs The namespace to use for the proxy classes.
     * @param boolean $autoGenerate Whether to automatically generate proxy classes.
     * @throws ProxyException
     */
    public function __construct(DocumentManager $dm, $proxyDir, $proxyNs, $autoGenerate = false)
    {
        if ( ! $proxyDir) {
            throw ProxyException::proxyDirectoryRequired();
        }
        if ( ! $proxyNs) {
            throw ProxyException::proxyNamespaceRequired();
        }
        $this->dm = $dm;
        $this->proxyDir = $proxyDir;
        $this->autoGenerate = $autoGenerate;
        $this->proxyNamespace = $proxyNs;
    }

    /**
     * Gets a reference proxy instance for the entity of the given type and identified by
     * the given identifier.
     *
     * @param string $className
     * @param mixed $identifier
     * @return object
     */
    public function getProxy($className, $identifier)
    {
        $fqn = ClassUtils::generateProxyClassName($className, $this->proxyNamespace);

        if ( ! class_exists($fqn, false)) {
            $fileName = $this->getProxyFileName($className);
            if ($this->autoGenerate) {
                $this->generateProxyClass($this->dm->getClassMetadata($className), $fileName, self::$proxyClassTemplate);
            }
            require $fileName;
        }

        if ( ! $this->dm->getMetadataFactory()->hasMetadataFor($fqn)) {
            $this->dm->getMetadataFactory()->setMetadataFor($fqn, $this->dm->getClassMetadata($className));
        }

        return new $fqn($this->dm, $identifier);
    }

    /**
     * Generate the Proxy file name
     *
     * @param string $className
     * @param string $baseDir Optional base directory for proxy file name generation.
     *                        If not specified, the directory configured on the Configuration of the
     *                        EntityManager will be used by this factory.
     * @return string
     */
    private function getProxyFileName($className, $baseDir = null)
    {
        $proxyDir = $baseDir ?: $this->proxyDir;

        return $proxyDir . DIRECTORY_SEPARATOR . '__CG__' . str_replace('\\', '', $className) . '.php';
    }

    /**
     * Generates proxy classes for all given classes.
     *
     * @param array $classes The classes (ClassMetadata instances) for which to generate proxies.
     * @param string $toDir The target directory of the proxy classes. If not specified, the
     *                      directory configured on the Configuration of the DocumentManager used
     *                      by this factory is used.
     */
    public function generateProxyClasses(array $classes, $toDir = null)
    {
        $proxyDir = $toDir ?: $this->proxyDir;
        $proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        foreach ($classes as $class) {
            /* @var $class ClassMetadata */
            if ($class->isMappedSuperclass) {
                continue;
            }

            $proxyFileName = $this->getProxyFileName($class->name, $toDir);
            $this->generateProxyClass($class, $proxyFileName, self::$proxyClassTemplate);
        }
    }

    /**
     * Generates a proxy class file.
     *
     * @param $class
     * @param $fileName
     * @param $template
     */
    private function generateProxyClass($class, $fileName, $template)
    {
        $methods = $this->generateMethods($class);
        $sleepImpl = $this->generateSleep($class);

        $placeholders = array(
            '<namespace>',
            '<proxyClassName>', '<className>',
            '<methods>', '<sleepImpl>'
        );

        $className = ltrim($class->name, '\\');
        $proxyClassName = ClassUtils::generateProxyClassName($class->name, $this->proxyNamespace);
        $parts = explode('\\', strrev($proxyClassName), 2);
        $proxyClassNamespace = strrev($parts[1]);
        $proxyClassName = strrev($parts[0]);

        $replacements = array(
            $proxyClassNamespace,
            $proxyClassName,
            $className,
            $methods,
            $sleepImpl
        );

        $template = str_replace($placeholders, $replacements, $template);

        file_put_contents($fileName, $template, LOCK_EX);
    }

    /**
     * Generates the methods of a proxy class.
     *
     * @param ClassMetadata $class
     * @return string The code of the generated methods.
     */
    private function generateMethods(ClassMetadata $class)
    {
        $methods = '';

        foreach ($class->reflClass->getMethods() as $method) {
            /* @var $method \ReflectionMethod */
            if ($method->isConstructor() || strtolower($method->getName()) == "__sleep") {
                continue;
            }

            if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) {
                $methods .= PHP_EOL . '    public function ';
                if ($method->returnsReference()) {
                    $methods .= '&';
                }
                $methods .= $method->getName() . '(';
                $firstParam = true;
                $parameterString = $argumentString = '';

                foreach ($method->getParameters() as $param) {
                    if ($firstParam) {
                        $firstParam = false;
                    } else {
                        $parameterString .= ', ';
                        $argumentString  .= ', ';
                    }

                    // We need to pick the type hint class too
                    if (($paramClass = $param->getClass()) !== null) {
                        $parameterString .= '\\' . $paramClass->getName() . ' ';
                    } else if ($param->isArray()) {
                        $parameterString .= 'array ';
                    }

                    if ($param->isPassedByReference()) {
                        $parameterString .= '&';
                    }

                    $parameterString .= '$' . $param->getName();
                    $argumentString  .= '$' . $param->getName();

                    if ($param->isDefaultValueAvailable()) {
                        $parameterString .= ' = ' . var_export($param->getDefaultValue(), true);
                    }
                }

                $methods .= $parameterString . ')';
                $methods .= PHP_EOL . '    {' . PHP_EOL;
                $methods .= '        $this->__load();' . PHP_EOL;
                $methods .= '        return parent::' . $method->getName() . '(' . $argumentString . ');';
                $methods .= PHP_EOL . '    }' . PHP_EOL;
            }
        }

        return $methods;
    }

    /**
     * Generates the code for the __sleep method for a proxy class.
     *
     * @param $class
     * @return string
     */
    private function generateSleep(ClassMetadata $class)
    {
        $sleepImpl = '';

        if ($class->reflClass->hasMethod('__sleep')) {
            $sleepImpl .= "return array_merge(array('__isInitialized__'), parent::__sleep());";
        } else {
            $sleepImpl .= "return array('__isInitialized__', ";

            $properties = array();
            foreach ($class->fieldMappings as $name => $prop) {
                $properties[] = "'$name'";
            }

            $sleepImpl .= implode(',', $properties) . ');';
        }

        return $sleepImpl;
    }

    /** Proxy class code template */
    private static $proxyClassTemplate = <<<'PHP'
<?php

namespace <namespace>;

/**
 * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
 */
class <proxyClassName> extends \<className> implements \Doctrine\ODM\CouchDB\Proxy\Proxy
{
    private $__doctrineDocumentManager__;
    private $__doctrineIdentifier__;
    public $__isInitialized__ = false;
    public function __construct($documentManager, $identifier)
    {
        $this->__doctrineDocumentManager__ = $documentManager;
        $this->__doctrineIdentifier__ = $identifier;
    }
    public function __load()
    {
        if (!$this->__isInitialized__ && $this->__doctrineDocumentManager__) {
            $this->__isInitialized__ = true;
            $this->__doctrineDocumentManager__->refresh($this);
            unset($this->__doctrineDocumentManager__, $this->__doctrineIdentifier__);
        }
    }

    public function __isInitialized()
    {
        return $this->__isInitialized__;
    }

    <methods>

    public function __sleep()
    {
        <sleepImpl>
    }
}
PHP;
}


<?php

namespace Doctrine\ODM\CouchDB;

use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;

use Doctrine\CouchDB\HTTP\Client;
use Doctrine\CouchDB\HTTP\SocketClient;
use Doctrine\CouchDB\HTTP\LoggingClient;

use Doctrine\ODM\CouchDB\Mapping\MetadataResolver\MetadataResolver;
use Doctrine\ODM\CouchDB\Mapping\MetadataResolver\DoctrineResolver;
use Doctrine\ODM\CouchDB\Migrations\DocumentMigration;

/**
 * Configuration class
 *
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link        www.doctrine-project.com
 * @since       1.0
 * @author      Benjamin Eberlei <kontakt@beberlei.de>
 * @author      Lukas Kahwe Smith <smith@pooteeweet.org>
 */
class Configuration
{
    /**
     * Array of attributes for this configuration instance.
     *
     * @var array $attributes
     */
    private $attributes = array(
        'designDocuments' => array(
            'doctrine_associations' => array(
                'className' => 'Doctrine\ODM\CouchDB\View\DoctrineAssociations',
                'options' => array(),
            ),
            'doctrine_repositories' => array(
                'className' => 'Doctrine\ODM\CouchDB\View\DoctrineRepository',
                'options' => array(),
            ),
        ),
        'writeDoctrineMetadata' => true,
        'validateDoctrineMetadata' => true,
        'UUIDGenerationBufferSize' => 20,
        'proxyNamespace' => 'MyCouchDBProxyNS',
        'allOrNothingFlush' => true,
        'luceneHandlerName' => false,
        'metadataResolver' => null,
        'autoGenerateProxyClasses' => false,
    );

    /**
     * Sets the default UUID Generator buffer size
     *
     * @param integer $UUIDGenerationBufferSize
     */
    public function setUUIDGenerationBufferSize($UUIDGenerationBufferSize)
    {
        $this->attributes['UUIDGenerationBufferSize'] = $UUIDGenerationBufferSize;
    }

    /**
     * Gets the default UUID Generator buffer size
     *
     * @return integer
     */
    public function getUUIDGenerationBufferSize()
    {
        return $this->attributes['UUIDGenerationBufferSize'];
    }
    /**
     * Sets if all CouchDB document metadata should be validated on read
     *
     * @param boolean $validateDoctrineMetadata
     */
    public function setValidateDoctrineMetadata($validateDoctrineMetadata)
    {
        $this->attributes['validateDoctrineMetadata'] = $validateDoctrineMetadata;
    }

    /**
     * Gets if all CouchDB document metadata should be validated on read
     *
     * @return boolean
     */
    public function getValidateDoctrineMetadata()
    {
        return $this->attributes['validateDoctrineMetadata'];
    }

    /**
     * Adds a namespace under a certain alias.
     *
     * @param string $alias
     * @param string $namespace
     */
    public function addDocumentNamespace($alias, $namespace)
    {
        $this->attributes['documentNamespaces'][$alias] = $namespace;
    }

    /**
     * Resolves a registered namespace alias to the full namespace.
     *
     * @param string $documentNamespaceAlias
     * @return string
     * @throws CouchDBException
     */
    public function getDocumentNamespace($documentNamespaceAlias)
    {
        if ( ! isset($this->attributes['documentNamespaces'][$documentNamespaceAlias])) {
            throw CouchDBException::unknownDocumentNamespace($documentNamespaceAlias);
        }

        return trim($this->attributes['documentNamespaces'][$documentNamespaceAlias], '\\');
    }

    /**
     * Set the document alias map
     *
     * @param array $documentNamespaces
     * @return void
     */
    public function setDocumentNamespaces(array $documentNamespaces)
    {
        $this->attributes['documentNamespaces'] = $documentNamespaces;
    }

    /**
     * Sets the cache driver implementation that is used for metadata caching.
     *
     * @param MappingDriver $driverImpl
     * @todo Force parameter to be a Closure to ensure lazy evaluation
     *       (as soon as a metadata cache is in effect, the driver never needs to initialize).
     */
    public function setMetadataDriverImpl(MappingDriver $driverImpl)
    {
        $this->attributes['metadataDriverImpl'] = $driverImpl;
    }

    /**
     * Add a new default annotation driver with a correctly configured annotation reader.
     *
     * @param array $paths
     * @return Mapping\Driver\AnnotationDriver
     */
    public function newDefaultAnnotationDriver($paths = array())
    {
        $reader = new \Doctrine\Common\Annotations\SimpleAnnotationReader();
        $reader->addNamespace('Doctrine\ODM\CouchDB\Mapping\Annotations');

        return new \Doctrine\ODM\CouchDB\Mapping\Driver\AnnotationDriver($reader, (array) $paths);
    }

    public function setMetadataResolverImpl(MetadataResolver $resolver)
    {
        $this->attributes['metadataResolver'] = $resolver;
    }

    public function getMetadataResolverImpl()
    {
        if (!$this->attributes['metadataResolver']) {
            return new DoctrineResolver();
        }
        return $this->attributes['metadataResolver'];
    }

    /**
     * Gets the cache driver implementation that is used for the mapping metadata.
     *
     * @return MappingDriver
     */
    public function getMetadataDriverImpl()
    {
        if (!isset($this->attributes['metadataDriverImpl'])) {
            $this->attributes['metadataDriverImpl'] = $this->newDefaultAnnotationDriver();
        }
        return $this->attributes['metadataDriverImpl'];
    }

    /**
     * Gets the cache driver implementation that is used for metadata caching.
     *
     * @return \Doctrine\Common\Cache\Cache
     */
    public function getMetadataCacheImpl()
    {
        return isset($this->attributes['metadataCacheImpl']) ?
                $this->attributes['metadataCacheImpl'] : null;
    }

    /**
     * Sets the cache driver implementation that is used for metadata caching.
     *
     * @param \Doctrine\Common\Cache\Cache $cacheImpl
     */
    public function setMetadataCacheImpl(Cache $cacheImpl)
    {
        $this->attributes['metadataCacheImpl'] = $cacheImpl;
    }

    /**
     * Sets the directory where Doctrine generates any necessary proxy class files.
     *
     * @param string $dir
     */
    public function setProxyDir($dir)
    {
        $this->attributes['proxyDir'] = $dir;
    }

    /**
     * Gets the directory where Doctrine generates any necessary proxy class files.
     *
     * @return string
     */
    public function getProxyDir()
    {
        if (!isset($this->attributes['proxyDir'])) {
            $this->attributes['proxyDir'] = \sys_get_temp_dir();
        }

        return $this->attributes['proxyDir'];
    }

    /**
     * Sets the namespace for Doctrine proxy class files.
     *
     * @param string $namespace
     */
    public function setProxyNamespace($namespace)
    {
        $this->attributes['proxyNamespace'] = $namespace;
    }

    /**
     * Gets the namespace for Doctrine proxy class files.
     *
     * @return string
     */
    public function getProxyNamespace()
    {
        return $this->attributes['proxyNamespace'];
    }

    public function setAutoGenerateProxyClasses($bool)
    {
        $this->attributes['autoGenerateProxyClasses'] = $bool;
    }

    public function getAutoGenerateProxyClasses()
    {
        return $this->attributes['autoGenerateProxyClasses'];
    }

    /**
     * @param string $name
     * @param string $className
     * @param array $options
     */
    public function addDesignDocument($name, $className, $options)
    {
        $this->attributes['designDocuments'][$name] = array(
            'className' => $className,
            'options' => $options,
        );
    }

    /**
     * @return array
     */
    public function getDesignDocumentNames()
    {
        return array_keys($this->attributes['designDocuments']);
    }

    /**
     * @param  string $name
     * @return array
     */
    public function getDesignDocument($name)
    {
        if (isset($this->attributes['designDocuments'][$name])) {
            return $this->attributes['designDocuments'][$name];
        }
        return null;
    }

    /**
     * @param bool $allOrNothing
     */
    public function setAllOrNothingFlush($allOrNothing)
    {
        $this->attributes['allOrNothingFlush'] = (bool)$allOrNothing;
    }

    /**
     * @return bool
     */
    public function getAllOrNothingFlush()
    {
        return $this->attributes['allOrNothingFlush'];
    }

    public function setLuceneHandlerName($handlerName = '_fti')
    {
        $this->attributes['luceneHandlerName'] = $handlerName;
    }

    public function getLuceneHandlerName()
    {
        if (!$this->attributes['luceneHandlerName']) {
            throw CouchDBException::luceneNotConfigured();
        }

        return $this->attributes['luceneHandlerName'];
    }

    /**
     * @return \Doctrine\ODM\CouchDB\Migrations\NullMigration;
     */
    public function getMigrations()
    {
        if (!isset($this->attributes['migrations'])) {
            $this->attributes['migrations'] = new Migrations\NullMigration();
        }

        return $this->attributes['migrations'];
    }

    /**
     * @param DocumentMigration $migration
     */
    public function setMigrations(DocumentMigration $migration)
    {
        $this->attributes['migrations'] = $migration;
    }
}


<?php

namespace Doctrine\ODM\CouchDB\Mapping;

use Doctrine\ODM\CouchDB\DocumentManager,
    Doctrine\ODM\CouchDB\CouchDBException,
    Doctrine\ODM\CouchDB\Mapping\ClassMetadata,
    Doctrine\Common\Persistence\Mapping\Driver\MappingDriver,
    Doctrine\Common\Persistence\Mapping\ClassMetadata as ClassMetadataInterface,
    Doctrine\Common\Persistence\Mapping\ReflectionService,
    Doctrine\Common\Persistence\Mapping\RuntimeReflectionService,
    Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory;

/**
 * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
 * metadata mapping information of a class which describes how a class should be mapped
 * to a document database.

 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link        www.doctrine-project.com
 * @since       1.0
 * @author      Benjamin Eberlei <kontakt@beberlei.de>
 * @author      Lukas Kahwe Smith <smith@pooteeweet.org>
 */
class ClassMetadataFactory extends AbstractClassMetadataFactory
{
    /**
     * @var DocumentManager
     */
    private $dm;

    /**
     *  The used metadata driver.
     *
     * @var MappingDriver
     */
    private $driver;

    /**
     * Creates a new factory instance that uses the given DocumentManager instance.
     *
     * @param DocumentManager $dm The DocumentManager instance
     * @throws \RuntimeException
     */
    public function __construct(DocumentManager $dm)
    {
        $this->dm = $dm;
        $config = $this->dm->getConfiguration();
        $this->setCacheDriver($config->getMetadataCacheImpl());
        $this->driver = $config->getMetadataDriverImpl();
        if (!$this->driver) {
            throw new \RuntimeException('No metadata driver was configured.');
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents)
    {
        /** @var $parent ClassMetaData */
        if ($parent) {
            $this->addAssociationsMapping($class, $parent);
            $this->addFieldMapping($class, $parent);
            $this->addIndexes($class, $parent);
            $parent->deriveChildMetadata($class);
            $class->setParentClasses($nonSuperclassParents);
        }

        if ($this->getDriver()) {
            $this->getDriver()->loadMetadataForClass($class->getName(), $class);
        }

        $this->validateMapping($class);
    }

    /**
     * Check for any possible shortcomings in the class:
     *
     * The class must have an identifier field unless it's an embedded document or mapped superclass.
     */
    private function validateMapping(ClassMetadataInterface $class)
    {
        if (!$class->identifier && !$class->isEmbeddedDocument && !$class->isMappedSuperclass) {
            throw new MappingException("An identifier (@Id) field is required in {$class->getName()}.");
        }
    }

    private function addFieldMapping(ClassMetadataInterface $class, ClassMetadataInterface $parent)
    {
        foreach ($parent->reflFields as $name => $field) {
            $class->reflFields[$name] = $field;
        }

        foreach ($parent->fieldMappings as $name => $field) {
            $class->fieldMappings[$name] = $field;
        }

        foreach ($parent->jsonNames as $name => $field) {
            $class->jsonNames[$name] = $field;
        }

        if ($parent->identifier) {
            $class->setIdentifier($parent->identifier);
        }
    }

    private function addIndexes(ClassMetadata $class, ClassMetadata $parent)
    {
        $class->indexes = $parent->indexes;
    }

    /**
     *
     * @param ClassMetadataInterface $class
     * @param ClassMetadataInterface $parent
     */
    private function addAssociationsMapping(ClassMetadataInterface $class, ClassMetadataInterface $parent)
    {
        foreach ($parent->associationsMappings as $name => $field) {
            $class->associationsMappings[$name] = $field;
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function getFqcnFromAlias($namespaceAlias, $simpleClassName)
    {
        return $this->dm->getConfiguration()->getDocumentNamespace($namespaceAlias) . '\\' . $simpleClassName;
    }

    /**
     * Forces the factory to load the metadata of all classes known to the underlying
     * mapping driver.
     *
     * @return array The ClassMetadata instances of all mapped classes.
     */
    public function getAllMetadata()
    {
        $metadata = array();
        foreach ($this->driver->getAllClassNames() as $className) {
            $metadata[] = $this->getMetadataFor($className);
        }

        return $metadata;
    }

    /**
     * Gets the class metadata descriptor for a class.
     *
     * @param string $className The name of the class.
     * @return ClassMetadata
     * @throws MappingException
     */
    public function getMetadataFor($className)
    {
        $metadata = parent::getMetadataFor($className);

        if ($metadata) {
            return $metadata;
        }

        throw MappingException::classNotMapped($className);
    }

    /**
     * Loads the metadata of the class in question and all it's ancestors whose metadata
     * is still not loaded.
     *
     * @param string $className The name of the class for which the metadata should get loaded.
     * @return array
     * @throws MappingException
     */
    protected function loadMetadata($className)
    {
        if (class_exists($className)) {
            return parent::loadMetadata($className);
        }
        throw MappingException::classNotFound($className);
    }

    /**
     * Creates a new ClassMetadata instance for the given class name.
     *
     * @param string $className
     * @return ClassMetadata
     */
    protected function newClassMetadataInstance($className)
    {
        return new ClassMetadata($className);
    }

    /**
     * {@inheritdoc}
     */
    protected function getDriver()
    {
        return $this->driver;
    }

    /**
     * {@inheritdoc}
     */
    protected function initialize()
    {
        $this->initialized = true;
    }

    /**
     * {@inheritdoc}
     */
    protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService)
    {
        $class->initializeReflection($reflService);
    }

    /**
     * {@inheritdoc}
     */
    protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService)
    {
        $class->wakeupReflection($reflService);
    }

    /**
     * {@inheritDoc}
     */
    protected function isEntity(ClassMetadataInterface $class)
    {
        return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false;
    }
}


<?php

namespace Doctrine\ODM\CouchDB\Id;

use Doctrine\ODM\CouchDB\DocumentManager;
use Doctrine\ODM\CouchDB\Mapping\ClassMetadata;

/**
 * Used to abstract ID generation
 *
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link        www.doctrine-project.com
 * @since       1.0
 * @author      Benjamin Eberlei <kontakt@beberlei.de>
 * @author      Lukas Kahwe Smith <smith@pooteeweet.org>
 */
abstract class IdGenerator
{
    /**
     * @param int $generatorType
     *
     * @return IdGenerator
     * @throws \Exception
     */
    static public function create($generatorType)
    {
        switch ($generatorType) {
            case ClassMetadata::IDGENERATOR_ASSIGNED:
                $instance = new AssignedIdGenerator();
                break;
            case ClassMetadata::IDGENERATOR_UUID:
                $instance = new CouchUUIDGenerator();
                break;
            default:
                throw new \Exception("ID Generator does not exist!");
        }
        return $instance;
    }

    /**
     * @param object $document
     * @param ClassMetadata $cm
     * @param DocumentManager $dm
     */
    abstract public function generate($document, ClassMetadata $cm, DocumentManager $dm);
}


<?php

namespace Doctrine\ODM\CouchDB;

use Doctrine\Common\EventManager;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\CouchDB\CouchDBClient;
use Doctrine\CouchDB\View;
use Doctrine\CouchDB\View\Query;
use Doctrine\ODM\CouchDB\Mapping\ClassMetadataFactory;
use Doctrine\ODM\CouchDB\View\ODMQuery;
use Doctrine\ODM\CouchDB\View\ODMLuceneQuery;
use Doctrine\ODM\CouchDB\CouchDBException;

class DocumentManager implements ObjectManager
{
    /**
     * @var Configuration
     */
    private $config;

    /**
     * @var Mapping\ClassMetadataFactory
     */
    private $metadataFactory;

    /**
     * @var UnitOfWork
     */
    private $unitOfWork = null;

    /**
     * @var \Doctrine\ODM\CouchDB\Proxy\ProxyFactory
     */
    private $proxyFactory = null;

    /**
     * @var array
     */
    private $repositories = array();

    /**
     * @var CouchDBClient
     */
    private $couchDBClient = null;

    /**
     * @var EventManager
     */
    private $evm;

    /**
     * @param CouchDBClient $couchClient
     * @param Configuration $config
     * @param EventManager $evm
     */
    public function __construct(CouchDBClient $couchClient, Configuration $config = null, EventManager $evm = null)
    {
        $this->couchDBClient = $couchClient;
        $this->config = $config ?: new Configuration();
        $this->evm = $evm ?: new EventManager();
        $this->metadataFactory = new ClassMetadataFactory($this);
        $this->unitOfWork = new UnitOfWork($this);
        $this->proxyFactory = new Proxy\ProxyFactory($this, $this->config->getProxyDir(), $this->config->getProxyNamespace(), $this->config->getAutoGenerateProxyClasses());
    }

    /**
     * @return EventManager
     */
    public function getEventManager()
    {
        return $this->evm;
    }

    /**
     * @return CouchDBClient
     */
    public function getCouchDBClient()
    {
        return $this->couchDBClient;
    }

    /**
     * Factory method for a Document Manager.
     *
     * @param array|CouchDBClient $couchParams
     * @param Configuration       $config
     * @param EventManager        $evm
     * @return DocumentManager
     * @throws \InvalidArgumentException
     */
    public static function create($couchParams, Configuration $config = null, EventManager $evm = null)
    {
        if (is_array($couchParams)) {
            $couchClient = CouchDBClient::create($couchParams);
        } else if ($couchParams instanceof CouchDBClient) {
            $couchClient = $couchParams;
        } else {
            throw new \InvalidArgumentException("Expecting array of instance of CouchDBClient as first argument to DocumentManager::create().");
        }

        return new DocumentManager($couchClient, $config, $evm);
    }

    /**
     * @return ClassMetadataFactory
     */
    public function getMetadataFactory()
    {
        return $this->metadataFactory;
    }

    /**
     * @return Proxy\ProxyFactory
     */
    public function getProxyFactory()
    {
        return $this->proxyFactory;
    }

    public function getHttpClient()
    {
        return $this->couchDBClient->getHttpClient();
    }

    public function getDatabase()
    {
        return $this->couchDBClient->getDatabase();
    }

    /**
     * @return Configuration
     */
    public function getConfiguration()
    {
        return $this->config;
    }

    /**
     * @return ClassMetadataFactory
     */
    public function getClassMetadataFactory()
    {
        return $this->metadataFactory;
    }

    /**
     * @param  string $class
     * @return \Doctrine\ODM\CouchDB\Mapping\ClassMetadata
     */
    public function getClassMetadata($class)
    {
        return $this->metadataFactory->getMetadataFor($class);
    }

    /**
     * Find the Document with the given id.
     *
     * Will return null if the document wasn't found.
     *
     * @param string $documentName
     * @param string $id
     * @return object
     */
    public function find($documentName, $id)
    {
        return $this->getRepository($documentName)->find($id);
    }

    /**
     * @param  string $documentName
     * @return \Doctrine\ODM\CouchDB\DocumentRepository
     */
    public function getRepository($documentName)
    {
        $documentName  = ltrim($documentName, '\\');
        if (!isset($this->repositories[$documentName])) {
            $class = $this->getClassMetadata($documentName);
            if ($class->customRepositoryClassName) {
                $repositoryClass = $class->customRepositoryClassName;
            } else {
                $repositoryClass = 'Doctrine\ODM\CouchDB\DocumentRepository';
            }
            $this->repositories[$documentName] = new $repositoryClass($this, $class);
        }
        return $this->repositories[$documentName];
    }

    /**
     * Create a Query for the view in the specified design document.
     *
     * @param  string $designDocName
     * @param  string $viewName
     * @return \Doctrine\ODM\CouchDB\View\ODMQuery
     */
    public function createQuery($designDocName, $viewName)
    {
        $designDoc = $this->config->getDesignDocument($designDocName);
        if ($designDoc) {
            $designDoc = new $designDoc['className']($designDoc['options']);
        }
        $query = new ODMQuery($this->couchDBClient->getHttpClient(), $this->couchDBClient->getDatabase(), $designDocName, $viewName, $designDoc);
        $query->setDocumentManager($this);
        return $query;
    }

    /**
     * Create a Native query for the view of the specified design document.
     *
     * A native query will return an array of data from the &include_docs=true parameter.
     *
     * @param  string $designDocName
     * @param  string $viewName
     * @return \Doctrine\CouchDB\View\Query
     */
    public function createNativeQuery($designDocName, $viewName)
    {
        $designDoc = $this->config->getDesignDocument($designDocName);
        if ($designDoc) {
            $designDoc = new $designDoc['className']($designDoc['options']);
        }
        $query = new Query($this->couchDBClient->getHttpClient(), $this->couchDBClient->getDatabase(), $designDocName, $viewName, $designDoc);
        return $query;
    }

    /**
     * Create a CouchDB-Lucene Query.
     *
     * @param string $designDocName
     * @param string $viewName
     * @return \Doctrine\ODM\CouchDB\View\ODMLuceneQuery
     */
    public function createLuceneQuery($designDocName, $viewName)
    {
        $luceneHandlerName = $this->config->getLuceneHandlerName();
        $designDoc = $this->config->getDesignDocument($designDocName);
        if ($designDoc) {
            $designDoc = new $designDoc['className']($designDoc['options']);
        }
        $query = new ODMLuceneQuery($this->couchDBClient->getHttpClient(),
            $this->couchDBClient->getDatabase(), $luceneHandlerName, $designDocName,
            $viewName, $designDoc
        );
        $query->setDocumentManager($this);
        return $query;
    }

    public function persist($object)
    {
        $this->unitOfWork->scheduleInsert($object);
    }

    public function remove($object)
    {
        $this->unitOfWork->scheduleRemove($object);
    }

    /**
     * Refresh the given document by querying the CouchDB to get the current state.
     *
     * @param object $document
     */
    public function refresh($document)
    {
        $this->getUnitOfWork()->refresh($document);
    }

    public function merge($document)
    {
        return $this->getUnitOfWork()->merge($document);
    }

    public function detach($document)
    {
        $this->getUnitOfWork()->detach($document);
    }

    /**
     * Gets a reference to the entity identified by the given type and identifier
     * without actually loading it, if the entity is not yet loaded.
     *
     * @param string $documentName The name of the entity type.
     * @param mixed $identifier The entity identifier.
     * @return object The entity reference.
     */
    public function getReference($documentName, $identifier)
    {
        $class = $this->metadataFactory->getMetadataFor(ltrim($documentName, '\\'));

        // Check identity map first, if its already in there just return it.
        if ($document = $this->unitOfWork->tryGetById($identifier)) {
            return $document;
        }
        $document = $this->proxyFactory->getProxy($class->name, $identifier);
        $this->unitOfWork->registerManaged($document, $identifier, null);

        return $document;
    }

    public function flush()
    {
        $this->unitOfWork->flush(); // todo: rename commit
    }

    /**
     * @param  object $document
     * @return bool
     */
    public function contains($document)
    {
        return $this->unitOfWork->contains($document);
    }

    /**
     * @return UnitOfWork
     */
    public function getUnitOfWork()
    {
        return $this->unitOfWork;
    }

    /**
     * Clears the ObjectManager. All objects that are currently managed
     * by this ObjectManager become detached.
     *
     * @param string $objectName if given, only objects of this type will get detached
     * @throws CouchDBException
     */
    public function clear($objectName = null)
    {
        if ($objectName === null) {
            // Todo: Do a real delegated clear?
            $this->unitOfWork = new UnitOfWork($this);
        } else {
            //TODO
            throw new CouchDBException("DocumentManager#clear(\$objectName) not yet implemented.");
        }
    }

    /**
     * Initialize an object that is a lazy load proxy, or do nothing.
     *
     * @param object $obj
     */
    public function initializeObject($obj)
    {
        if ($obj instanceof PersistentCollection) {
            $obj->initialize();
        } else if ($obj instanceof Proxy\Proxy) {
            $obj->__doctrineLoad__();
        }
    }
}


<?php

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Spomky-Labs
 *
 * This software may be modified and distributed under the terms
 * of the MIT license.  See the LICENSE file for details.
 */

namespace AESGCM;

use Assert\Assertion;

final class AESGCM
{
    /**
     * @param string      $K          Key encryption key
     * @param string      $IV         Initialization vector
     * @param null|string $P          Data to encrypt (null for authentication)
     * @param null|string $A          Additional Authentication Data
     * @param int         $tag_length Tag length
     *
     * @return array
     */
    public static function encrypt($K, $IV, $P = null, $A = null, $tag_length = 128)
    {
        Assertion::string($K, 'The key encryption key must be a binary string.');
        $key_length = mb_strlen($K, '8bit') * 8;
        Assertion::inArray($key_length, [128, 192, 256], 'Bad key encryption key length.');
        Assertion::string($IV, 'The Initialization Vector must be a binary string.');
        Assertion::nullOrString($P, 'The data to encrypt must be null or a binary string.');
        Assertion::nullOrString($A, 'The Additional Authentication Data must be null or a binary string.');
        Assertion::integer($tag_length, 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
        Assertion::inArray($tag_length, [128, 120, 112, 104, 96], 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');

        if (version_compare(PHP_VERSION, '7.1.0RC5') >= 0 && null !== $P) {
            return self::encryptWithPHP71($K, $key_length, $IV, $P, $A, $tag_length);
        } elseif (class_exists('\Crypto\Cipher')) {
            return self::encryptWithCryptoExtension($K, $key_length, $IV, $P, $A, $tag_length);
        }

        return self::encryptWithPHP($K, $key_length, $IV, $P, $A, $tag_length);
    }

    /**
     * This method will append the tag at the end of the ciphertext.
     *
     * @param string      $K          Key encryption key
     * @param string      $IV         Initialization vector
     * @param null|string $P          Data to encrypt (null for authentication)
     * @param null|string $A          Additional Authentication Data
     * @param int         $tag_length Tag length
     *
     * @return string
     */
    public static function encryptAndAppendTag($K, $IV, $P = null, $A = null, $tag_length = 128)
    {
        return implode(self::encrypt($K, $IV, $P, $A, $tag_length));
    }

    /**
     * @param string      $K          Key encryption key
     * @param string      $key_length Key length
     * @param string      $IV         Initialization vector
     * @param null|string $P          Data to encrypt (null for authentication)
     * @param null|string $A          Additional Authentication Data
     * @param int         $tag_length Tag length
     *
     * @return array
     */
    private static function encryptWithPHP71($K, $key_length, $IV, $P = null, $A = null, $tag_length = 128)
    {
        $mode = 'aes-'.($key_length).'-gcm';
        $T = null;
        $C = openssl_encrypt($P, $mode, $K, OPENSSL_RAW_DATA, $IV, $T, $A, $tag_length / 8);
        Assertion::true(false !== $C, 'Unable to encrypt the data.');

        return [$C, $T];
    }

    /**
     * @param string      $K          Key encryption key
     * @param string      $key_length Key length
     * @param string      $IV         Initialization vector
     * @param null|string $P          Data to encrypt (null for authentication)
     * @param null|string $A          Additional Authentication Data
     * @param int         $tag_length Tag length
     *
     * @return array
     */
    private static function encryptWithPHP($K, $key_length, $IV, $P = null, $A = null, $tag_length = 128)
    {
        list($J0, $v, $a_len_padding, $H) = self::common($K, $key_length, $IV, $A);

        $C = self::getGCTR($K, $key_length, self::getInc(32, $J0), $P);
        $u = self::calcVector($C);
        $c_len_padding = self::addPadding($C);

        $S = self::getHash($H, $A.str_pad('', $v / 8, "\0").$C.str_pad('', $u / 8, "\0").$a_len_padding.$c_len_padding);
        $T = self::getMSB($tag_length, self::getGCTR($K, $key_length, $J0, $S));

        return [$C, $T];
    }

    /**
     * @param string      $K          Key encryption key
     * @param string      $key_length Key length
     * @param string      $IV         Initialization vector
     * @param null|string $P          Data to encrypt (null for authentication)
     * @param null|string $A          Additional Authentication Data
     * @param int         $tag_length Tag length
     *
     * @return array
     */
    private static function encryptWithCryptoExtension($K, $key_length, $IV, $P = null, $A = null, $tag_length = 128)
    {
        $cipher = \Crypto\Cipher::aes(\Crypto\Cipher::MODE_GCM, $key_length);
        $cipher->setAAD($A);
        $cipher->setTagLength($tag_length / 8);
        $C = $cipher->encrypt($P, $K, $IV);
        $T = $cipher->getTag();

        return [$C, $T];
    }

    /**
     * @param string      $K  Key encryption key
     * @param string      $IV Initialization vector
     * @param string|null $C  Data to encrypt (null for authentication)
     * @param string|null $A  Additional Authentication Data
     * @param string      $T  Tag
     *
     * @return string
     */
    public static function decrypt($K, $IV, $C, $A, $T)
    {
        Assertion::string($K, 'The key encryption key must be a binary string.');
        $key_length = mb_strlen($K, '8bit') * 8;
        Assertion::inArray($key_length, [128, 192, 256], 'Bad key encryption key length.');
        Assertion::string($IV, 'The Initialization Vector must be a binary string.');
        Assertion::nullOrString($C, 'The data to encrypt must be null or a binary string.');
        Assertion::nullOrString($A, 'The Additional Authentication Data must be null or a binary string.');

        $tag_length = self::getLength($T);
        Assertion::integer($tag_length, 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
        Assertion::inArray($tag_length, [128, 120, 112, 104, 96], 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');

        if (version_compare(PHP_VERSION, '7.1.0RC5') >= 0 && null !== $C) {
            return self::decryptWithPHP71($K, $key_length, $IV, $C, $A, $T);
        } elseif (class_exists('\Crypto\Cipher')) {
            return self::decryptWithCryptoExtension($K, $key_length, $IV, $C, $A, $T, $tag_length);
        }

        return self::decryptWithPHP($K, $key_length, $IV, $C, $A, $T, $tag_length);
    }

    /**
     * This method should be used if the tag is appended at the end of the ciphertext.
     * It is used by some AES GCM implementations such as the Java one.
     *
     * @param string      $K          Key encryption key
     * @param string      $IV         Initialization vector
     * @param string|null $Ciphertext Data to encrypt (null for authentication)
     * @param string|null $A          Additional Authentication Data
     * @param int         $tag_length Tag length
     *
     * @return string
     *
     * @see self::encryptAndAppendTag
     */
    public static function decryptWithAppendedTag($K, $IV, $Ciphertext = null, $A = null, $tag_length = 128)
    {
        $tag_length_in_bits = $tag_length / 8;
        $C = mb_substr($Ciphertext, 0, -$tag_length_in_bits, '8bit');
        $T = mb_substr($Ciphertext, -$tag_length_in_bits, null, '8bit');

        return self::decrypt($K, $IV, $C, $A, $T);
    }

    /**
     * @param string      $K          Key encryption key
     * @param string      $key_length Key length
     * @param string      $IV         Initialization vector
     * @param string|null $C          Data to encrypt (null for authentication)
     * @param string|null $A          Additional Authentication Data
     * @param string      $T          Tag
     *
     * @return string
     */
    private static function decryptWithPHP71($K, $key_length, $IV, $C, $A, $T)
    {
        $mode = 'aes-'.($key_length).'-gcm';
        $P = openssl_decrypt(null === $C ? '' : $C, $mode, $K, OPENSSL_RAW_DATA, $IV, $T, null === $A ? '' : $A);
        Assertion::true(false !== $P, 'Unable to decrypt or to verify the tag.');

        return $P;
    }

    /**
     * @param string      $K          Key encryption key
     * @param string      $key_length Key length
     * @param string      $IV         Initialization vector
     * @param string|null $C          Data to encrypt (null for authentication)
     * @param string|null $A          Additional Authentication Data
     * @param string      $T          Tag
     * @param int         $tag_length Tag length
     *
     * @return string
     */
    private static function decryptWithPHP($K, $key_length, $IV, $C, $A, $T, $tag_length = 128)
    {
        list($J0, $v, $a_len_padding, $H) = self::common($K, $key_length, $IV, $A);

        $P = self::getGCTR($K, $key_length, self::getInc(32, $J0), $C);

        $u = self::calcVector($C);
        $c_len_padding = self::addPadding($C);

        $S = self::getHash($H, $A.str_pad('', $v / 8, "\0").$C.str_pad('', $u / 8, "\0").$a_len_padding.$c_len_padding);
        $T1 = self::getMSB($tag_length, self::getGCTR($K, $key_length, $J0, $S));
        Assertion::eq($T1, $T, 'Unable to decrypt or to verify the tag.');

        return $P;
    }

    /**
     * @param string      $K          Key encryption key
     * @param string      $key_length Key length
     * @param string      $IV         Initialization vector
     * @param string|null $C          Data to encrypt (null for authentication)
     * @param string|null $A          Additional Authentication Data
     * @param string      $T          Tag
     * @param int         $tag_length Tag length
     *
     * @return string
     */
    private static function decryptWithCryptoExtension($K, $key_length, $IV, $C, $A, $T, $tag_length = 128)
    {
        $cipher = \Crypto\Cipher::aes(\Crypto\Cipher::MODE_GCM, $key_length);
        $cipher->setTag($T);
        $cipher->setAAD($A);
        $cipher->setTagLength($tag_length / 8);

        return $cipher->decrypt($C, $K, $IV);
    }

    /**
     * @param $K
     * @param $key_length
     * @param $IV
     * @param $A
     *
     * @return array
     */
    private static function common($K, $key_length, $IV, $A)
    {
        $H = openssl_encrypt(str_repeat("\0", 16), 'aes-'.($key_length).'-ecb', $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA); //---
        $iv_len = self::getLength($IV);

        if (96 === $iv_len) {
            $J0 = $IV.pack('H*', '00000001');
        } else {
            $s = self::calcVector($IV);
            Assertion::eq(($s + 64) % 8, 0, 'Unable to decrypt or to verify the tag.');

            $packed_iv_len = pack('N', $iv_len);
            $iv_len_padding = str_pad($packed_iv_len, 8, "\0", STR_PAD_LEFT);
            $hash_X = $IV.str_pad('', ($s + 64) / 8, "\0").$iv_len_padding;
            $J0 = self::getHash($H, $hash_X);
        }
        $v = self::calcVector($A);
        $a_len_padding = self::addPadding($A);

        return [$J0, $v, $a_len_padding, $H];
    }

    /**
     * @param string $value
     *
     * @return int
     */
    private static function calcVector($value)
    {
        return (128 * ceil(self::getLength($value) / 128)) - self::getLength($value);
    }

    /**
     * @param string $value
     *
     * @return string
     */
    private static function addPadding($value)
    {
        return str_pad(pack('N', self::getLength($value)), 8, "\0", STR_PAD_LEFT);
    }

    /**
     * @param string $x
     *
     * @return int
     */
    private static function getLength($x)
    {
        return mb_strlen($x, '8bit') * 8;
    }

    /**
     * @param int $num_bits
     * @param int $x
     *
     * @return string
     */
    private static function getMSB($num_bits, $x)
    {
        $num_bytes = $num_bits / 8;

        return mb_substr($x, 0, $num_bytes, '8bit');
    }

    /**
     * @param int $num_bits
     * @param int $x
     *
     * @return string
     */
    private static function getLSB($num_bits, $x)
    {
        $num_bytes = ($num_bits / 8);

        return mb_substr($x, -$num_bytes, null, '8bit');
    }

    /**
     * @param int $s_bits
     * @param int $x
     *
     * @return string
     */
    private static function getInc($s_bits, $x)
    {
        $lsb = self::getLSB($s_bits, $x);
        $X = self::toUInt32Bits($lsb) + 1;
        $res = self::getMSB(self::getLength($x) - $s_bits, $x).pack('N', $X);

        return $res;
    }

    /**
     * @param string $bin
     *
     * @return mixed
     */
    private static function toUInt32Bits($bin)
    {
        list(, $h, $l) = unpack('n*', $bin);

        return $l + ($h * 0x010000);
    }

    /**
     * @param $X
     * @param $Y
     *
     * @return string
     */
    private static function getProduct($X, $Y)
    {
        $R = pack('H*', 'E1').str_pad('', 15, "\0");
        $Z = str_pad('', 16, "\0");
        $V = $Y;

        $parts = str_split($X, 4);
        $x = sprintf('%032b%032b%032b%032b', self::toUInt32Bits($parts[0]), self::toUInt32Bits($parts[1]), self::toUInt32Bits($parts[2]), self::toUInt32Bits($parts[3]));
        $lsb_mask = "\1";
        for ($i = 0; $i < 128; $i++) {
            if ($x[$i]) {
                $Z = self::getBitXor($Z, $V);
            }
            $lsb_8 = mb_substr($V, -1, null, '8bit');
            if (ord($lsb_8 & $lsb_mask)) {
                $V = self::getBitXor(self::shiftStringToRight($V), $R);
            } else {
                $V = self::shiftStringToRight($V);
            }
        }

        return $Z;
    }

    /**
     * @param string $input
     *
     * @return string
     */
    private static function shiftStringToRight($input)
    {
        $width = 4;
        $parts = array_map('self::toUInt32Bits', str_split($input, $width));
        $runs = count($parts);

        for ($i = $runs - 1; $i >= 0; $i--) {
            if ($i) {
                $lsb1 = $parts[$i - 1] & 0x00000001;
                if ($lsb1) {
                    $parts[$i] = ($parts[$i] >> 1) | 0x80000000;
                    $parts[$i] = pack('N', $parts[$i]);
                    continue;
                }
            }
            $parts[$i] = ($parts[$i] >> 1) & 0x7FFFFFFF;
            $parts[$i] = pack('N', $parts[$i]);
        }
        $res = implode('', $parts);

        return $res;
    }

    /**
     * @param string $H
     * @param string $X
     *
     * @return mixed
     */
    private static function getHash($H, $X)
    {
        $Y = [];
        $Y[0] = str_pad('', 16, "\0");
        $num_blocks = (int) (mb_strlen($X, '8bit') / 16);
        for ($i = 1; $i <= $num_blocks; $i++) {
            $Y[$i] = self::getProduct(self::getBitXor($Y[$i - 1], mb_substr($X, ($i - 1) * 16, 16, '8bit')), $H);
        }

        return $Y[$num_blocks];
    }

    /**
     * @param string $K
     * @param int    $key_length
     * @param string $ICB
     * @param string $X
     *
     * @return string
     */
    private static function getGCTR($K, $key_length, $ICB, $X)
    {
        if (empty($X)) {
            return '';
        }

        $n = (int) ceil(self::getLength($X) / 128);
        $CB = [];
        $Y = [];
        $CB[1] = $ICB;
        for ($i = 2; $i <= $n; $i++) {
            $CB[$i] = self::getInc(32, $CB[$i - 1]);
        }
        $mode = 'aes-'.($key_length).'-ecb';
        for ($i = 1; $i < $n; $i++) {
            $C = openssl_encrypt($CB[$i], $mode, $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA);
            $Y[$i] = self::getBitXor(mb_substr($X, ($i - 1) * 16, 16, '8bit'), $C);
        }

        $Xn = mb_substr($X, ($n - 1) * 16, null, '8bit');
        $C = openssl_encrypt($CB[$n], $mode, $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA);
        $Y[$n] = self::getBitXor($Xn, self::getMSB(self::getLength($Xn), $C));

        return implode('', $Y);
    }

    /**
     * @param string $o1
     * @param string $o2
     *
     * @return string
     */
    private static function getBitXor($o1, $o2)
    {
        $xorWidth = PHP_INT_SIZE;
        $o1 = str_split($o1, $xorWidth);
        $o2 = str_split($o2, $xorWidth);
        $res = '';
        $runs = count($o1);
        for ($i = 0; $i < $runs; $i++) {
            $res .= $o1[$i] ^ $o2[$i];
        }

        return $res;
    }
}


<?php

/**
 * TwitterOAuth - https://github.com/ricardoper/TwitterOAuth
 * PHP library to communicate with Twitter OAuth API version 1.1
 *
 * @author Ricardo Pereira <github@ricardopereira.es>
 * @copyright 2014
 *
 * Connecting to Twitter API using SSL
 * https://dev.twitter.com/overview/api/ssl
 */

namespace TwitterOAuth\Common;

use TwitterOAuth\Exception\CurlException;

class Curl
{

    /**
     * cURL Options
     */
    protected $options;

    /**
     * Curl constructor
     */
    public function __construct()
    {
        // Set cURL default options //
        $this->options = array(
            CURLOPT_HEADER => true,
            CURLOPT_TIMEOUT => 60,
            CURLOPT_CONNECTTIMEOUT => 60,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_CAINFO => dirname(__DIR__) . '/Certificates/cacert.pem',

            // FOR DEBUG ONLY - PROXY SETTINGS //
            //CURLOPT_PROXY => '127.0.0.1',
            //CURLOPT_PROXYPORT => 8888,
        );
    }

    /**
     * Send a request
     *
     * @param string $url Request URL
     * @param array $params Configuration array
     * @return array  Headers & Body
     * @throws CurlException
     */
    public function send($url, array $params = array())
    {
        $out = array();

        $default = array(
            'get' => array(),
            'post' => array(),
            'headers' => array(),
            'cookies' => false,
            'gzip' => true,
        );

        $params = array_merge($default, $params);

        // Get Params //
        if (is_array($params['get']) && !empty($params['get'])) {
            $getParams = $this->getParams($params['get']);

            if (!empty($getParams)) {
                $url = $url . '?' . $getParams;
            }
        } elseif (is_string($params['get']) && !empty($params['get'])) {
            $url = $url . '?' . $params['get'];
        }

        // Curl Options //
        $options = array_replace(
            $this->getOptions(),
            array(
                CURLOPT_URL => $url,
                CURLOPT_USERAGENT => 'TwitterOAuth for v1.1 API (https://github.com/ricardoper/TwitterOAuth)',
            )
        );

        // Post Params //
        if (is_array($params['post']) && !empty($params['post'])) {
            $options[CURLOPT_POST] = count($params['post']);
            $options[CURLOPT_POSTFIELDS] = $this->getParams($params['post']);
        } elseif (is_string($params['post']) && !empty($params['post'])) {
            $options[CURLOPT_POST] = 1;
            $options[CURLOPT_POSTFIELDS] = $params['post'];
        }

        // Headers //
        if (is_array($params['headers']) && !empty($params['headers'])) {
            $options[CURLOPT_HTTPHEADER] = $params['headers'];
        }

        // Cookies Filename //
        if ($params['cookies'] !== false) {
            $options[CURLOPT_COOKIEJAR] = $params['cookies'];
            $options[CURLOPT_COOKIEFILE] = $params['cookies'];
        }

        // Gzip & Deflate //
        if ($params['gzip'] === true) {
            $options[CURLOPT_ENCODING] = 'gzip,deflate';
        }

        // Run Curl //
        $c = curl_init();

        curl_setopt_array($c, $options);

        $response = curl_exec($c);

        if (curl_errno($c) !== 0) {
            throw new CurlException(curl_error($c), curl_errno($c));
        }

        $cInfo = curl_getinfo($c);

        curl_close($c);

        // Process Response //
        $out['headers'] = $this->processHeaders(substr($response, 0, $cInfo['header_size']));

        $out['body'] = trim(substr($response, $cInfo['header_size']));

        unset($params, $options, $c, $cInfo, $response);

        return $out;
    }

    /**
     * Converting parameters array to a single string with encoded values
     *
     * @param array $params Input parameters
     * @return string  Single string with encoded values
     */
    public function getParams(array $params)
    {
        $r = '';

        ksort($params);

        foreach ($params as $key => $value) {
            $r .= '&' . $key . '=' . rawurlencode($value);
        }

        unset($params, $key, $value);

        return trim($r, '&');
    }

    /**
     * Get cURL options
     *
     * @return mixed
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Set cURL options
     *
     * @param array $options
     * @return $this
     */
    public function setOptions(array $options)
    {
        $this->options = $options;

        return $this;
    }

    /**
     * Returns response headers as array
     *
     * This can be useful to avoid extra requests for rate-limit info
     *    x-rate-limit-limit      (max request per period)
     *    x-rate-limit-remaining  (remaining this period)
     *    x-rate-limit-reset      (start of next period, UTC timestamp)
     *
     * @param array $headers Headers string
     * @return array  Headers array
     */
    protected function processHeaders($headers)
    {
        $out = array();

        $headers = explode("\r\n", trim($headers));

        foreach ($headers as $header) {
            if (strpos($header, ':') !== false) {
                $tmp = explode(':', $header);

                $out[reset($tmp)] = end($tmp);
            } else {
                if (!isset($out['http-code'])) {
                    $out['http-code'] = $header;
                }
            }
        }

        unset($headers, $header, $tmp);

        return $out;
    }
}


<?php

/**
 * TwitterOAuth - https://github.com/ricardoper/TwitterOAuth
 * PHP library to communicate with Twitter OAuth API version 1.1
 *
 * @author Ricardo Pereira <github@ricardopereira.es>
 * @copyright 2014
 */

namespace TwitterOAuth\Auth;

class SingleUserAuth extends AuthAbstract
{
    /**
     * Expects the follow parameters:
     *   - consumer_key         Twitter API key               * Required
     *   - consumer_secret      Twitter API secret            * Required
     *   - oauth_token          Twitter Access token
     *   - oauth_token_secret   Twitter Access token secret
     */
    protected $requiredCredentials = array(
        'consumer_key',
        'consumer_secret',
    );

    protected $urls = array(
        'domain' => 'https://api.twitter.com/',
        'upload' => 'https://upload.twitter.com/',
        'api' => '1.1/',
    );

    /**
     * Gets the Twitter Access token
     *
     * @return string
     */
    public function getAccessToken()
    {
        if (empty($this->credentials['oauth_token'])) {
            return null;
        }

        return $this->credentials['oauth_token'];
    }

    /**
     * Gets the Twitter Access token secret
     *
     * @return null|string
     */
    public function getAccessTokenSecret()
    {
        if (empty($this->credentials['oauth_token_secret'])) {
            return null;
        }

        return $this->credentials['oauth_token_secret'];
    }

    /**
     * Send a POST call to Twitter API via OAuth
     *
     * @param string $call Twitter resource string
     * @param array $postParams POST parameters to send
     * @param array $getParams GET parameters to send
     * @return mixed  Output with selected format
     * @throws \TwitterOAuth\Exception\TwitterException
     */
    public function post($call, array $postParams = null, array $getParams = null)
    {
        $this->resetCallState();

        $this->call = $call;

        $this->method = 'POST';

        if ($postParams !== null && is_array($postParams)) {
            $this->postParams = $postParams;
        }

        if ($getParams !== null && is_array($getParams)) {
            $this->getParams = $getParams;
        }

        $response = $this->getResponse();

        $response['body'] = $this->findExceptions($response);

        $this->headers = $response['headers'];

        unset($call, $postParams, $getParams);

        return $this->serializer->format($response['body']);
    }

    /**
     * Send a POST call with media upload to Twitter API via OAuth
     *
     * @param string $call Twitter resource string
     * @param string $filename File location to upload
     * @return mixed  Output with selected format
     * @throws \TwitterOAuth\Exception\CurlException
     * @throws \TwitterOAuth\Exception\TwitterException
     */
    public function postMedia($call, $filename)
    {
        $this->resetCallState();

        $this->call = $call;

        $this->method = 'POST';

        $this->withMedia = true;

        $mimeBoundary = sha1($call . microtime());

        $params = array(
            'post' => $this->buildMultipart($mimeBoundary, $filename),
            'headers' => $this->buildUploadMediaHeader($mimeBoundary),
        );

        $response = $this->curl->send($this->getUrl(), $params);

        $obj = json_decode($response['body']);

        if (!$obj || !isset($obj->token_type) || $obj->token_type != 'bearer') {
            $this->findExceptions($response);
        }

        $this->headers = $response['headers'];

        $this->withMedia = null;

        unset($call, $filename, $mimeBoundary, $params, $obj);

        return $this->serializer->format($response['body']);
    }

    /**
     * Getting OAuth parameters to be used in request headers
     *
     * @return array  OAuth parameters
     */
    protected function getOauthParameters()
    {
        $time = time();

        return array(
            'oauth_consumer_key' => $this->getConsumerKey(),
            'oauth_nonce' => trim(base64_encode($time), '='),
            'oauth_signature_method' => 'HMAC-SHA1',
            'oauth_timestamp' => $time,
            'oauth_token' => $this->getAccessToken(),
            'oauth_version' => '1.0'
        );
    }

    /**
     * Converting all parameters agetrrays to a single string with encoded values
     *
     * @return string  Single string with encoded values
     */
    protected function getRequestString()
    {
        $params = array_merge($this->getParams, $this->postParams, $this->getOauthParameters());

        $params = $this->curl->getParams($params);

        return rawurlencode($params);
    }

    /**
     * Getting OAuth signature base string
     *
     * @return string  OAuth signature base string
     */
    protected function getSignatureBaseString()
    {
        $method = strtoupper($this->method);

        $url = rawurlencode($this->getUrl());

        return $method . '&' . $url . '&' . $this->getRequestString();
    }

    /**
     * Getting a signing key
     *
     * @return string  Signing key
     */
    protected function getSigningKey()
    {
        return $this->getConsumerSecret() . '&' . $this->getAccessTokenSecret();
    }

    /**
     * Calculating the signature
     *
     * @return string  Signature
     */
    protected function calculateSignature()
    {
        return base64_encode(hash_hmac('sha1', $this->getSignatureBaseString(), $this->getSigningKey(), true));
    }

    /**
     * Converting OAuth parameters array to a single string with encoded values
     *
     * @return string  Single string with encoded values
     */
    protected function getOauthString()
    {
        $oauth = array_merge($this->getOauthParameters(), array('oauth_signature' => $this->calculateSignature()));

        ksort($oauth);

        $values = array();

        foreach ($oauth as $key => $value) {
            $values[] = $key . '="' . rawurlencode($value) . '"';
        }

        $oauth = implode(', ', $values);

        unset($values, $key, $value);

        return $oauth;
    }

    /**
     * Building request HTTP headers
     *
     * @return array  HTTP headers
     */
    protected function buildRequestHeader()
    {
        return array(
            'Authorization: OAuth ' . $this->getOauthString(),
            'Expect:'
        );
    }

    /**
     * Building upload media headers
     *
     * @param string $mimeBoundary MIME boundary ID
     * @return array  HTTP headers
     */
    protected function buildUploadMediaHeader($mimeBoundary)
    {
        return array(
            'Authorization: OAuth ' . $this->getOauthString(),
            'Content-Type: multipart/form-data; boundary=' . $mimeBoundary,
            'Expect:'
        );
    }
}


<?php

/**
 * TwitterOAuth - https://github.com/ricardoper/TwitterOAuth
 * PHP library to communicate with Twitter OAuth API version 1.1
 *
 * @author Ricardo Pereira <github@ricardopereira.es>
 * @copyright 2014
 */

namespace TwitterOAuth\Auth;

use TwitterOAuth\Common\Curl;
use TwitterOAuth\Serializer\SerializerInterface;
use TwitterOAuth\Exception\TwitterException;
use TwitterOAuth\Exception\FileNotFoundException;
use TwitterOAuth\Exception\FileNotReadableException;
use TwitterOAuth\Exception\UnsupportedMimeException;
use TwitterOAuth\Exception\MissingCredentialsException;

abstract class AuthAbstract
{
    const EOL = "\r\n";

    protected $credentials = array();
    protected $serializer = null;
    protected $curl = null;

    protected $call = null;
    protected $method = null;
    protected $withMedia = null;
    protected $getParams = array();
    protected $postParams = array();

    protected $headers = null;

    /**
     * Authentication Base
     *
     * @param array $credentials Credentials Array
     * @param SerializerInterface $serializer Output Serializer
     * @throws MissingCredentialsException
     */
    public function __construct(array $credentials, SerializerInterface $serializer)
    {
        $this->validateCredentials($credentials);

        $this->credentials = $credentials;

        $this->serializer = $serializer;

        $this->curl = new Curl();

        unset($credentials, $serializer);
    }

    /**
     * Gets the Twitter API key
     *
     * @return null|string
     */
    public function getConsumerKey()
    {
        if (empty($this->credentials['consumer_key'])) {
            return null;
        }

        return $this->credentials['consumer_key'];
    }

    /**
     * Gets the Twitter API secret
     *
     * @return null|string
     */
    public function getConsumerSecret()
    {
        if (empty($this->credentials['consumer_secret'])) {
            return null;
        }

        return $this->credentials['consumer_secret'];
    }

    /**
     * Gets Serializer
     *
     * @return null|SerializerInterface
     */
    public function getSerializer()
    {
        if (empty($this->serializer)) {
            return null;
        }

        return $this->serializer;
    }

    /**
     * Get cURL Instance
     */
    public function getCurl()
    {
        return $this->curl;
    }

    /**
     * Get response headers
     *
     * @param null $key
     * @return array|string|false
     */
    public function getHeaders($key = null)
    {
        if ($key === null) {
            return $this->headers;
        }

        if (isset($this->headers[$key])) {
            return $this->headers[$key];
        }

        return false;
    }

    /**
     * Send a GET call to Twitter API via OAuth
     *
     * @param string $call Twitter resource string
     * @param array $getParams GET parameters to send
     * @return mixed  Output with selected format
     * @throws TwitterException
     */
    public function get($call, array $getParams = null)
    {
        $this->resetCallState();

        $this->call = $call;

        $this->method = 'GET';

        if ($getParams !== null && is_array($getParams)) {
            $this->getParams = $getParams;
        }

        $response = $this->getResponse();

        $response['body'] = $this->findExceptions($response);

        $this->headers = $response['headers'];

        unset($call, $getParams);

        return $this->serializer->format($response['body']);
    }

    /**
     * Validate Credentials Array
     *
     * @param $credentials
     * @throws MissingCredentialsException
     */
    protected function validateCredentials($credentials)
    {
        $credentials = array_filter($credentials);

        $keys = array_keys($credentials);

        $diff = array_diff($this->requiredCredentials, $keys);

        if (!empty($diff)) {
            throw new MissingCredentialsException('Missing Credentials: ' . implode($diff, ', '));
        }

        unset($credentials, $keys, $diff);
    }

    /**
     * Getting full URL from a Twitter resource
     *
     * @return string  Full URL
     */
    protected function getUrl()
    {
        $domain = $this->urls['domain'];

        $apiVersion = $this->urls['api'];

        $jsonExt = '.json';

        if (isset($this->withMedia) && $this->withMedia === true) {
            $domain = $this->urls['upload'];
        }

        if ($this->call === 'oauth/request_token' || $this->call === 'oauth/access_token') {
            $apiVersion = '';
            $jsonExt = '';
        }

        return $domain . $apiVersion . $this->call . $jsonExt;
    }

    /**
     * Returns raw response body
     *
     * @return array
     * @throws \TwitterOAuth\Exception\CurlException
     */
    protected function getResponse()
    {
        $url = $this->getUrl();

        $params = array(
            'get' => $this->getParams,
            'post' => $this->postParams,
            'headers' => $this->buildRequestHeader(),
        );

        return $this->curl->send($url, $params);
    }

    /**
     * Processing Twitter Exceptions in case of error
     *
     * @param array $response Raw response
     * @return string
     * @throws TwitterException
     */
    protected function findExceptions($response)
    {
        $response = $response['body'];

        $data = json_decode($response, true);

        if (isset($response[0]) && $response[0] !== '{' && $response[0] !== '[' && !$data) {
            if (strpos($response, 'oauth_token=') !== false) {
                parse_str($response, $data);
            }

            if (empty($data) || !is_array($data)) {
                throw new TwitterException($response, 0);
            }

            return json_encode($data);
        }

        if (!empty($data['errors']) || !empty($data['error'])) {
            if (!empty($data['errors'])) {
                $data = current($data['errors']);
            }

            if (empty($data['message']) && !empty($data['error'])) {
                $data['message'] = $data['error'];
            }

            if (!isset($data['code']) || empty($data['code'])) {
                $data['code'] = 0;
            }

            throw new TwitterException($data['message'], $data['code']);
        }

        unset($data);

        return $response;
    }

    /**
     * Build a multipart message
     *
     * @param string $mimeBoundary MIME boundary ID
     * @param string $filename File location
     * @return string  Multipart message
     */
    protected function buildMultipart($mimeBoundary, $filename)
    {
        $binary = $this->getBinaryFile($filename);

        $details = pathinfo($filename);

        $type = $this->supportedMimes($details['extension']);

        $data = '--' . $mimeBoundary . static::EOL;
        $data .= 'Content-Disposition: form-data; name="media"; filename="' . $details['basename'] . '"' . static::EOL;
        $data .= 'Content-Type: application/octet-stream' . static::EOL . static::EOL;
        $data .= $binary . static::EOL;
        $data .= '--' . $mimeBoundary . '--' . static::EOL . static::EOL;

        unset($mimeBoundary, $filename, $binary, $details, $type);

        return $data;
    }

    /**
     * Twitter supported MIME types for media upload
     *
     * @param string $mime File extension
     * @return mixed  MIME type
     * @throws UnsupportedMimeException
     */
    protected function supportedMimes($mime)
    {
        $mimes = array(
            'png' => 'image/png',
            'jpe' => 'image/jpeg',
            'jpeg' => 'image/jpeg',
            'jpg' => 'image/jpeg',
            'gif' => 'image/gif',
        );

        if (isset($mimes[$mime])) {
            return $mimes[$mime];
        }

        throw new UnsupportedMimeException;
    }

    /**
     * Get binary data of a file
     *
     * @param string $filename File location
     * @return string
     * @throws FileNotFoundException
     * @throws FileNotReadableException
     */
    protected function getBinaryFile($filename)
    {
        if (!file_exists($filename)) {
            throw new FileNotFoundException;
        }

        if (!is_readable($filename)) {
            throw new FileNotReadableException;
        }

        ob_start();

        readfile($filename);

        $binary = ob_get_contents();

        ob_end_clean();

        unset($filename);

        return $binary;
    }

    /**
     * Reset Call State
     */
    protected function resetCallState()
    {
        $this->call = null;
        $this->method = null;
        $this->withMedia = null;
        $this->getParams = array();
        $this->postParams = array();
        $this->headers = null;
    }
}


<?php

/**
 * TwitterOAuth - https://github.com/ricardoper/TwitterOAuth
 * PHP library to communicate with Twitter OAuth API version 1.1
 *
 * @author Ricardo Pereira <github@ricardopereira.es>
 * @copyright 2014
 */

namespace TwitterOAuth\Auth;

class ApplicationOnlyAuth extends AuthAbstract
{
    /**
     * Expects the follow parameters:
     *   - consumer_key      Twitter API key      * Required
     *   - consumer_secret   Twitter API secret   * Required
     */
    protected $requiredCredentials = array(
        'consumer_key',
        'consumer_secret',
    );

    protected $urls = array(
        'domain' => 'https://api.twitter.com/',
        'api' => '1.1/',
        'getToken' => 'oauth2/token',
        'invToken' => 'oauth2/invalidate_token',
    );

    protected $bearerToken = null;

    /**
     *  Get a bearer token from consumer keys
     *
     * @return null|string  Bearer token
     * @throws \TwitterOAuth\Exception\CurlException
     * @throws \TwitterOAuth\Exception\TwitterException
     */
    public function getBearerToken()
    {
        $url = $this->getBearerTokenUrl();

        $params = array(
            'post' => array('grant_type' => 'client_credentials'),
            'headers' => $this->buildBearerTokenHeader(),
        );

        $response = $this->curl->send($url, $params);

        $obj = json_decode($response['body']);

        if (!$obj || !isset($obj->token_type) || $obj->token_type != 'bearer') {
            $this->findExceptions($response);
        }

        $this->bearerToken = rawurldecode($obj->access_token);

        unset($url, $params, $response, $obj);

        return $this->bearerToken;
    }

    /**
     *  Set a bearer token
     *
     * @param string $bearerToken Bearer Token
     * @return bool
     */
    public function setBearerToken($bearerToken)
    {
        $this->bearerToken = $bearerToken;

        unset($bearerToken);

        return true;
    }

    /**
     *  Invalidate a bearer token
     *
     * @return bool
     * @throws \TwitterOAuth\Exception\CurlException
     * @throws \TwitterOAuth\Exception\TwitterException
     */
    public function invalidateBearerToken()
    {
        $url = $this->getInvalidateBearerTokenUrl();

        $bearerToken = $this->bearerToken;

        if ($bearerToken === null) {
            $bearerToken = $this->getBearerToken();
        }

        $params = array(
            'post' => array('access_token' => $bearerToken),
            'headers' => $this->buildBearerTokenHeader(),
        );

        $response = $this->curl->send($url, $params);

        $obj = json_decode($response['body']);

        if (!$obj || !isset($obj->access_token) || $obj->access_token != $bearerToken) {
            $this->findExceptions($response);
        }

        unset($url, $bearerToken, $params, $response, $obj);

        return true;
    }

    /**
     * Generate bearer token credentials
     *
     * @return string  Bearer token credentials
     */
    protected function getBearerTokenCredentials()
    {
        $signingKey = rawurlencode($this->getConsumerKey()) . ':' . rawurlencode($this->getConsumerSecret());

        return base64_encode($signingKey);
    }

    /**
     * Getting bearer token URL
     *
     * @return string  Bearer token URL
     */
    protected function getBearerTokenUrl()
    {
        return $this->urls['domain'] . $this->urls['getToken'];
    }

    /**
     * Getting invalidate bearer token URL
     *
     * @return string  Invalidate bearer token URL
     */
    protected function getInvalidateBearerTokenUrl()
    {
        return $this->urls['domain'] . $this->urls['invToken'];
    }

    /**
     * Building request bearer token HTTP headers
     *
     * @return array  HTTP headers
     */
    protected function buildBearerTokenHeader()
    {
        return array(
            'Authorization: Basic ' . $this->getBearerTokenCredentials(),
            'Content-Type: application/x-www-form-urlencoded;charset=UTF-8',
            'Expect:'
        );
    }

    /**
     * Building request HTTP headers
     *
     * @return array  HTTP headers
     */
    protected function buildRequestHeader()
    {
        $bearerToken = $this->bearerToken;

        if ($this->bearerToken === null) {
            $bearerToken = $this->getBearerToken();
        }

        return array(
            'Authorization: Bearer ' . rawurlencode($bearerToken),
            'Expect:'
        );
    }
}


<?php

namespace Kue;

use DateTime;
use DateInterval;

use Kue\Scheduler\Expression;
use Kue\Scheduler\CronExpression;
use Kue\Scheduler\SimpleExpression;

/**
 * Simple, tickless scheduler which puts jobs into the queue when
 * expressions are due.
 *
 * @author Christoph Hochstrasser <christoph.hochstrasser@gmail.com>
 */
class Scheduler
{
    protected $entries = array();
    protected $queue;

    function __construct(Queue $queue)
    {
        $this->queue = $queue;
    }

    /**
     * Adds the job to the scheduler.
     *
     * @param int $interval Interval in seconds
     * @param Job $job
     *
     * @return Scheduler
     */
    function every($interval, Job $job)
    {
        $expression = new SimpleExpression($interval);
        $this->add($expression, $job);

        return $this;
    }

    /**
     * Schedules a job with a CRON expression.
     *
     * @param string $expression CRON expression
     * @param Job $job
     *
     * @return Scheduler
     */
    function cron($expression, Job $job)
    {
        $expression = new CronExpression($expression);
        $this->add($expression, $job);

        return $this;
    }

    function add(Expression $expression, Job $job)
    {
        $this->entries[] = array($expression, $job);
        return $this;
    }

    /**
     * Schedules jobs, sleeps until a job has to be scheduled. Returns
     * when jobs were scheduled.
     *
     * @return int Number of scheduled jobs.
     */
    function run()
    {
        $now = new DateTime('now');

        $sleep = min(array_map(
            function($entry) use ($now) {
                list($expression, $job) = $entry;

                return $expression->getNextRunDate($now)->getTimestamp();
            },
            $this->entries
        ));

        time_sleep_until($sleep);

        $scheduled = 0;

        foreach ($this->entries as $entry) {
            list($expression, $job) = $entry;

            if ($expression->isDue($now)) {
                $this->queue->push($job);
                $scheduled += 1;
            }
        }

        $this->queue->flush();

        return $scheduled;
    }
}


<?php

namespace Kue;

/**
 * Simple implementation of a queue, that stores jobs into a Redis list.
 *
 * Uses http://github.com/nicolasff/phpredis
 *
 * @author Christoph Hochstrasser <christoph.hochstrasser@gmail.com>
 */
class RedisQueue implements Queue
{
    const DEFAULT_KEY = "spark:queue";

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

    /** @var \Redis */
    protected $redis;

    function __construct(\Redis $redis, $key = self::DEFAULT_KEY)
    {
        $this->key = $key;
        $this->redis = $redis;
    }

    /**
     * Does a Redis BLPOP on the queue, and blocks until a job is available.
     *
     * @return Job
     */
    function pop()
    {
        $response = $this->redis->blPop($this->key, 10);

        if ($response) {
            list($list, $serializedJob) = $response;

            $job = unserialize($serializedJob);
            return $job;
        }
    }

    function push(Job $job)
    {
        $this->redis->rPush($this->key, serialize($job));
    }

    function flush()
    {
        # We send jobs directly in `push`, so we don't need to flush
    }

    function process(Worker $worker)
    {
    }
}