<?php
/**
 * User: Jim Heys
 * Date: 6/17/13
 * Time: 5:06 PM
 * @package Sherlock\requests
 */
namespace Sherlock\requests;

use Sherlock\common\exceptions;
use Sherlock\components;
use Sherlock\components\queries;
use Symfony\Component\EventDispatcher\EventDispatcher;
use sherlock\components\FacetInterface;
use Sherlock\responses\DocumentResponse;

/**
 * SearchRequest facilitates document retrieval by id
 */
class GetDocumentRequest extends Request
{
    /**
     * @var \Symfony\Component\EventDispatcher\EventDispatcher
     */
    protected $dispatcher;

    protected $params;

    /**
     * @param  \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
     *
     * @throws \Sherlock\common\exceptions\BadMethodCallException
     */
    public function __construct($dispatcher)
    {
        if (!isset($dispatcher)) {
            throw new \Sherlock\common\exceptions\BadMethodCallException("Dispatcher argument required for DocumentRequest");
        }

        $this->dispatcher       = $dispatcher;

        parent::__construct($dispatcher);
    }

    /**
     * Sets the id of the document
     * @param $id
     * @return DocumentRequest
     */
    public function id($id)
    {
        $this->params['id'] = $id;

        return $this;
    }

    /**
     * Sets the index to operate on
     *
     * @param  string        $index     indices to query
     * @param  string        $index,... indices to query
     *
     * @return SearchRequest
     */
    public function index($index)
    {
        $this->params['index'] = array();
        $args                  = func_get_args();
        foreach ($args as $arg) {
            $this->params['index'][] = $arg;
        }

        return $this;
    }

    /**
     * Sets the type to operate on
     *
     * @param  string        $type     types to query
     * @param  string        $type,... types to query
     *
     * @return SearchRequest
     */
    public function type($type)
    {
        $this->params['type'] = array();
        $args                 = func_get_args();
        foreach ($args as $arg) {
            $this->params['type'][] = $arg;
        }

        return $this;
    }

    /**
     * Execute the search request on the ES cluster
     *
     * @throws \Sherlock\common\exceptions\RuntimeException
     * @return \Sherlock\responses\QueryResponse
     */
    public function execute()
    {

        $id = $this->params['id'];

        if (isset($this->params['index'])) {
            $index = implode(',', $this->params['index']);
        } else {
            $index = '';
        }

        if (isset($this->params['type'])) {
            $type = implode(',', $this->params['type']);
        } else {
            $type = '';
        }

        if (isset($queryParams)) {
            $queryParams = '?' . implode("&", $queryParams);
        } else {
            $queryParams = '';
        }

        $command = new \Sherlock\requests\Command();
        $command->index($index)
            ->type($type)
            ->id($id . $queryParams)
            ->action('get');

        $this->batch->clearCommands();
        $this->batch->addCommand($command);

        $ret = parent::execute();

        return $ret[0];
    }

    /**
     * @param $response
     * @return \Sherlock\responses\DocumentResponse|\Sherlock\responses\Response
     */
    protected function getReturnResponse($response)
    {
        return new DocumentResponse($response);
    }
}


<?php
/**
 * User: Zachary Tong
 * Date: 2013-02-19
 * Time: 08:26 PM
 * Auto-generated by "generate.filters.php"
 * @package Sherlock\components\filters
 */
namespace Sherlock\components\filters;

use Sherlock\components;

/**
 * @method \Sherlock\components\filters\Ids type() type(\string $value)
 */
class Ids extends \Sherlock\components\BaseComponent implements \Sherlock\components\FilterInterface
{
    public function __construct($hashMap = null)
    {

        parent::__construct($hashMap);
    }

    /**
     * @param  \string | array $values
     *
     * @return Ids
     */
    public function values($ids)
    {

        $args = func_get_args();
        //single param, array of ids
        if (count($args) == 1 && is_array($args[0])) {
            $args = $args[0];
        }

        $this->params['values'] = $args;

        return $this;
    }

    public function toArray()
    {
        $ret = array(
            'ids' =>
            array(
                'type'   => $this->params["type"],
                'values' => $this->params["values"],
            ),
        );

        return $ret;
    }

}


<?php
/**
 * User: Zachary Tong
 * Date: 2/4/13
 * Time: 10:28 AM
 * @package Sherlock
 * @author  Zachary Tong
 * @version 0.1.2
 */

namespace Sherlock;

use Sherlock\common\Cluster;
use Sherlock\common\events\Events;
use Sherlock\requests;
use Sherlock\common\exceptions;
use Sherlock\wrappers;
use Symfony\Component\EventDispatcher\EventDispatcher;

/**
 * Class Sherlock
 * @package Sherlock
 */
class Sherlock
{
    /**
     * @var array Sherlock settings, can be replaced with user-settings through constructor
     */
    protected $settings;
    /**
     * @var array Templates - not used at the moment
     */
    protected $templates = array();

    /**
     * Sherlock constructor, accepts optional user settings
     *
     * @param array $userSettings Optional user settings to over-ride the default
     */
    public function __construct($userSettings = array())
    {
        $this->initializeSherlock($userSettings);
        $this->autodetectClusterState();
    }

    /********************************************************************************
     * PSR-0 Autoloader
     *
     * Do not use if you are using Composer to autoload dependencies.
     *******************************************************************************/

    /**
     * Sherlock PSR-0 autoloader
     */
    public static function autoload($className)
    {
        $thisClass = str_replace(__NAMESPACE__ . '\\', '', __CLASS__);

        $baseDir = __DIR__;

        if (substr($baseDir, -strlen($thisClass)) === $thisClass) {
            $baseDir = substr($baseDir, 0, -strlen($thisClass));
        }

        $className = ltrim($className, '\\');
        $fileName  = $baseDir;
        if ($lastNsPos = strripos($className, '\\')) {
            $namespace = substr($className, 0, $lastNsPos);
            $className = substr($className, $lastNsPos + 1);
            $fileName .= str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
        }
        $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';

        if (file_exists($fileName)) {
            require $fileName;
        }
    }

    /**
     * Register Sherlock's PSR-0 autoloader
     */
    public static function registerAutoloader()
    {
        spl_autoload_register(__NAMESPACE__ . "\\Sherlock::autoload");
    }

    /**
     * Query builder, used to return a QueryWrapper through which a Query component can be selected
     * @return wrappers\QueryWrapper
     */
    public static function queryBuilder()
    {
        return new \Sherlock\wrappers\QueryWrapper();
    }

    /**
     * Filter builder, used to return a FilterWrapper through which a Filter component can be selected
     * @return wrappers\FilterWrapper
     */
    public static function filterBuilder()
    {
        return new wrappers\FilterWrapper();
    }

    /**
     * Facet builder, used to return a FilterWrapper through which a Filter component can be selected
     * @return wrappers\FacetWrapper
     */
    public static function facetBuilder()
    {
        return new wrappers\FacetWrapper();
    }

    /**
     * Highlight builder, used to return a HighlightWrapper through which a Highlight component can be selected
     * @return wrappers\HighlightWrapper
     */
    public static function highlightBuilder()
    {
        return new wrappers\HighlightWrapper();
    }

    /**
     * Index builder, used to return a IndexWrapper through which an Index component can be selected
     * @return wrappers\IndexSettingsWrapper
     */
    public static function indexSettingsBuilder()
    {
        return new wrappers\IndexSettingsWrapper();
    }

    /**
     * Mapping builder, used to return a MappingWrapper through which a Mapping component can be selected
     *
     * @param  null|string                     $type
     *
     * @return wrappers\MappingPropertyWrapper
     */
    public static function mappingBuilder($type = null)
    {
        return new wrappers\MappingPropertyWrapper($type);
    }

    /**
     * @return wrappers\SortWrapper
     */
    public static function sortBuilder()
    {
        return new wrappers\SortWrapper();
    }

    /**
     * Used to obtain a SearchRequest object, allows querying the cluster with searches
     * @return requests\SearchRequest
     */
    public function search()
    {
        return new requests\SearchRequest($this->settings['event.dispatcher']);
    }

    /**
     * RawRequests allow the user to issue arbitrary commands to the ES cluster
     * Effectively one step above a raw CURL command
     *
     * @return requests\RawRequest
     */
    public function raw()
    {
        return new requests\RawRequest($this->settings['event.dispatcher']);
    }

    /**
     * Used to return an IndexDocumentRequest object, allows adding a doc to the index
     * @return requests\IndexDocumentRequest
     */
    public function document()
    {
        return new requests\IndexDocumentRequest($this->settings['event.dispatcher']);
    }

    /**
     * Used to return a DeleteDocumentRequest object, allows deleting a doc from the index
     * @return requests\DeleteDocumentRequest
     */
    public function deleteDocument()
    {
        return new requests\DeleteDocumentRequest($this->settings['event.dispatcher']);
    }

    /**
     * Used to return a GetDocumentRequest object, allows retrieving a doc by id
     * @return requests\GetDocumentRequest
     */
    public function getDocument()
    {
        return new requests\GetDocumentRequest($this->settings['event.dispatcher']);
    }

    /**
     * @param  string                $index     Index to operate on
     * @param  string                $index,... Index to operate on
     *
     * @return requests\IndexRequest
     */
    public function index($index = null)
    {
        $args  = func_get_args();
        $index = array();
        foreach ($args as $arg) {
            $index[] = $arg;
        }

        return new requests\IndexRequest($this->settings['event.dispatcher'], $index);
    }

    /**
     * Autodetects various properties of the cluster and indices
     */
    public function autodetectClusterState()
    {
        //If we have nodes and are supposed to detect cluster settings/configuration
        if ($this->settings['cluster.autodetect'] == true) {
            $this->settings['cluster']->autodetect();
        }
    }

    /**
     * Add a new node to the ES cluster
     *
     * @param  string                                     $host server host address (either IP or domain)
     * @param  int                                        $port ElasticSearch port (defaults to 9200)
     *
     * @return \Sherlock\Sherlock
     * @throws common\exceptions\BadMethodCallException
     * @throws common\exceptions\InvalidArgumentException
     */
    public function addNode($host, $port = 9200)
    {
        $this->settings['cluster']->addNode($host, $port, $this->settings['cluster.autodetect']);

        return $this;
    }

    /**
     * @return array
     */
    public function getSherlockSettings()
    {
        return $this->settings;
    }

    /**
     * @param $userSettings
     */
    private function initializeSherlock($userSettings)
    {
        $this->mergeUserSettingsWithDefault($userSettings);
        $this->initializeCluster();
        $this->initializeEventDispatcher();
    }

    /**
     * @param array $userSettings
     */
    private function mergeUserSettingsWithDefault($userSettings)
    {
        $this->settings = array_merge($this->getDefaultSettings(), $userSettings);
    }

    private function initializeCluster()
    {
        $this->settings['cluster'] = new Cluster($this->settings['event.dispatcher']);
    }

    private function initializeEventDispatcher()
    {
        $eventCallback = array($this->settings['cluster'], 'onRequestExecute');
        $this->settings['event.dispatcher']->addListener(Events::REQUEST_PREEXECUTE, $eventCallback);
    }

    /**
     * @return array Default settings
     */
    private function getDefaultSettings()
    {
        return array(
            // Application
            'base'               => __DIR__ . '/',
            'mode'               => 'development',
            'event.dispatcher'   => new EventDispatcher(),
            'cluster'            => null,
            'cluster.autodetect' => false,
        );
    }

}


<?php
/**
 * User: Zachary Tong
 * Date: 2013-02-19
 * Time: 08:26 PM
 * Auto-generated by "generate.filters.php"
 * @package Sherlock\components\filters
 */
namespace Sherlock\components\filters;

use Sherlock\components;
use Sherlock\components\FilterInterface;

/**
 * Bool Filter
 *
 * @method \Sherlock\components\filters\Bool _cache() _cache(\bool $value) Default: false
 */
class Bool extends \Sherlock\components\BaseComponent implements FilterInterface
{
    /**
     * @param null $hashMap Optional assoc array of values to prefill Filter
     */
    public function __construct($hashMap = null)
    {
        $this->params['_cache']   = false;
        $this->params['must']     = array();
        $this->params['must_not'] = array();
        $this->params['should']   = array();

        parent::__construct($hashMap);
    }

    /**
     * Must clause of Bool
     *
     * @param FilterInterface|array $value Single or array of FilterInterface objects
     *
     * @return $this
     */
    public function must($value)
    {
        $args = $this->normalizeFuncArgs(func_get_args());

        foreach ($args as $arg) {
            if ($arg instanceof FilterInterface) {
                $this->params['must'][] = $arg->toArray();
            }
        }

        return $this;
    }

    /**
     * Must_not clause of Bool
     *
     * @param FilterInterface|array $value Single or array of FilterInterface objects
     *
     * @return $this
     */
    public function must_not($value)
    {
        $args = $this->normalizeFuncArgs(func_get_args());

        foreach ($args as $arg) {
            if ($arg instanceof FilterInterface) {
                $this->params['must_not'][] = $arg->toArray();
            }
        }

        return $this;
    }

    /**
     * Should clause of Bool
     *
     * @param FilterInterface|array $value Single or array of FilterInterface objects
     *
     * @return $this
     */
    public function should($value)
    {
        $args = $this->normalizeFuncArgs(func_get_args());

        foreach ($args as $arg) {
            if ($arg instanceof FilterInterface) {
                $this->params['should'][] = $arg->toArray();
            }
        }

        return $this;
    }

    /**
     * @return array
     */
    public function toArray()
    {
        $ret = array(
            'bool' =>
            array(
                'must'     => $this->params["must"],
                'must_not' => $this->params["must_not"],
                'should'   => $this->params["should"],
                '_cache'   => $this->params["_cache"],
            ),
        );

        return $ret;
    }

}


<?php
/**
 * User: Zachary Tong
 * Date: 2013-02-19
 * Time: 08:26 PM
 * Auto-generated by "generate.filters.php"
 * @package Sherlock\components\filters
 */
namespace Sherlock\components\filters;

use Sherlock\components;

/**
 * @method \Sherlock\components\filters\Not _cache() _cache(\bool $value) Default: false
 */
class Not extends \Sherlock\components\BaseComponent implements \Sherlock\components\FilterInterface
{
    public function __construct($hashMap = null)
    {
        $this->params['_cache'] = false;

        parent::__construct($hashMap);
    }

    /**
     * @param  \Sherlock\components\QueryInterface | \Sherlock\components\FilterInterface $value
     *
     * @return Not
     */
    public function not($value)
    {
        if ($value instanceof \Sherlock\components\QueryInterface) {
            $this->params['not'] = $value->toArray();
        } elseif ($value instanceof \Sherlock\components\FilterInterface) {
            $this->params['not'] = array("filter" => $value->toArray());
        }

        return $this;
    }

    public function toArray()
    {
        $ret = array(
            'not'    => $this->params["not"],
            '_cache' => $this->params["_cache"],
        );

        return $ret;
    }

}


<?php

namespace Sherlock\requests;

use Sherlock\common\exceptions;
use Sherlock\responses\DeleteResponse;

/**
 * This class facilitates deleting single documents into an ElasticSearch index
 *
 */
class DeleteDocumentRequest extends Request
{
    protected $dispatcher;

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

    /**
     * @param  \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
     *
     * @throws \Sherlock\common\exceptions\BadMethodCallException
     * @internal param $node
     */
    public function __construct($dispatcher)
    {
        if (!isset($dispatcher)) {
            throw new \Sherlock\common\exceptions\BadMethodCallException("Dispatcher argument required for IndexRequest");
        }

        $this->dispatcher = $dispatcher;

        parent::__construct($dispatcher);
    }

    /**
     * @param $name
     * @param $args
     *
     * @return DeleteDocumentRequest
     */
    public function __call($name, $args)
    {
        $this->params[$name] = $args[0];

        return $this;
    }

    /**
     * Set the index to delete documents from
     *
     * @param  string               $index     indices to query
     * @param  string               $index,... indices to query
     *
     * @return DeleteDocumentRequest
     */
    public function index($index)
    {
        $this->params['index'] = array();
        $args                  = func_get_args();
        foreach ($args as $arg) {
            $this->params['index'][] = $arg;
        }

        return $this;
    }

    /**
     * Set the type to delete documents from
     *
     * @param  string               $type
     * @param  string               $type,...
     *
     * @return DeleteDocumentRequest
     */
    public function type($type)
    {
        $this->params['type'] = array();
        $args                 = func_get_args();
        foreach ($args as $arg) {
            $this->params['type'][] = $arg;
        }

        return $this;
    }

    /**
     * The document to delete
     *
     * @param  null                                         $id
     *
     * @throws \Sherlock\common\exceptions\RuntimeException
     * @return DeleteDocumentRequest
     */
    public function document($id)
    {
        if (!$this->batch instanceof BatchCommand) {
                        throw new exceptions\RuntimeException("Cannot delete a document from an external BatchCommandInterface");
        }

        $command = new Command();
        $command->id($id)
            ->action('delete');

        //Only doing this because typehinting is wonky without it...
        if ($this->batch instanceof BatchCommand) {
            $this->batch->addCommand($command);
        }

        return $this;
    }

    /**
     * Accepts an array of Commands or a BatchCommand
     *
     * @param array|BatchCommandInterface $values
     *
     * @return $this
     * @throws \Sherlock\common\exceptions\BadMethodCallException
     */
    public function documents($values)
    {
        if ($values instanceof BatchCommandInterface) {
            $this->batch = $values;
        } elseif (is_array($values)) {

            $isBatch = true;
            $batch   = new BatchCommand();

            /**
             * @param mixed $value
             */
            $map = function ($value) use ($isBatch, $batch) {
                if (!$value instanceof Command) {
                    $isBatch = false;
                } else {
                    $batch->addCommand($value);
                }
            };

            array_map($map, $values);

            if (!$isBatch) {
                                throw new exceptions\BadMethodCallException("If an array is supplied, all elements must be a Command object.");
            }

            $this->batch = $batch;

        } else {
                        throw new exceptions\BadMethodCallException("Documents method only accepts arrays of Commands or BatchCommandInterface objects");
        }

        return $this;

    }

    /**
     * Perform the delete operation
     *
     * @throws exceptions\RuntimeException
     * @return \Sherlock\responses\DeleteResponse
     */
    public function execute()
    {

        //if this is an internal Sherlock BatchCommand, make sure index/types/action are filled
        if ($this->batch instanceof BatchCommand) {
            $this->batch->fillIndex($this->params['index'][0])
                ->fillType($this->params['type'][0]);
        }

        return parent::execute();
    }

    /**
     * @param $response
     * @return \Sherlock\responses\DeleteResponse|\Sherlock\responses\Response
     */
    protected function getReturnResponse($response)
    {
        return new DeleteResponse($response);
    }
}


<?php

/**
 * A cURL library to fetch a large number of resources while maintaining
 * a consistent number of simultaneous connections
 *
 * @package RollingCurl
 * @version 2.0
 * @author  Jeff Minard (http://jrm.cc/)
 * @author  Josh Fraser (www.joshfraser.com)
 * @author  Alexander Makarov (http://rmcreative.ru/)
 * @license Apache License 2.0
 * @link    https://github.com/chuyskywalker/rolling-curl
 */

namespace Sherlock\common\tmp\RollingCurl;

use Sherlock\common\tmp\RollingCurl\Request;

/**
 * Class that holds a rolling queue of curl requests.
 */
class RollingCurl
{

    /**
     * @var int
     *
     * Max number of simultaneous requests.
     */
    private $simultaneousLimit = 5;

    /**
     * @var int
     *
     * Timeout is the timeout used for curl_multi_select.
     */
    private $timeout = 10;

    /**
     * @var \Closure
     *
     * Callback function to be applied to each result.
     */
    private $callback;

    /**
     * @var array
     *
     * Set your base options that you want to be used with EVERY request. (Can be overridden individually)
     */
    protected $options = array(
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_FOLLOWLOCATION => 1,
        CURLOPT_MAXREDIRS      => 5,
        CURLOPT_CONNECTTIMEOUT => 30,
        CURLOPT_TIMEOUT        => 30,
    );

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

    /**
     * @var Request[]
     *
     * Requests queued to be processed
     */
    private $pendingRequests = array();

    private $pendingRequestsPosition = 0;

    private $pendingRequestsCount = 0;

    /**
     * @var Request[]
     *
     * Requests currently being processed by curl
     */
    private $activeRequests = array();

    /**
     * @var Request[]
     *
     * All processed requests
     */
    private $completedRequests = array();

    /**
     * Add a request to the request queue
     *
     * @param  Request     $request
     *
     * @return RollingCurl
     */
    public function add(Request $request)
    {
        $this->pendingRequests[] = $request;
        $this->pendingRequestsCount += 1;

        return $this;
    }

    /**
     * Create new Request and add it to the request queue
     *
     * @param  string       $url
     * @param  string       $method
     * @param  array|string $postData
     * @param  array        $headers
     * @param  array        $options
     *
     * @return RollingCurl
     */
    public function request($url, $method = "GET", $postData = null, $headers = null, $options = null)
    {
        $newRequest = new Request($url, $method);
        if ($postData) {
            $newRequest->setPostData($postData);
        }
        if ($headers) {
            $newRequest->setHeaders($headers);
        }
        if ($options) {
            $newRequest->setOptions($options);
        }

        return $this->add($newRequest);
    }

    /**
     * Perform GET request
     *
     * @param  string      $url
     * @param  array       $headers
     * @param  array       $options
     *
     * @return RollingCurl
     */
    public function get($url, $headers = null, $options = null)
    {
        return $this->request($url, "GET", null, $headers, $options);
    }

    /**
     * Perform POST request
     *
     * @param  string       $url
     * @param  array|string $postData
     * @param  array        $headers
     * @param  array        $options
     *
     * @return RollingCurl
     */
    public function post($url, $postData = null, $headers = null, $options = null)
    {
        return $this->request($url, "POST", $postData, $headers, $options);
    }

    /**
     * Perform PUT request
     *
     * @param  string      $url
     * @param  null        $putData
     * @param  array       $headers
     * @param  array       $options
     *
     * @return RollingCurl
     */
    public function put($url, $putData = null, $headers = null, $options = null)
    {
        return $this->request($url, "PUT", $putData, $headers, $options);
    }

    /**
     * Perform DELETE request
     *
     * @param  string      $url
     * @param  array       $headers
     * @param  array       $options
     *
     * @return RollingCurl
     */
    public function delete($url, $headers = null, $options = null)
    {
        return $this->request($url, "DELETE", null, $headers, $options);
    }

    /**
     * Run all queued requests
     *
     * @return void
     */
    public function execute()
    {

        $master = curl_multi_init();

        // start the first batch of requests
        $firstBatch = $this->getNextPendingRequests($this->getSimultaneousLimit());

        // what a silly "error"
        if (count($firstBatch) == 0) {
            return;
        }

        foreach ($firstBatch as $request) {
            // setup the curl request, queue it up, and put it in the active array
            $ch      = curl_init();
            $options = $this->prepareRequestOptions($request);
            curl_setopt_array($ch, $options);
            curl_multi_add_handle($master, $ch);
            $this->activeRequests[(string)$ch] = $request;
        }

        do {

            while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM) {
                ;
            }

            if ($execrun != CURLM_OK) {
                // todo: throw exception
                break;
            }

            // a request was just completed -- find out which one
            while ($transfer = curl_multi_info_read($master)) {

                // get the request object back and put the curl response into it
                $key     = (string)$transfer['handle'];
                $request = $this->activeRequests[$key];
                $request->setResponseText(curl_multi_getcontent($transfer['handle']));
                $request->setResponseErrno(curl_errno($transfer['handle']));
                $request->setResponseError(curl_error($transfer['handle']));
                $request->setResponseInfo(curl_getinfo($transfer['handle']));

                // if there is a callback, run it
                if (is_callable($this->callback)) {
                    $callback = $this->callback;
                    $callback($request, $this);
                }

                // remove the request from the list of active requests
                unset($this->activeRequests[$key]);

                // move the request to the completed set
                $this->completedRequests[] = $request;

                // start a new request (it's important to do this before removing the old one)
                if ($nextRequest = $this->getNextPendingRequest()) {
                    // setup the curl request, queue it up, and put it in the active array
                    $ch      = curl_init();
                    $options = $this->prepareRequestOptions($nextRequest);
                    curl_setopt_array($ch, $options);
                    curl_multi_add_handle($master, $ch);
                    $this->activeRequests[(string)$ch] = $nextRequest;
                }

                // remove the curl handle that just completed
                curl_multi_remove_handle($master, $transfer['handle']);

            }

            if ($running) {
                curl_multi_select($master, $this->timeout);
            }

            // keep the loop going as long as multi_exec says it is running
        } while ($running);

        curl_multi_close($master);
    }

    /**
     * Helper function to gather all the curl options: global, inferred, and per request
     *
     * @param  Request $request
     *
     * @return array
     */
    private function prepareRequestOptions(Request $request)
    {

        // options for this entire curl object
        $options = $this->getOptions();

        // set the request URL
        $options[CURLOPT_URL] = $request->getUrl();

        // set the request method
        $options[CURLOPT_CUSTOMREQUEST] = $request->getMethod();

        // posting data w/ this request?
        if ($request->getPostData()) {
            $options[CURLOPT_POST]       = 1;
            $options[CURLOPT_POSTFIELDS] = $request->getPostData();
        }

        // if the request has headers, use those, or if there are global headers, use those
        if ($request->getHeaders()) {
            $options[CURLOPT_HEADER]     = 0;
            $options[CURLOPT_HTTPHEADER] = $request->getHeaders();
        } elseif ($this->getHeaders()) {
            $options[CURLOPT_HEADER]     = 0;
            $options[CURLOPT_HTTPHEADER] = $this->getHeaders();
        }

        // if the request has options set, use those and have them take precedence
        if ($request->getOptions()) {
            $options = $request->getOptions() + $options;
        }

        return $options;
    }

    /**
     * Define an anonymous callback to handle the response:
     *
     *     $rc = new RollingCurl()
     *     $rc->setCallback(function($response, $info, $request, $rolling_curl) {
     *         // process
     *     });
     *
     * Function should take four parameters: $response, $info, $request, $rolling_callback.
     * $response is response body
     * $info is additional curl info
     * $request is the original request
     * $rolling_curl is the current instance of the RollingCurl (useful if you want to requeue a URL)
     *
     * @param  \Closure    $callback
     *
     * @return RollingCurl
     */
    public function setCallback(\Closure $callback)
    {
        $this->callback = $callback;

        return $this;
    }

    /**
     * @return \Closure
     */
    public function getCallback()
    {
        return $this->callback;
    }

    /**
     * @param  array                     $headers
     *
     * @throws \InvalidArgumentException
     * @return RollingCurl
     */
    public function setHeaders($headers)
    {
        if (!is_array($headers)) {
            throw new \InvalidArgumentException("headers must be an array");
        }
        $this->headers = $headers;

        return $this;
    }

    /**
     * @return array
     */
    public function getHeaders()
    {
        return $this->headers;
    }

    /**
     * @param  array                     $options
     *
     * @throws \InvalidArgumentException
     * @return RollingCurl
     */
    public function setOptions($options)
    {
        if (!is_array($options)) {
            throw new \InvalidArgumentException("options must be an array");
        }
        $this->options = $options;

        return $this;
    }

    /**
     * Override and add options
     *
     * @param  array                     $options
     *
     * @throws \InvalidArgumentException
     * @return RollingCurl
     */
    public function addOptions($options)
    {
        if (!is_array($options)) {
            throw new \InvalidArgumentException("options must be an array");
        }
        $this->options = $options + $this->options;

        return $this;
    }

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

    /**
     * @param  int                       $timeout
     *
     * @throws \InvalidArgumentException
     * @return RollingCurl
     */
    public function setTimeout($timeout)
    {
        if (!is_int($timeout) || $timeout < 0) {
            throw new \InvalidArgumentException("Timeout must be an int >= 0");
        }
        $this->timeout = $timeout;

        return $this;
    }

    /**
     * @return float
     */
    public function getTimeout()
    {
        return $this->timeout;
    }

    /**
     * Set the limit for how many cURL requests will be execute simultaneously.
     *
     * Please be mindful that if you set this too high, requests are likely to fail
     * more frequently or automated software may perceive you as a DOS attack and
     * automatically block further requests.
     *
     * @param  int                       $count
     *
     * @throws \InvalidArgumentException
     * @return RollingCurl
     */
    public function setSimultaneousLimit($count)
    {
        if (!is_int($count) || $count < 2) {
            throw new \InvalidArgumentException("setSimultaneousLimit count must be an int >= 2");
        }
        $this->simultaneousLimit = $count;

        return $this;
    }

    /**
     * @return int
     */
    public function getSimultaneousLimit()
    {
        return $this->simultaneousLimit;
    }

    /**
     * Return the next $limit pending requests (may return nothing)
     *
     * @param  int       $limit
     *
     * @return Request[]
     */
    public function getNextPendingRequests($limit = 1)
    {
        //Old method did a lot of array shuffling
        //New method simply maintains a pointer to the current position in the pending queue
        //and walks over the array
        //
        //Much faster, at the expense of not cleaning up memory.  Acceptable, if memory is a problem
        //a streaming interface should be used.

        //pendingRequestCount is incremented when docs are added, since that will happen less frequently than
        //being called here, saves on the slow-ish count()
        $count = $this->pendingRequestsCount;
        $limit = $this->pendingRequestsPosition + $limit;

        if ($limit > $count) {
            $limit = $count;
        }

        $ret = array();

        //for() is faster than foreach, while/each
        //$ret[] is faster than array_push()
        for ($i = $this->pendingRequestsPosition; $i < $limit; ++$i) {
            $ret[] = $this->pendingRequests[$i];
        }

        $this->pendingRequestsPosition = $limit;
        return $ret;

        //return array_splice($this->pendingRequests, 0, $limit);
    }

    /**
     * Return the next pending requests (may return nothing)
     *
     * @return Request|null
     */
    public function getNextPendingRequest()
    {
        $next = $this->getNextPendingRequests();
        if (count($next)) {
            return $next[0];
        }

        return null;
    }

    /**
     * @return Request[]
     */
    public function getCompletedRequests()
    {

        /*
         * Do some minimal memory management.  If the user is requesting all the completed
         * requests, it's probably a good time to clear out the requests in the "pending" buffer, which
         * have already been requested.
         *
         * Grab anything that hasn't been requested and put at the front of the array,
         * reset pointers
         */

        $tmp = array();

        for ($i = $this->pendingRequestsPosition; $i < $this->pendingRequestsCount; ++$i) {
            $tmp[] = $this->pendingRequests[$i];
        }

        $this->pendingRequests         = $tmp;
        $this->pendingRequestsCount    = count($this->pendingRequests);
        $this->pendingRequestsPosition = 0;

        return $this->completedRequests;
    }

}


<?php
/**
 * User: Zachary Tong
 * Date: 3/10/13
 * Time: 11:23 AM
 */

namespace Sherlock\common;

use Sherlock\common\events\RequestEvent;
use Sherlock\common\exceptions\RuntimeException;
use Symfony\Component\EventDispatcher\EventDispatcher;

/**
 * Class Cluster
 * @package Sherlock\common
 */
class Cluster
{
    /**
     * @var array
     */
    private $nodes = array();

    /**
     * @var EventDispatcher
     */
    private $dispatcher;

    /**
     * @param EventDispatcher $dispatcher
     */
    public function __construct(EventDispatcher $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    /**
     * @param  string                              $host
     * @param  int                                 $port
     * @param  bool                                $autodetect
     *
     * @throws exceptions\BadMethodCallException
     * @throws exceptions\InvalidArgumentException
     */
    public function addNode($host, $port, $autodetect = true)
    {
        if (!isset($host)) {
            throw new exceptions\BadMethodCallException("A server address must be provided when adding a node.");
        }

        if (!is_numeric($port)) {
            throw new exceptions\InvalidArgumentException("Port argument must be a number");
        }

        $this->nodes[$host] = array('host' => $host, 'port' => $port);
    }

    /**
     * Autodect various cluster properties
     */
    public function autodetect()
    {
        $this->autodetect_parseNodes();
    }

    /**
     * Triggered just prior to a request being executed
     * Inject a random node into the Request object
     *
     * @param  RequestEvent                $event
     *
     * @throws exceptions\RuntimeException
     */
    public function onRequestExecute(RequestEvent $event)
    {
        $request = $event->getRequest();

        //Make sure we have some nodes to choose from
        if (count($this->nodes) === 0) {
            throw new RuntimeException("No nodes in cluster, request failed");
        }

        //Choose a random node
        $request->node = $this->nodes[array_rand($this->nodes)];

    }

    /**
     * Autodetect the nodes in this cluster through Cluster State API
     */
    private function autodetect_parseNodes()
    {
        foreach ($this->nodes as $node) {
            try {
                $client   = new Client('http://' . $node['host'] . ':' . $node['port']);
                $request  = $client->get('/_nodes/http');
                $response = $request->send()->json();

                foreach ($response['nodes'] as $newNode) {

                    //we don't want http-inaccessible nodes
                    if (!isset($newNode['http_address'])) {
                        continue;
                    }

                    preg_match('/inet\[\/([0-9\.]+):([0-9]+)\]/i', $newNode['http_address'], $match);

                    $tNode = array('host' => $match[1], 'port' => $match[2]);

                    //use host as key so that we don't add duplicates
                    $this->nodes[$match[1]] = $tNode;

                }

                //we have the complete node list, no need to keep checking
                break;

            } catch (\Guzzle\Http\Exception\BadResponseException $e) {
                //error with this node, continue onto the next one
            }
        }
    }
}


<?php
/**
 * User: Zachary Tong
 * Date: 2/17/13
 * Time: 6:39 PM
 * @package Sherlock\requests
 */

namespace Sherlock\requests;

use Sherlock\common\exceptions;
use Sherlock\responses\IndexResponse;

/**
 * This class facilitates indexing single documents into an ElasticSearch index
 * @todo Refactor this whole damn thing
 *
 */
class IndexDocumentRequest extends Request
{
    protected $dispatcher;

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

    /** @var Command */
    private $currentCommand;

    /**
     * @param  \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
     *
     * @throws \Sherlock\common\exceptions\BadMethodCallException
     * @internal param $node
     */
    public function __construct($dispatcher)
    {
        if (!isset($dispatcher)) {
            throw new \Sherlock\common\exceptions\BadMethodCallException("Dispatcher argument required for IndexRequest");
        }

        $this->dispatcher = $dispatcher;

        $this->params['index'] = array();
        $this->params['type']  = array();

        $this->currentCommand   = null;
        $this->params['update'] = false;

        $this->params['updateScript'] = null;
        $this->params['updateParams'] = null;
        $this->params['updateUpsert'] = null;
        $this->params['doc']          = null;

        parent::__construct($dispatcher);
    }

    /**
     * @param $name
     * @param $args
     *
     * @return IndexDocumentRequest
     */
    public function __call($name, $args)
    {
        $this->params[$name] = $args[0];

        return $this;
    }

    /**
     * Set the index to add documents to
     *
     * @param  string               $index     indices to query
     * @param  string               $index,... indices to query
     *
     * @return IndexDocumentRequest
     */
    public function index($index)
    {
        $this->params['index'] = array();
        $args                  = func_get_args();
        foreach ($args as $arg) {
            $this->params['index'][] = $arg;
        }

        return $this;
    }

    /**
     * Set the type to add documents to
     *
     * @param  string               $type
     * @param  string               $type,...
     *
     * @return IndexDocumentRequest
     */
    public function type($type)
    {
        $this->params['type'] = array();
        $args                 = func_get_args();
        foreach ($args as $arg) {
            $this->params['type'][] = $arg;
        }

        return $this;
    }

    /**
     * @param \string $script
     *
     * @return $this
     */
    public function updateScript($script)
    {
        $this->params['updateScript'] = $script;
        $this->currentCommandCheck();
        return $this;
    }

    /**
     * @param \array $params
     *
     * @return $this
     */
    public function updateParams($params)
    {
        $this->params['updateParams'] = $params;
        $this->currentCommandCheck();
        return $this;
    }

    /**
     * @param \string $upsert
     *
     * @return $this
     */
    public function updateUpsert($upsert)
    {
        $this->params['updateUpsert'] = $upsert;
        $this->currentCommandCheck();
        return $this;
    }

    /**
     * The document to index
     *
     * @param  \string|\array $value
     * @param  null           $id
     * @param bool|null       $update
     *
     * @throws \Sherlock\common\exceptions\RuntimeException
     * @return IndexDocumentRequest
     */
    public function document($value, $id = null, $update = false)
    {
        if (!$this->batch instanceof BatchCommand) {
                        throw new exceptions\RuntimeException("Cannot add a new document to an external BatchCommandInterface");
        }

        $this->finalizeCurrentCommand();

        if (is_array($value)) {
            $this->params['doc'] = $value;

        } elseif (is_string($value)) {
            $this->params['doc'] = json_decode($value, true);
        }

        if ($id !== null) {
            $this->currentCommand->id($id)
                ->action('put');

            $this->params['update'] = $update;

        } else {
            $this->currentCommand->action('post');

            $this->params['update'] = false;
        }

        return $this;
    }

    /**
     * Accepts an array of Commands or a BatchCommand
     *
     * @param array|BatchCommandInterface $values
     *
     * @return $this
     * @throws \Sherlock\common\exceptions\BadMethodCallException
     */
    public function documents($values)
    {
        if ($values instanceof BatchCommandInterface) {
            $this->batch = $values;
        } elseif (is_array($values)) {

            $isBatch = true;
            $batch   = new BatchCommand();

            /**
             * @param mixed $value
             */
            $map = function ($value) use ($isBatch, $batch) {
                if (!$value instanceof Command) {
                    $isBatch = false;
                } else {
                    $batch->addCommand($value);
                }
            };

            array_map($map, $values);

            if (!$isBatch) {
                                throw new exceptions\BadMethodCallException("If an array is supplied, all elements must be a Command object.");
            }

            $this->batch = $batch;

        } else {
                        throw new exceptions\BadMethodCallException("Documents method only accepts arrays of Commands or BatchCommandInterface objects");
        }

        return $this;

    }

    /**
     * Perform the indexing operation
     *
     * @throws exceptions\RuntimeException
     * @return \Sherlock\responses\IndexResponse
     */
    public function execute()
    {

        /*
        foreach (array('index', 'type') as $key) {
            if (!isset($this->params[$key])) {
                                throw new exceptions\RuntimeException($key." cannot be empty.");
            }
        }

        foreach (array('index', 'type') as $key) {
            if (count($this->params[$key]) > 1) {
                                throw new exceptions\RuntimeException("Only one ".$key." may be inserted into at a time.");
            }
        }

        */

        $this->finalizeCurrentCommand();

        //if this is an internal Sherlock BatchCommand, make sure index/types/action are filled
        if ($this->batch instanceof BatchCommand) {
            if (isset($this->params['index'][0])) {
                $this->batch->fillIndex($this->params['index'][0]);
            }

            if (isset($this->params['type'][0])) {
                $this->batch->fillType($this->params['type'][0]);
            }

        }

        return parent::execute();
    }

    /**
     *This is all horribly terible and will be ripped out as soon as possible.
     *
     * Basically, This class stores an intermediate Command that maintains state across
     * chained requests, which allows updates (scripts, params, etc) to affect it.
     *
     * The command is finalized when a new document is added, or the request is executed.
     * Finalization means collapsing the doc field data into a param, as well as updating
     * the action/suffix as necessary.
     */
    private function finalizeCurrentCommand()
    {

        if ($this->batch instanceof BatchCommand && $this->currentCommand !== null) {

            if (isset($this->params['update']) && $this->params['update'] === true) {
                $this->currentCommand->action('post')->suffix('_update');

                if ($this->params['doc'] !== null) {
                    $data["doc"] = $this->params['doc'];
                }

                if ($this->params['updateScript'] !== null) {
                    $data["script"] = $this->params['updateScript'];
                }

                if ($this->params['updateParams'] !== null) {
                    $data["params"] = $this->params['updateParams'];
                }

                if ($this->params['updateUpsert'] !== null) {
                    $data["upsert"] = $this->params['updateUpsert'];
                }

                $this->currentCommand->data($data);

            } else {
                $this->currentCommand->data($this->params['doc']);
            }

            $this->batch->addCommand($this->currentCommand);

            $this->params['update'] = false;
        }

        $this->currentCommand = new Command();
    }

    /**
     *Simple helper function to check if the current command is populated
     * If not, create a new one
     */
    private function currentCommandCheck()
    {
        $this->params['update'] = true;
        if ($this->currentCommand === null) {
            $this->currentCommand = new Command();
        }

    }

    /**
     * @param $response
     * @return \Sherlock\responses\IndexResponse|\Sherlock\responses\Response
     */
    protected function getReturnResponse($response)
    {
        return new IndexResponse($response);
    }

}


<?php
/**
 * User: Zachary Tong
 * Date: 2013-02-19
 * Time: 08:26 PM
 * Auto-generated by "generate.filters.php"
 * @package Sherlock\components\filters
 */
namespace Sherlock\components\filters;

use Sherlock\components;

/**
 * Class OrFilter
 * @package Sherlock\components\filters
 *
 * @method \Sherlock\components\filters\OrFilter _cache() _cache(\bool $value) Default: false
 */
class OrFilter extends \Sherlock\components\BaseComponent implements \Sherlock\components\FilterInterface
{
    /**
     * @param null|array $hashMap
     */
    public function __construct($hashMap = null)
    {
        $this->params['_cache'] = false;

        parent::__construct($hashMap);
    }

    /**
     * @param  \Sherlock\components\QueryInterface | \Sherlock\components\QueryInterface | array $values,... - one or more Queries can be specified individually, or an array of filters
     *
     * @return OrFilter
     */
    public function queries($values)
    {

        $args = func_get_args();

        //single param, array of queries\filters
        if (count($args) == 1 && is_array($args[0])) {
            $args = $args[0];
        }

        foreach ($args as $arg) {
            if ($arg instanceof \Sherlock\components\QueryInterface) {
                $this->params['queries'][] = $arg->toArray();
            } elseif ($arg instanceof \Sherlock\components\FilterInterface) {
                $this->params['queries'][] = $arg->toArray();
            }
        }

        return $this;
    }

    /**
     * @return array
     */
    public function toArray()
    {
        $ret = array(
            'or' => array(
                'filters' => $this->params["queries"],
                '_cache'  => $this->params["_cache"],
            ),
        );

        return $ret;
    }

}


<?php
/**
 * User: Zachary Tong
 * Date: 3/14/13
 * Time: 6:27 AM
 */

namespace Sherlock\components\facets;

use Sherlock\common\exceptions\RuntimeException;
use Sherlock\components;

/**]
 * Class Terms
 * @package Sherlock\components\facets
 *
 * @method \Sherlock\components\facets\Terms facetname() facetname(\string $value)
 * @method \Sherlock\components\facets\Terms size() size(\int $value)
 * @method \Sherlock\components\facets\Terms order() order(\string $value) Default: count
 * @method \Sherlock\components\facets\Terms all_terms() all_terms(\bool $value) Default: false
 * @method \Sherlock\components\facets\Terms exclude() exclude(array $value)
 * @method \Sherlock\components\facets\Terms regex() regex(\string $value)
 * @method \Sherlock\components\facets\Terms regex_flags() regex_flags(\int $value)
 * @method \Sherlock\components\facets\Terms script() script(\string $value)
 * @method \Sherlock\components\facets\Terms script_field() script_field(\string $value)
 * @method \Sherlock\components\facets\Terms params() params(array $value)
 * @method \Sherlock\components\facets\Terms lang() lang(\string $value)
 * @method \Sherlock\components\facets\DateHistogram facet_filter() facet_filter(\Sherlock\components\FilterInterface $value)
 */
class Terms extends components\BaseComponent implements components\FacetInterface
{
    /**
     * @param null $hashMap
     */
    public function __construct($hashMap = null)
    {
        $this->params['order']     = 'count';
        $this->params['all_terms'] = false;

        $this->params['facetname']    = null;
        $this->params['size']         = null;
        $this->params['exclude']      = null;
        $this->params['regex']        = null;
        $this->params['regex_flags']  = null;
        $this->params['script']       = null;
        $this->params['script_field'] = null;
        $this->params['params']       = null;
        $this->params['lang']         = null;
        $this->params['facet_filter'] = null;
        $this->params['nested']       = null;

        parent::__construct($hashMap);
    }

    /**
     * @param $queries
     *
     * @return $this
     */
    public function fields($queries)
    {

        $args = func_get_args();

        //single param, array of fields
        if (count($args) == 1 && is_array($args[0])) {
            $args = $args[0];
        }

        foreach ($args as $arg) {
            if (is_string($arg)) {
                $this->params['fields'][] = $arg;
            }
        }

        return $this;
    }

    /**
     * @throws \Sherlock\common\exceptions\RuntimeException
     * @return array
     */
    public function toArray()
    {
        if (!isset($this->params['fields'])) {
                        throw new RuntimeException("Fields parameter is required for a Facet");
        }

        if ($this->params['fields'] === null) {
                        throw new RuntimeException("Fields parameter may not be null");
        }

        //if the user didn't provide a facetname, use the (first) field as a default name
        if ($this->params['facetname'] === null) {
            $this->params['facetname'] = $this->params['fields'][0];
        }

        if ($this->params['facet_filter'] !== null) {
            $this->params['facet_filter'] = $this->params['facet_filter']->toArray();
        }

        $ret = array(
            $this->params['facetname'] => array(
                "terms"        => array(
                    "fields"       => $this->params['fields'],
                    "order"        => $this->params['order'],
                    "all_terms"    => $this->params['all_terms'],
                    "size"         => $this->params['size'],
                    "exclude"      => $this->params['exclude'],
                    "regex"        => $this->params['regex'],
                    "regex_flags"  => $this->params['regex_flags'],
                    "script"       => $this->params['script'],
                    "script_field" => $this->params['script_field'],
                    "params"       => $this->params['params'],
                    "lang"         => $this->params['lang']
                ),
                "facet_filter"  => $this->params['facet_filter'],
                "nested"    => $this->params['nested']
            )
        );

        return $ret;
    }

}


<?php
App::uses('ModelBehavior', 'Model');
App::uses('Album', 'Gallery.Model');
App::uses('Picture', 'Gallery.Model');

class GalleryBehavior extends ModelBehavior
{
    public function afterFind(Model $Model, $results, $primary = false)
    {
        foreach ($results as $key => $val) {

            if ($this->settings[$Model->alias]['automatic']) {
                $gallery = $this->getGallery($Model, $val[$Model->name]['id']);

                if ($gallery) {
                    $results[$key]['Gallery'] = $gallery;
                    $results[$key]['Gallery']['numPictures'] = count($gallery['Picture']);
                }

            }
        }
        return $results;
    }

    public function setup(Model $Model, $settings = array())
    {
        if (!isset($this->settings[$Model->alias])) {
            $this->settings[$Model->alias] = array(
                'automatic' => true
            );
        }
        $this->settings[$Model->alias] = array_merge(
            $this->settings[$Model->alias],
            (array)$settings
        );
    }

    /**
     * Get model gallery
     * @param Model $Model
     * @return mixed
     */
    public function getGallery(Model $Model, $object_id = null)
    {
        $Album = new Album();

        if (!$object_id) {
            $object_id = $Model->id;
        }

        return $Album->getAttachedAlbum($Model->alias, $object_id);
    }
}


<?php

/*
 * This file is part of the PhpMob package.
 *
 * (c) Ishmael Doss <nukboon@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace PhpMob\SettingsBundle\Twig\Helper;

use PhpMob\Settings\Manager\SettingManagerInterface;
use Symfony\Component\Templating\Helper\Helper;

/**
 * @author Ishmael Doss <nukboon@gmail.com>
 */
class SettingHelper extends Helper
{
    /**
     * @var SettingManagerInterface
     */
    private $settingManager;

    /**
     * @param SettingManagerInterface $settingManager
     */
    public function __construct(SettingManagerInterface $settingManager)
    {
        $this->settingManager = $settingManager;
    }

    /**
     * @param string $path
     * @param null|string $owner
     *
     * @return mixed
     */
    public function get(string $path, ?string $owner = null)
    {
        return $this->settingManager->get($path, $owner);
    }

    /**
     * @param string $path
     * @param $value
     * @param null|string $owner
     */
    public function set(string $path, $value, ?string $owner = null): void
    {
        $this->settingManager->set($path, $value, $owner);
    }

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


<?php
App::uses('Folder', 'Utility');
App::uses('GalleryAppModel', 'Gallery.Model');

class Album extends GalleryAppModel
{
    public $name = 'Album';
    public $tablePrefix = 'gallery_';
    public $order = 'Album.id DESC';
    public $hasMany = array(
        'Picture' => array(
            'className' => 'Gallery.Picture',
            'conditions' => array('Picture.main_id' => null),
            'order' => array('Picture.order' => 'ASC')
        )
    );

    public $validate = array(
        'title' => array(
            array(
                'rule' => array('notEmpty'),
                'message' => 'A title is required.'
            )
        )
    );

    /**
     * Create a folder in webroot/files/gallery/{album_id}
     * for this folder after save it (only on create)
     * @param $created
     * @param array $options
     */
    public function afterSave($created, $options = array())
    {
        if ($created) {
            $this->createFolder($this->data['Album']['id']);
        }
    }

    /**
     * Get all published albums
     * @return mixed
     */
    public function published($fields = null)
    {
        return $this->find(
            'all',
            array(
                'conditions' => array(
                    'Album.status' => 'published'
                ),
                'recursive' => 2,
                'fields' => $fields
            )
        );
    }

    /**
     * Get all draft albums
     * @return array
     */
    public function draft($fields = null)
    {
        return $this->find(
            'all',
            array(
                'conditions' => array(
                    'Album.status' => 'draft'
                ),
                'recursive' => 2,
                'fields' => $fields
            )
        );
    }

    /**
     * Create an album record on database
     * @param $model
     * @param $model_id
     */
    public function init($model = null, $model_id = null)
    {
        # If there is a Model and ModelID on parameters, get or create a folder for it
        if ($model && $model_id) {
            # Searching for folder that belongs to this particular $model and $model_id
            if (!$album = $this->getAttachedAlbum($model, $model_id)) {
                # If there is no Album , lets create one for it
                $album = $this->createInitAlbum($model, $model_id);
            }
        } else {
            # If there is no model on parameters, lets create a generic folder
            $album = $this->createInitAlbum(null, null);
        }

        return $album;
    }

    /**
     * Save album with a random name in database
     * @param null $model
     * @param null $model_id
     * @return mixed
     */
    private function createInitAlbum($model = null, $model_id = null)
    {
        $this->save(
            array(
                'Album' => array(
                    'model' => $model,
                    'model_id' => $model_id,
                    'status' => 'draft',
                    'tags' => '',
                    'title' => $this->generateAlbumName($model, $model_id)
                )
            )
        );
        return $this->read(null);
    }

    /**
     * Get an attached album
     * @param null $model
     * @param null $model_id
     * @return mixed
     */
    public function getAttachedAlbum($model = null, $model_id = null)
    {
        return $this->find(
            'first',
            array(
                'conditions' => array(
                    'Album.model' => $model,
                    'Album.model_id' => $model_id
                )
            )
        );
    }

    /**
     * Generate a random album name
     * @param null $model
     * @param null $model_id
     * @return string
     */
    private function generateAlbumName($model = null, $model_id = null)
    {
        $name = 'Album - ' . rand(111, 999);

        if ($model && $model_id) {
            $name = Inflector::humanize('Album ' . $model . ' - ' . $model_id);
        }

        return $name;
    }

    /**
     * Create an folder at webroot/files/gallery to store album pictures
     * @param $folder_name
     */
    private function createFolder($folder_name)
    {
        # Folder to store galleries folders
        $galleries_path = WWW_ROOT . 'files' . DS . 'gallery';

        # Gallery folder
        $folder_path = $galleries_path . DS . $folder_name;

        # Check if webroot/files and webroot/files/gallery folder are created
        if (!is_dir($galleries_path)) {
            if (!is_dir(WWW_ROOT . 'files')) {
                # Create webroot/files folder if dont exists
                new Folder(WWW_ROOT . 'files', true, 0755);
            }
            # Create webroot/files/gallery folder if dont exists
            new Folder($galleries_path, true, 0755);
        }
        if (!is_dir($folder_path)) {
            # Create gallery folder if dont exists
            new Folder($folder_path, true, 0755);
        }
    }
}

?>


<?php

class AlbumsController extends GalleryAppController
{
    public $helpers = array('Form' => array('className' => 'Gallery.CakePHPFTPForm'));

    public $uses = array('Gallery.Album', 'Gallery.Picture');

    public function add()
    {
    }

    public function view($id = null)
    {
        $this->layout = 'showroom';

        $this->Album->id = $id;

        if (!$this->Album->exists()) {
            throw new NotFoundException(__d('gallery', 'This album does not exist'));
        }

        $album = $this->Album->read(null);

        $this->set('title_for_layout', $album['Album']['title']);

        $this->set(compact('album'));
    }

    public function update()
    {
        if ($this->request->is('post')) {
            if ($this->Album->save($this->request->data)) {
                echo __d('gallery', 'You configurations are saved.');
            }
        }
        $this->render(false, false);
    }

    /**
     * Create or find the requested album
     * @param null $model
     * @param null $model_id
     */
    public function upload($model = null, $model_id = null)
    {
        ini_set("memory_limit", "10000M");

        if (isset($this->params['gallery_id']) && !empty($this->params['gallery_id'])) {
            $album = $this->Album->findById($this->params['gallery_id']);
        } else {
            # If the gallery doesnt exists, create a new one and redirect back to this page with the
            # gallery_id
            $album = $this->Album->init($model, $model_id);

            # Redirect back to this page with an album ID
            $this->redirect(
                array(
                    'action' => 'upload',
                    'gallery_id' => $album['Album']['id']
                )
            );
        }

        $files = $album['Picture'];

        $this->set(compact('model', 'model_id', 'album', 'files'));
    }

    public function delete($id)
    {
        $this->Album->id = $id;

        $album = $this->Album->read(null);

        if (count($album['Picture'])) {
            foreach ($album['Picture'] as $pic) {
                # Original
                if ($pic['style'] = 'full') {
                    # Remove from database and all files
                    $this->Picture->delete($pic['id']);
                }
            }
        }

        if ($this->Album->delete($id)) {
            # Delete album folders
            $album_dir = WWW_ROOT . 'files' . DS . 'gallery' . DS . $id . DS;
            $this->Util->deleteDir($album_dir);

            $this->Session->setFlash(__d('gallery', 'Album deleted.'));

            $this->redirect(array('controller' => 'gallery', 'action' => 'index', 'plugin' => 'gallery'));
        }
    }
}

?>


<?php
App::uses('File', 'Utility');

class Picture extends GalleryAppModel
{
    public $name = 'Picture';

    public $pictureToDelete = null;

    public $tablePrefix = 'gallery_';

    public $belongsTo = array('Gallery.Album');

    public $order = 'Picture.order ASC';

    public $validate = array(
        'album_id' => array(
            'rule' => 'numeric',
            'required' => true,
            'message' => 'The album ID is required'
        ),
        'size' => array(
            'rule' => 'numeric',
            'required' => true,
            'message' => 'The SIZE is required'
        ),
        'path' => array(
            'rule' => 'notEmpty',
            'required' => true,
            'message' => 'The PATH is required'
        )
    );

    /**
    * Preserve picture ID for deleting versions after remove from database
    */
    public function beforeDelete($cascade = true)
    {
      $this->pictureToDelete = $this->id;

      return true;
    }

    /**
     * Remove all versions of the picture from the storage after delete the
     * record from the database
     */
    public function afterDelete()
    {
      if( $this->pictureToDelete ) {

        $this->deleteVersions($this->pictureToDelete);

        $this->pictureToDelete = null;
      }
    }

    /**
     * @param $album_id
     * @return int
     */
    public function getNextNumber($album_id)
    {
        return (int)$this->find('count', array('conditions' => array('Picture.album_id' => $album_id))) + 1;
    }

    public function beforeValidate($options = array())
    {
        if (empty($this->data['Picture']['size'])) {
            $file = new File($this->data['Picture']['path']);
            $this->data['Picture']['size'] = $file->size();
        }

        return true;
    }

    /**
     * @param $results
     * @param bool $primary
     * @return mixed
     */
    public function afterFind($results, $primary = false)
    {
        foreach ($results as $key => $val) {
            $root_url = WWW_ROOT;
            $relative_url = Router::url('/');

            if (isset($val['Picture']['path'])) {
                # Add custom styles
                $results[$key]['Picture']['styles'] = $this->getChild($val['Picture']['id']);

                # Add relative image path
                $results[$key]['Picture']['link'] = str_replace($root_url, $relative_url, $val['Picture']['path']);
            }
        }
        return $results;
    }

    /**
     * Return configured main image resize attributes
     * @return array
     */
    public function getResizeToSize()
    {
        $width = $height = 0;

        if (Configure::check('GalleryOptions.Pictures.resize_to.0')) {
            $width = Configure::read('GalleryOptions.Pictures.resize_to.0');
        }

        if (Configure::check('GalleryOptions.Pictures.resize_to.1')) {
            $height = Configure::read('GalleryOptions.Pictures.resize_to.1');
        }

        $crop = Configure::read('GalleryOptions.Pictures.resize_to.2');
        $action = $crop ? "crop" : "";

        return array(
            'width' => $width,
            'height' => $height,
            'action' => $action
        );
    }

    /**
     * Add image styles configured in bootstrap.php
     *
     * @param $path
     * @param array $sizes
     * @return array
     */
    public function addImageStyles($path)
    {
        # Styles configured in bootstrap.php
        $sizes = Configure::read('GalleryOptions.Pictures.styles');

        $links = array();

        if (count($sizes)) {
            $root_url = WWW_ROOT;
            $relative_url = Router::url('/');

            # Current filename
            $filename = end(explode("/", $path));

            foreach ($sizes as $sizename => $size) {
                # Filename with size prefix. E.g: small-filename.jpg
                $modified = $sizename . "-" . $filename;
                # Get final path replacing absolute URl to application relative URL
                $final_path = str_replace($root_url, $relative_url, str_replace($filename, $modified, $path));
                # Add to array
                $links[$sizename] = $final_path;
            }
        }

        return $links;
    }

    /**
     * Remove all versions from a picture from the database and from the server
     *
     * @param $id
     */
    public function deleteVersions($id)
    {
        # Remove all versions of the picture
        $pictures = $this->find(
            'all',
            array(
                'conditions' => array(
                    'Picture.main_id' => $id
                )
            )
        );

        if (count($pictures)) {
            foreach ($pictures as $pic) {
                # Remove file
                if (unlink($pic['Picture']['path'])) {
                    # Remove from database
                    $this->delete($pic['Picture']['id'], array('callback' => false));
                }
            }
        }

        return true;
    }

    /**
     * Save picture information in database
     *
     * @param $album_id
     * @param $filename
     * @param $filesize
     * @param $path
     * @param null $main_id
     * @param string $style
     * @return mixed
     */
    public function savePicture($album_id, $filename, $path, $main_id = null, $style = 'full')
    {
        $this->create();

        # Save the record in database
        $picture = array(
            'Picture' => array(
                'album_id' => $album_id,
                'name' => $filename,
                'path' => $path,
                'main_id' => $main_id,
                'style' => $style
            )
        );

        $this->save($picture);

        return $this->id;
    }

    /**
     * Resize and/or crop an image
     *
     * @param $path
     * @param int $width
     * @param int $height
     * @param null $action
     * @return mixed
     * @throws InternalErrorException
     */
    public function resizeCrop($path, $width = 0, $height = 0, $action = null)
    {
        ini_set("memory_limit", "10000M");

        # Flag
        $delete_png = false;

        $image = new Zebra_Image();

        # Load image
        $image->source_path = $path;

        # The target will be the same image
        $target = $path;

        # Get File Extension
        $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));

        # Convert PNG files to JPG if configured on bootstrap.php
        if (Configure::read('GalleryOptions.Pictures.png2jpg') && $ext == "png") {
            # Flag to check must delete the png file
            $delete_png = true;

            # Store PNG file path to delete later
            $png_file = $target;

            # Update target path with JPG extension
            $target = str_replace(array('.png', '.PNG'), '.jpg', $path);
        }

        # The target will be the same image
        $image->target_path = $target;

        # JPG quality
        $image->jpeg_quality = Configure::read('GalleryOptions.Pictures.jpg_quality');

        # Extra configs
        $image->preserve_aspect_ratio = true;
        $image->enlarge_smaller_images = true;
        $image->preserve_time = true;

        if ($action == "crop") {
            $action = ZEBRA_IMAGE_CROP_CENTER;
        }

        if (!$image->resize($width, $height, $action)) {
            // if there was an error, let's see what the error is about
            switch ($image->error) {

                case 1:
                    throw new InternalErrorException('Source file could not be found!');
                    break;
                case 2:
                    throw new InternalErrorException('Source file is not readable!');
                    break;
                case 3:
                    throw new InternalErrorException('Could not write target file!');
                    break;
                case 4:
                    throw new InternalErrorException('Unsupported source file format!');
                    break;
                case 5:
                    throw new InternalErrorException('Unsupported target file format!');
                    break;
                case 6:
                    throw new InternalErrorException('GD library version does not support target file format!');
                    break;
                case 7:
                    throw new InternalErrorException('GD library is not installed!');
                    break;
                case 8:
                    throw new InternalErrorException('"chmod" command is disabled via configuration!');
                    break;

            }
        } else {
            # Delete PNG file if needed
            if ($delete_png) {
                unlink($png_file);
            }

            return $target;
        }
    }

    /**
     * Upload the image to WWW_ROOT/files/gallery/{album_id}/picture.jpg
     * Optionaly save it to database
     *
     * @param $path
     * @param $album_id
     * @param $filename
     * @param $filesize
     * @param $tmp_name
     * @param int $width
     * @param int $height
     * @param $action
     * @param bool $save
     * @param null $main_id
     * @param string $style
     * @return mixed|null
     * @throws ForbiddenException
     */
    public function uploadFile(
        $path = null,
        $album_id = null,
        $filename,
        $tmp_name = null,
        $width = 0,
        $height = 0,
        $action,
        $save = false,
        $main_id = null,
        $style = 'full'
    ) {

        if (!$album_id) {
            throw new ForbiddenException("The album ID is required");
        }

        if (!$path) {
            throw new ForbiddenException("The PATH is required");
        }

        if (!$tmp_name) {
            throw new ForbiddenException("The TMP_NAME is required");
        }

        if (copy($tmp_name, $path)) {
            # Resize only if the width or the height has benn informed
            if (!!$width || !!$height) {
                # Image transformation / Manipulation
                try {
                    $path = $this->resizeCrop($path, $width, $height, $action);
                } catch (InternalErrorException $e) {
                    throw new ForbiddenException($e->getMessage());
                }
            }
            if ($save) {
                return $this->savePicture($album_id, $filename, $path, $main_id, $style);
            }
            return null;
        } else {
            throw new ForbiddenException("Upload failed. Check your folders permissions.");
        }
    }

    /**
     * Create extra images from a original one bases on
     * styles defined on bootstrap.php
     *
     * @param $styles
     * @param $filename
     * @param $filesize
     * @param $tmp_name
     * @param $album_id
     * @param $main_id
     * @param $image_name
     * @throws ForbiddenException
     */
    public function createExtraImages($styles, $filename, $tmp_name, $album_id, $main_id, $image_name)
    {
        $ext = pathinfo($filename, PATHINFO_EXTENSION);

        if (count($styles)) {
            foreach ($styles as $style_name => $style) {
                $width = $style[0];
                $height = $style[1];
                $crop = $style[2] ? "crop" : "";

                # eg: medium-1982318927313.jpg
                $custom_filename = $style_name . '-' . $image_name . '.' . $ext;

                $path = $this->generateFilePath($album_id, $custom_filename);

                try {
                    $this->uploadFile(
                        $path,
                        $album_id,
                        $style_name . '-' . $filename,
                        $tmp_name,
                        $width,
                        $height,
                        $crop,
                        true,
                        $main_id,
                        $style_name
                    );
                } catch (ForbiddenException $e) {
                    throw new ForbiddenException($e->getMessage());
                }
            }
        }
    }

    /**
     * @param null $picture_id
     * @return array
     */
    private function getChild($picture_id = null)
    {
        $this->unbindModel(
            array('belongsTo' => array('Gallery.Album'))
        );

        $childrens = $this->find(
            'all',
            array(
                'conditions' => array(
                    'main_id' => $picture_id
                ),
                'fields' => array('Picture.path', 'Picture.id', 'Picture.style')
            )
        );

        $childs = array();
        foreach ($childrens as $child) {
            $childs[$child['Picture']['style']] = $child['Picture']['link'];
        }

        return $childs;
    }

    /**
     * Get file size
     * @param null $path
     * @return integer
     */
    public function getFileSize($path = null)
    {
        $file = new File($path);
        return $file->size();
    }

    /**
     * @param $album_id
     * @param $filename
     * @return string
     */
    public function generateFilePath($album_id = null, $filename = null)
    {
        if (!$album_id || !$filename) {
            return false;
        }

        $folder = WWW_ROOT . 'files' . DS . 'gallery' . DS . $album_id . DS;

        if(!is_writable($folder)) {
          throw new ForbiddenException('Your files/gallery directory is not writtable.');
        }

        return  $folder . $filename;
    }
}

?>


<?php

/*
 * This file is part of the PhpMob package.
 *
 * (c) Ishmael Doss <nukboon@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PhpMob\SettingsBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;

/**
 * @author Ishmael Doss <nukboon@gmail.com>
 */
class PhpMobSettingsExtension extends Extension
{
    /**
     * {@inheritdoc}
     */
    public function getAlias()
    {
        return 'phpmob_settings';
    }

    /**
     * {@inheritdoc}
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

        $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.xml');

        $schemaRegistryDef = $container->getDefinition('phpmob.settings.schema_registry');

        foreach ($config['schemas'] as $section => $data) {
            $schemaRegistryDef->addMethodCall('add', [$section, $data]);
        }

        if ($config['cache']['service']) {
            $cachedDefinition = $container->getDefinition('phpmob.settings.cached_manager');
            $cachedDefinition->setAbstract(false);
            $cachedDefinition->setLazy(true);
            $cachedDefinition->setDecoratedService('phpmob.settings.manager');
            $cachedDefinition->setArgument(0, new Reference('phpmob.settings.cached_manager.inner'));
            $cachedDefinition->setArgument(1, new Reference($config['cache']['service']));
            $cachedDefinition->setArgument(2, $config['cache']['lifetime']);
        }
    }
}