<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\EventListener\DcGeneral\Table\MetaModel;

use Contao\System;
use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use MenAtWork\MultiColumnWizardBundle\Event\GetOptionsEvent;

/**
 * This provides the language name options.
 */
class LanguageOptionsListener
{
    /**
     * The scope determinator.
     *
     * @var RequestScopeDeterminator
     */
    private $scopeDeterminator;

    /**
     * Create a new instance.
     *
     * @param RequestScopeDeterminator $scopeDeterminator The scope determinator.
     */
    public function __construct(RequestScopeDeterminator $scopeDeterminator)
    {
        $this->scopeDeterminator = $scopeDeterminator;
    }

    /**
     * Prepare the language options.
     *
     * @param GetOptionsEvent $event The event.
     *
     * @return void
     */
    public function handle(GetOptionsEvent $event)
    {
        if (!$this->wantToHandle($event)) {
            return;
        }

        $event->setOptions(array_flip(array_filter(array_flip(System::getLanguages()), function ($langCode) {
            // Disable >2 char long language codes for the moment.
            return (strlen($langCode) == 2);
        })));
    }

    /**
     * Test if the event is for the correct table and in backend scope.
     *
     * @param GetOptionsEvent $event The event to test.
     *
     * @return bool
     */
    protected function wantToHandle(GetOptionsEvent $event)
    {
        if ($event->getOptions() !== null) {
            return false;
        }

        if (!$this->scopeDeterminator->currentScopeIsBackend()) {
            return false;
        }

        $environment = $event->getEnvironment();
        if ('tl_metamodel' !== $environment->getDataDefinition()->getName()) {
            return false;
        }

        if ($event->getEnvironment()->getDataDefinition()->getName() !== $event->getModel()->getProviderName()) {
            return false;
        }

        return ('languages' === $event->getPropertyName()) && ('langcode' === $event->getSubPropertyName());
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @author     Richard Henkenjohann <richardhenkenjohann@googlemail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\EventListener\DcGeneral\Table\RenderSettings;

use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use MenAtWork\MultiColumnWizardBundle\Event\GetOptionsEvent;
use Symfony\Component\Finder\Finder;
use Webmozart\PathUtil\Path;

/**
 * This handles the rendering of models to labels.
 */
class AddAssetListener
{
    /**
     * The scope determinator.
     *
     * @var RequestScopeDeterminator
     */
    private $scopeDeterminator;

    /**
     * The upload path to scan within.
     *
     * @var string
     */
    private $uploadPath;

    /**
     * Create a new instance.
     *
     * @param RequestScopeDeterminator $scopeDeterminator The scope determinator.
     * @param string                   $uploadPath        The upload path to scan within.
     */
    public function __construct(RequestScopeDeterminator $scopeDeterminator, $uploadPath)
    {
        $this->scopeDeterminator = $scopeDeterminator;
        $this->uploadPath        = $uploadPath;
    }

    /**
     * Provide options for additional css files.
     *
     * @param GetOptionsEvent $event The event.
     *
     * @return void
     */
    public function getStylesheets(GetOptionsEvent $event)
    {
        if (!$this->wantToHandle($event)
            || ($event->getPropertyName() !== 'additionalCss')
            || ($event->getSubPropertyName() !== 'file')) {
            return;
        }

        $event->setOptions($this->scanFiles('css'));
    }

    /**
     * Provide options for additional javascript files.
     *
     * @param GetOptionsEvent $event The event.
     *
     * @return void
     */
    public function getJavascripts(GetOptionsEvent $event)
    {
        if (($event->getEnvironment()->getDataDefinition()->getName() !== 'tl_metamodel_rendersettings')
            || ($event->getPropertyName() !== 'additionalJs')
            || ($event->getSubPropertyName() !== 'file')) {
            return;
        }

        $event->setOptions($this->scanFiles('js'));
    }

    /**
     * Scan for files with the given extension.
     *
     * @param string $extension The file extension.
     *
     * @return array
     */
    private function scanFiles($extension)
    {
        $files = [];
        foreach (Finder::create()->in($this->uploadPath)->name('*.' . $extension)->getIterator() as $item) {
            $files[] = 'files/' . Path::normalize($item->getRelativePathname());
        }

        return $files;
    }

    /**
     * Test if the event is for the correct table and in backend scope.
     *
     * @param GetOptionsEvent $event The event to test.
     *
     * @return bool
     */
    private function wantToHandle(GetOptionsEvent $event)
    {
        if (!$this->scopeDeterminator->currentScopeIsBackend()) {
            return false;
        }

        $environment = $event->getEnvironment();
        if ('tl_metamodel_rendersettings' !== $environment->getDataDefinition()->getName()) {
            return false;
        }

        if ($event->getEnvironment()->getDataDefinition()->getName() !== $event->getModel()->getProviderName()) {
            return false;
        }

        return true;
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @author     Ingolf Steinhardt <info@e-spin.de>
 * @author     David Molineus <david.molineus@netzmacht.de>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\Helper;

use Contao\System;
use Contao\User;
use Doctrine\DBAL\Connection;
use MetaModels\BackendIntegration\InputScreen\IInputScreen;
use MetaModels\BackendIntegration\InputScreen\InputScreen;
use MetaModels\IMetaModelsServiceContainer;

/**
 * Class ViewCombinations.
 *
 * Retrieve combinations of view and input screens for the currently logged in user (either frontend or backend).
 *
 * @deprecated This will get removed.
 */
abstract class ViewCombinations
{
    const COMBINATION   = 'combination';
    const INPUTSCREEN   = 'inputscreen';
    const RENDERSETTING = 'rendersetting';
    const MODELID       = 'metamodel';

    /**
     * All MetaModel combinations for lookup.
     *
     * @var array
     */
    protected $information = array();

    /**
     * All MetaModel combinations for lookup.
     *
     * Mapping is: id => table name.
     *
     * @var string[]
     */
    protected $tableMap = array();

    /**
     * MetaModels by their parent table.
     *
     * @var array
     */
    protected $parentMap = array();

    /**
     * MetaModels to their parent table.
     *
     * @var array
     */
    protected $childMap = array();

    /**
     * The service container.
     *
     * @var IMetaModelsServiceContainer
     */
    protected $container;

    /**
     * The Contao user.
     *
     * @var User
     */
    private $user;

    /**
     * Database connection.
     *
     * @var Connection
     */
    private $connection;

    /**
     * Create a new instance.
     *
     * @param IMetaModelsServiceContainer $container  The service container.
     *
     * @param User                        $user       The current user.
     *
     * @param Connection|null             $connection Database connection.
     */
    public function __construct(IMetaModelsServiceContainer $container, User $user, Connection $connection = null)
    {
        $this->container = $container;
        $this->user      = $user;

        if (!$this->loadFromCache()) {
            $this->resolve();
            $this->saveToCache();
        }

        if (null === $connection) {
            // @codingStandardsIgnoreStart
            @trigger_error(
                'Connection is missing. It has to be passed in the constructor. Fallback will be dropped.',
                E_USER_DEPRECATED
            );
            // @codingStandardsIgnoreEnd
            $connection = System::getContainer()->get('database_connection');
        }

        $this->connection = $connection;
    }

    /**
     * Try to load the combinations from cache.
     *
     * @return string|null
     */
    protected function calculateCacheKey()
    {
        $key = 'view_combination_' . strtolower(TL_MODE);

        // Authenticate the user - if this fails, we use an anonymous cache file.
        if ($this->authenticateUser()) {
            /** @noinspection PhpUndefinedFieldInspection */
            $key .= '_' . $this->getUser()->id;
        } else {
            $key .= '_anonymous';
        }

        return $key;
    }

    /**
     * Retrieve the current user.
     *
     * @return User
     */
    public function getUser()
    {
        static $authenticated;
        if (!isset($authenticated)) {
            $authenticated = true;
            $this->authenticateUser();
        }

        return $this->user;
    }

    /**
     * Try to load the combinations from cache.
     *
     * @return bool
     */
    protected function loadFromCache()
    {
        $key = $this->calculateCacheKey();
        if (!$this->container->getCache()->contains($key)) {
            return false;
        }

        // Perform loading now.
        $data = json_decode($this->container->getCache()->fetch($key), true);

        if (empty($data)) {
            return false;
        }

        $this->information = $data['information'];
        $this->tableMap    = $data['tableMap'];
        $this->parentMap   = $data['parentMap'];
        $this->childMap    = $data['childMap'];

        return true;
    }

    /**
     * Try to load the combinations from cache.
     *
     * @return bool
     */
    protected function saveToCache()
    {
        // Pretty print only came available with php 5.4.
        $flags = 0;
        if (defined('JSON_PRETTY_PRINT')) {
            $flags = JSON_PRETTY_PRINT;
        }

        return $this->container->getCache()->save(
            $this->calculateCacheKey(),
            json_encode(
                array(
                    'information' => $this->information,
                    'tableMap'    => $this->tableMap,
                    'parentMap'   => $this->parentMap,
                    'childMap'    => $this->childMap
                ),
                $flags
            )
        );
    }

    /**
     * Translate the MetaModel id to a valid table name.
     *
     * @param int $metaModelId The id of the MetaModel to translate.
     *
     * @return string
     */
    protected function tableNameFromId($metaModelId)
    {
        return $this->container->getFactory()->translateIdToMetaModelName($metaModelId);
    }

    /**
     * Resolve all combinations available.
     *
     * @return void
     */
    protected function resolve()
    {
        $factory = $this->container->getFactory();

        $names = $factory->collectNames();

        foreach ($names as $name) {
            $this->information[$name] = array
            (
                self::COMBINATION     => null,
                self::INPUTSCREEN     => null,
                self::RENDERSETTING   => null,
                self::MODELID         => null,
            );
        }

        $found = $this->getPaletteCombinationRows();

        if (!$found) {
            $found = array();
        }

        // Clean any undefined.
        foreach (array_keys($this->information) as $tableName) {
            if (empty($this->information[$tableName][self::COMBINATION])
                || empty($this->information[$tableName][self::COMBINATION]['dca_id'])
                || empty($this->information[$tableName][self::COMBINATION]['view_id'])
            ) {
                unset($this->information[$tableName]);
            }
        }

        $this->fetchInputScreenDetails();
    }

    /**
     * Set a table mapping.
     *
     * @param string $modelId   The id of the MetaModel.
     *
     * @param string $tableName The name of the MetaModel.
     *
     * @return void
     */
    protected function setTableMapping($modelId, $tableName)
    {
        $this->information[$tableName][self::MODELID] = $modelId;

        $this->tableMap[$modelId] = $tableName;
    }

    /**
     * Ensure the value is a MetaModel name and not an id.
     *
     * @param string $nameOrId The value to transform to the name.
     *
     * @return string
     */
    protected function getMetaModelName($nameOrId)
    {
        return isset($this->tableMap[$nameOrId]) ? $this->tableMap[$nameOrId] : $nameOrId;
    }

    /**
     * Retrieve the Contao database singleton instance.
     *
     * @return \Database
     */
    protected function getDatabase()
    {
        return $this->container->getDatabase();
    }

    /**
     * Authenticate the user preserving the object stack.
     *
     * NOTE: ONLY call this when not using the cache.
     *
     * @return bool
     */
    abstract protected function authenticateUser();

    /**
     * Retrieve the user groups of the current user.
     *
     * @return array
     */
    protected function getUserGroups()
    {
        /** @noinspection PhpUndefinedFieldInspection */
        return $this->user->groups ? array_filter($this->user->groups) : array();
    }

    /**
     * Retrieve the palette combinations from the database.
     *
     * @return array|\stdClass[]
     */
    protected function getCombinationsFromDatabase()
    {
        $groups = $this->getUserGroups();
        if (!$groups) {
            return null;
        }

        $statement = $this->connection->createQueryBuilder()
            ->select('*')
            ->from('tl_metamodel_dca_combine')
            ->where(strtolower(TL_MODE) . '_group IN (:groups)')
            ->orderBy('pid')
            ->addOrderBy('sorting', 'ASC')
            ->setParameter('groups', $groups)
            ->execute();

        return $statement->fetchAll(\PDO::FETCH_OBJ);
    }

    /**
     * Fetch the palette view configurations valid for the given group ids.
     *
     * This returns the ids that have been resolved.
     *
     * @return int[]
     */
    protected function getPaletteCombinationRows()
    {
        $combinations = $this->getCombinationsFromDatabase();
        $success      = array();

        // No combinations present, nothing to resolve.
        if (!$combinations) {
            return array_keys($this->information);
        }

        foreach ($combinations as $combination) {
            /** @noinspection PhpUndefinedFieldInspection */
            $modelId   = $combination->pid;
            $modelName = $this->tableNameFromId($modelId);

            // Already a combination present, continue with next one.
            if (!empty($this->information[$modelName][self::COMBINATION])) {
                continue;
            }

            /** @noinspection PhpUndefinedFieldInspection */
            $this->information[$modelName][self::MODELID] = $modelId;

            /** @noinspection PhpUndefinedFieldInspection */
            $this->information[$modelName][self::COMBINATION] = array(
                'dca_id'  => $combination->dca_id,
                'view_id' => $combination->view_id
            );

            $this->setTableMapping($modelId, $modelName);

            $success[] = $modelId;
        }

        return $success;
    }

    /**
     * Pull in all DCA settings for the buffered MetaModels and buffer them in the static class.
     *
     * @return void
     *
     * @throws \Doctrine\DBAL\DBALException When a database error occurs.
     */
    protected function fetchInputScreenDetails()
    {
        $inputScreenIds = array();
        foreach ($this->information as $info) {
            $inputScreenIds[] = $info[self::COMBINATION]['dca_id'];
        }

        if (!$inputScreenIds) {
            return;
        }

        $statement = $this->connection->query(
            sprintf(
                'SELECT * FROM tl_metamodel_dca WHERE id IN (%s)',
                implode(',', $inputScreenIds)
            )
        );

        while ($inputScreens = $statement->fetch(\PDO::FETCH_OBJ)) {
            /** @noinspection PhpUndefinedFieldInspection */
            $screenId = $inputScreens->id;
            /** @noinspection PhpUndefinedFieldInspection */
            $metaModelId   = $inputScreens->pid;
            $metaModelName = $this->tableNameFromId($metaModelId);

            $propertyRows = $this->connection
                ->prepare('SELECT * FROM tl_metamodel_dcasetting WHERE pid=? AND published=1 ORDER BY sorting ASC');

            $propertyRows->bindValue(1, $screenId);
            $propertyRows->execute();

            $conditions = $this->connection->prepare('
                    SELECT cond.*, setting.attr_id AS setting_attr_id
                    FROM tl_metamodel_dcasetting_condition AS cond
                    LEFT JOIN tl_metamodel_dcasetting AS setting
                    ON (cond.settingId=setting.id)
                    LEFT JOIN tl_metamodel_dca AS dca
                    ON (setting.pid=dca.id)
                    WHERE dca.id=? AND setting.published=1 AND cond.enabled=1
                    ORDER BY sorting ASC
                ');

            $conditions->bindValue(1, $screenId);
            $conditions->execute();

            $groupSort = $this->connection->prepare('
                    SELECT *
                    FROM tl_metamodel_dca_sortgroup
                    WHERE pid=?
                    ORDER BY sorting ASC
                ');
            $groupSort->bindValue(1, $screenId);
            $groupSort->execute();

            $inputScreen = array(
                'row'        => $inputScreens->row(),
                'properties' => $propertyRows->fetchAll(\PDO::FETCH_ASSOC),
                'conditions' => $conditions->fetchAll(\PDO::FETCH_ASSOC),
                'groupSort'  => $groupSort->fetchAll(\PDO::FETCH_ASSOC)
            );

            $this->information[$metaModelName][self::INPUTSCREEN] = $inputScreen;
            $this->information[$metaModelName][self::MODELID]     = $metaModelId;

            $parentTable = $inputScreen['row']['ptable'];
            if ($parentTable && !$this->isInputScreenStandalone($metaModelName)) {
                $this->parentMap[$parentTable][] = $this->information[$metaModelName][self::MODELID];
                $this->childMap[$metaModelName]  = $parentTable;
            }
        }
    }

    /**
     * Build an input screen instance and return it.
     *
     * @param string $metaModel The name or id of the MetaModel.
     *
     * @return IInputScreen
     */
    protected function buildInputScreen($metaModel)
    {
        $metaModelName = $this->getMetaModelName($metaModel);
        $inputScreen   = $this->information[$metaModelName][self::INPUTSCREEN];

        if (!is_object($inputScreen)) {
            $inputScreen = $this->information[$metaModelName][self::INPUTSCREEN] = new InputScreen(
                $this->container,
                $inputScreen['row'],
                $inputScreen['properties'],
                $inputScreen['conditions'],
                $inputScreen['groupSort']
            );
        }

        return $inputScreen;
    }

    /**
     * Check if the input screen for a MetaModel is stand alone.
     *
     * @param string $metaModel The name of the MetaModel.
     *
     * @return bool
     */
    protected function isInputScreenStandalone($metaModel)
    {
        $information = $this->information[$metaModel];
        $inputScreen = isset($information[self::INPUTSCREEN]) ? $information[self::INPUTSCREEN] : null;
        if (!is_object($inputScreen)) {
            return ($inputScreen['row']['rendertype'] == 'standalone');
        }

        /** @var IInputScreen $inputScreen */
        return $inputScreen->isStandalone();
    }

    /**
     * Retrieve the render setting that is active for the current user.
     *
     * @param string $metaModel The name or id of the MetaModel.
     *
     * @return int
     */
    public function getRenderSetting($metaModel)
    {
        $metaModelName = $this->getMetaModelName($metaModel);

        return isset($this->information[$metaModelName][self::COMBINATION]['view_id'])
            ? $this->information[$metaModelName][self::COMBINATION]['view_id']
            : null;
    }

    /**
     * Retrieve the input screen that is active for the current user.
     *
     * @param string $metaModel The name or id of the MetaModel.
     *
     * @return int
     */
    public function getInputScreen($metaModel)
    {
        $inputScreen = $this->getInputScreenDetails($metaModel);
        return $inputScreen ? $inputScreen->getId() : null;
    }

    /**
     * Retrieve the input screen that is active for the current user.
     *
     * @param string $metaModel The name or id of the MetaModel.
     *
     * @return IInputScreen
     */
    public function getInputScreenDetails($metaModel)
    {
        return $this->buildInputScreen($this->getMetaModelName($metaModel));
    }

    /**
     * Retrieve all standalone input screens.
     *
     * @return IInputScreen[]
     */
    public function getStandaloneInputScreens()
    {
        $result = array();
        foreach (array_keys($this->information) as $modelName) {
            if ($this->isInputScreenStandalone($modelName)) {
                $result[] = $this->getInputScreenDetails($modelName);
            }
        }

        return $result;
    }

    /**
     * Get the name of the parenting MetaModel.
     *
     * @param string $metaModel The name of a child.
     *
     * @return string
     */
    public function getParentOf($metaModel)
    {
        $metaModelName = $this->getMetaModelName($metaModel);

        return isset($this->childMap[$metaModelName]) ? $this->childMap[$metaModelName] : null;
    }

    /**
     * Retrieve all standalone input screens or optionally the children of a given parent.
     *
     * @param string|null $parent The parent table for which the children shall be returned.
     *
     * @return string[]
     */
    public function getParentedInputScreenNames($parent = null)
    {
        $result = array();

        if ($parent) {
            if (!isset($this->parentMap[$parent])) {
                return array();
            }
            foreach ($this->parentMap[$parent] as $child) {
                $result[] = (isset($this->tableMap[$child])) ? $this->tableMap[$child] : $child;
            }

            return $result;
        }

        foreach (array_keys($this->information) as $modelName) {
            if (!$this->isInputScreenStandalone($modelName)) {
                $result[] = (isset($this->tableMap[$modelName])) ? $this->tableMap[$modelName] : $modelName;
            }
        }

        return $result;
    }

    /**
     * Retrieve all standalone input screens or optionally the children of a given parent.
     *
     * @param string|null $parent The parent table for which the children shall be returned.
     *
     * @return IInputScreen[]
     */
    public function getParentedInputScreens($parent = null)
    {
        $result = array();

        foreach ($this->getParentedInputScreenNames($parent) as $modelName) {
            $result[] = $this->getInputScreenDetails($modelName);
        }

        return $result;
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @author     Ingolf Steinhardt <info@e-spin.de>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\Filter\Rules\Condition;

use MetaModels\Filter\FilterRule;
use MetaModels\Filter\IFilter;

/**
 * This is the MetaModel filter interface.
 */
class ConditionAnd extends FilterRule
{
    /**
     * The list of child filters that shall be evaluated.
     *
     * @var IFilter[]
     */
    protected $arrChildFilters = array();

    /**
     * Adds a child filter to this rule that will get evaluated when this rule is evaluated.
     *
     * @param IFilter $objFilter The filter to add as child.
     *
     * @return ConditionAnd
     */
    public function addChild(IFilter $objFilter)
    {
        $this->arrChildFilters[] = $objFilter;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getMatchingIds()
    {
        if (0 === count($this->arrChildFilters)) {
            return array();
        }

        $ids = null;
        foreach ($this->arrChildFilters as $objChildFilter) {
            $matchingIds = $objChildFilter->getMatchingIds();
            if (array() === $matchingIds) {
                // Empty array, no items allowed by this rule, break out.
                return array();
            }

            // If null => all items allowed by this rule => ignore it.
            if (null === $matchingIds) {
                continue;
            }

            if (null === $ids) {
                $ids = $matchingIds;
                continue;
            }

            $ids = array_intersect($ids, $matchingIds);
        }

        return is_array($ids) ? array_values($ids) : null;
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     David Molineus <david.molineus@netzmacht.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\Attribute;

use Doctrine\DBAL\Connection;
use MetaModels\Helper\TableManipulator;

/**
 * This is an abstract factory to query instances of attributes.
 *
 * Extend your own attribute factories from this class and register them when the create attribute factory event is
 * triggered.
 */
class AbstractSimpleAttributeTypeFactory extends AbstractAttributeTypeFactory
{
    /**
     * Database connection.
     *
     * @var Connection
     */
    protected $connection;

    /**
     * Table manipulator.
     *
     * @var TableManipulator
     */
    protected $tableManipulator;

    /**
     * Construct.
     *
     * @param Connection       $connection       Database connection.
     * @param TableManipulator $tableManipulator Table manipulator.
     */
    protected function __construct(Connection $connection, TableManipulator $tableManipulator)
    {
        $this->connection       = $connection;
        $this->tableManipulator = $tableManipulator;
    }

    /**
     * {@inheritDoc}
     */
    public function createInstance($information, $metaModel)
    {
        return new $this->typeClass($metaModel, $information, $this->connection, $this->tableManipulator);
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\EventListener\DcGeneral\Breadcrumb;

use MetaModels\CoreBundle\Assets\IconBuilder;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Translation\TranslatorInterface;

/**
 * This class creates an instance of a breadcrumb store.
 */
class BreadcrumbStoreFactory
{
    /**
     * The icon builder.
     *
     * @var IconBuilder
     */
    private $iconBuilder;

    /**
     * The translator.
     *
     * @var TranslatorInterface
     */
    private $translator;

    /**
     * The request stack.
     *
     * @var RequestStack
     */
    private $requestStack;

    /**
     * Create a new instance.
     *
     * @param IconBuilder         $iconBuilder  The icon builder.
     * @param TranslatorInterface $translator   The translator.
     * @param RequestStack        $requestStack The request stack.
     */
    public function __construct(IconBuilder $iconBuilder, TranslatorInterface $translator, RequestStack $requestStack)
    {
        $this->iconBuilder  = $iconBuilder;
        $this->translator   = $translator;
        $this->requestStack = $requestStack;
    }

    /**
     * Create a breadcrumb store.
     *
     * @return BreadcrumbStore
     */
    public function createStore()
    {
        $request = $this->requestStack->getCurrentRequest();

        return new BreadcrumbStore($this->iconBuilder, $this->translator, $request ? $request->getUri() : '');
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\EventListener\DcGeneral\Table\SearchablePages;

use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\NotCondition;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\PropertyConditionChain;
use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\Condition\Property\PropertyValueCondition;
use ContaoCommunityAlliance\DcGeneral\Factory\Event\BuildDataDefinitionEvent;

/**
 * This builds the palette conditions as specified by the configuration.
 */
class PaletteRestrictionListener
{
    /**
     * Build the data definition palettes.
     *
     * @param BuildDataDefinitionEvent $event The event.
     *
     * @return void
     */
    public function handle(BuildDataDefinitionEvent $event)
    {
        if (($event->getContainer()->getName() !== 'tl_metamodel_searchable_pages')) {
            return;
        }

        foreach ($event->getContainer()->getPalettesDefinition()->getPalettes() as $palette) {
            foreach ($palette->getProperties() as $property) {
                if ($property->getName() != 'filterparams') {
                    continue;
                }

                $chain = $property->getVisibleCondition();
                if (!($chain
                    && ($chain instanceof PropertyConditionChain)
                    && $chain->getConjunction() == PropertyConditionChain::AND_CONJUNCTION
                )
                ) {
                    $chain = new PropertyConditionChain(
                        $chain ?: array(),
                        PropertyConditionChain::AND_CONJUNCTION
                    );

                    $property->setVisibleCondition($chain);
                }

                $chain->addCondition(new NotCondition(new PropertyValueCondition('filter', 0)));
                break;
            }
        }
    }
}


<?php

/**
 * This file is part of MetaModels/core.
 *
 * (c) 2012-2019 The MetaModels team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * This project is provided in good faith and hope to be usable by anyone.
 *
 * @package    MetaModels/core
 * @author     Christian Schiffler <c.schiffler@cyberspectrum.de>
 * @author     Sven Baumann <baumann.sv@gmail.com>
 * @copyright  2012-2019 The MetaModels team.
 * @license    https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
 * @filesource
 */

namespace MetaModels\CoreBundle\EventListener\DcGeneral\Table\SearchablePages;

use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator;
use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\BuildWidgetEvent;
use MetaModels\Filter\Setting\IFilterSettingFactory;

/**
 * This handles the providing of available filters.
 */
class FilterParamWidgetListener extends AbstractAbstainingListener
{
    /**
     * The filter factory.
     *
     * @var IFilterSettingFactory
     */
    private $settingFactory;

    /**
     * Create a new instance.
     *
     * @param RequestScopeDeterminator $scopeDeterminator The scope determinator.
     * @param IFilterSettingFactory    $settingFactory    The connection.
     */
    public function __construct(RequestScopeDeterminator $scopeDeterminator, IFilterSettingFactory $settingFactory)
    {
        parent::__construct($scopeDeterminator);
        $this->settingFactory = $settingFactory;
    }

    /**
     * Build the filter params for the widget.
     *
     * @param BuildWidgetEvent $event The event.
     *
     * @return void
     */
    public function handle(BuildWidgetEvent $event)
    {
        if (!$this->wantToHandle($event) || ($event->getProperty()->getName() !== 'filterparams')) {
            return;
        }

        $model = $event->getModel();

        $objFilterSettings  = $this->settingFactory->createCollection($model->getProperty('filter'));
        $extra              = $event->getProperty()->getExtra();
        $extra['subfields'] = $objFilterSettings->getParameterDCA();
        $event->getProperty()->setExtra($extra);
    }
}


<?php // vim:set ts=4 sw=4 et:

namespace ElasticSearch\DSL;

/**
 * This file is part of the ElasticSearch PHP client
 *
 * (c) Raymond Julin <raymond.julin@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Parse a DSL object into a string based representation
 * Return string representation of DSL for search.
 * This will remove certain fields that are not supported
 * in a string representation
 *
 * @author Raymond Julin <raymond.julin@gmail.com>
 * @package ElasticSearch
 * @since 0.1
 * Created: 2010-07-24
 */
class Stringify {

    protected $dsl = array();

    public function __construct(array $dsl) {
        $this->dsl = $dsl;
    }

    public function __toString() {
        $dsl = $this->dsl;
        $query = $dsl['query'];

        $string = "";
        if (array_key_exists("term", $query))
            $string .= $this->transformDSLTermToString($query['term']);
        if (array_key_exists("wildcard", $query))
            $string .= $this->transformDSLTermToString($query['wildcard']);
        if (array_key_exists("sort", $dsl))
            $string .= $this->transformDSLSortToString($dsl['sort']);
        if (array_key_exists("fields", $dsl))
            $string .= $this->transformDSLFieldsToString($dsl['fields']);
        return $string;
    }

    /**
     * A naive transformation of possible term and wildcard arrays in a DSL
     * query
     *
     * @return string
     * @param mixed $dslTerm
     */
    protected function transformDSLTermToString($dslTerm) {
        $string = "";
        if (is_array($dslTerm)) {
            $key = key($dslTerm);
            $value = $dslTerm[$key];
            if (is_string($key))
                $string .= "$key:";
        }
        else
            $value = $dslTerm;
        /**
         * If a specific key is used as key in the array
         * this should translate to searching in a specific field (field:term)
         */
        if (strpos($value, " ") !== false)
            $string .= '"' . $value . '"';
        else
            $string .= $value;
        return $string;
    }

    /**
     * Transform search parameters to string
     *
     * @return string
     * @param mixed $dslSort
     */
    protected function transformDSLSortToString($dslSort) {
        $string = "";
        if (is_array($dslSort)) {
            foreach ($dslSort as $sort) {
                if (is_array($sort)) {
                    $field = key($sort);
                    $info = current($sort);
                }
                else
                    $field = $sort;
                $string .= "&sort=" . $field;
                if (isset($info)) {
                    if (is_string($info) && $info == "desc")
                        $string .= ":reverse";
                    elseif (is_array($info) && array_key_exists("reverse", $info) && $info['reverse'])
                        $string .= ":reverse";
                }
            }
        }
        return $string;
    }

    /**
     * Transform a selection of fields to return to string form
     *
     * @return string
     * @param mixed $dslFields
     */
    protected function transformDSLFieldsToString($dslFields) {
        $string = "";
        if (is_array($dslFields))
            $string .= "&fields=" . join(",", $dslFields);
        return $string;
    }
}


<?php // vim:set ts=4 sw=4 et:

namespace ElasticSearch\Transport;

/**
 * This file is part of the ElasticSearch PHP client
 *
 * (c) Raymond Julin <raymond.julin@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

abstract class Base {

    /**
     * What host to connect to for server
     * @var string
     */
    protected $host = "";

    /**
     * Port to connect on
     * @var int
     */
    protected $port = 9200;

    /**
     * ElasticSearch index
     * @var string
     */
    protected $index;

    /**
     * ElasticSearch document type
     * @var string
     */
    protected $type;

    /**
     * Default constructor, just set host and port
     * @param string $host
     * @param int $port
     */
    public function __construct($host, $port) {
        $this->host = $host;
        $this->port = $port;
    }

    /**
     * Method for indexing a new document
     *
     * @param array|object $document
     * @param mixed $id
     * @param array $options
     */
    abstract public function index($document, $id=false, array $options = array());

    /**
     * Method for updating a document
     *
     * @param array|object $partialDocument
     * @param mixed        $id
     * @param array        $options
     */
    abstract public function update($partialDocument, $id, array $options = array());

    /**
     * Perform a request against the given path/method/payload combination
     * Example:
     * $es->request('/_status');
     *
     * @param string|array $path
     * @param string $method
     * @param array|bool $payload
     * @return
     */
    abstract public function request($path, $method="GET", $payload=false);

    /**
     * Delete a document by its id
     * @param mixed $id
     */
    abstract public function delete($id=false);

    /**
     * Perform a search based on query
     * @param array|string $query
     */
    abstract public function search($query);

    /**
     * Search
     *
     * @return array
     * @param mixed $query String or array to use as criteria for delete
     * @param array $options Parameters to pass to delete action
     * @throws \Elasticsearch\Exception
     */
    public function deleteByQuery($query, array $options = array()) {
        throw new \Elasticsearch\Exception(__FUNCTION__ . ' not implemented for ' . __CLASS__);
    }

    /**
     * Set what index to act against
     * @param string $index
     */
    public function setIndex($index) {
        $this->index = $index;
    }

    /**
     * Set what document types to act against
     * @param string $type
     */
    public function setType($type) {
        $this->type = $type;
    }

    /**
     * Build a callable url
     *
     * @return string
     * @param array|bool $path
     * @param array $options Query parameter options to pass
     */
    protected function buildUrl($path = false, array $options = array()) {
        $isAbsolute = (is_array($path) ? $path[0][0] : $path[0]) === '/';
        $url = $isAbsolute || null === $this->index ? '' : "/" . $this->index;

        if ($path && is_array($path) && count($path) > 0)
            $url .= "/" . implode("/", array_filter($path));
        if (substr($url, -1) == "/")
            $url = substr($url, 0, -1);
        if (count($options) > 0)
          $url .= "?" . http_build_query($options, '', '&');

        return $url;
    }
}


<?php // vim:set ts=4 sw=4 et:

namespace ElasticSearch\Transport;

use \Memcache;
use \ElasticSearch\DSL\Stringify;

/**
 * This file is part of the ElasticSearch PHP client
 *
 * (c) Raymond Julin <raymond.julin@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class Memcached extends Base {
    public function __construct($host="127.0.0.1", $port=11311, $timeout=null) {
        parent::__construct($host, $port);
        $this->conn = new Memcache;
        $this->conn->connect($host, $port, $timeout);
    }

    /**
     * Index a new document or update it if existing
     *
     * @return array
     * @param array $document
     * @param mixed $id Optional
     * @param array $options
     * @throws \ElasticSearch\Exception
     */
    public function index($document, $id=false, array $options = array()) {
        if ($id === false)
            throw new \ElasticSearch\Exception("Memcached transport requires id when indexing");

        $document = json_encode($document);
        $url = $this->buildUrl(array($this->type, $id));
        $response = $this->conn->set($url, $document);
        return array(
            'ok' => $response
        );
    }

    /**
     * Update a part of a document
     *
     * @return array
     * @param  array                    $partialDocument
     * @param  mixed                    $id
     * @param  array                    $options
     */
    public function update($partialDocument, $id, array $options = array()) {
        $document = json_encode(array('doc' => $partialDocument));
        $url = $this->buildUrl(array($this->type, $id));
        $response = $this->conn->set($url, $document);

        return array(
            'ok' => $response,
        );
    }

    /**
     * Search
     *
     * @return array
     * @param array|string $query
     * @throws \ElasticSearch\Exception
     */
    public function search($query) {
        if (is_array($query)) {
            if (array_key_exists("query", $query)) {
                $dsl = new Stringify($query);
                $q = (string) $dsl;
                $url = $this->buildUrl(array(
                    $this->type, "_search?q=" . $q
                ));
                $result = json_decode($this->conn->get($url), true);
                return $result;
            }
            throw new \ElasticSearch\Exception("Memcached protocol doesnt support the full DSL, only query");
        }
        elseif (is_string($query)) {
            /**
             * String based search means http query string search
             */
            $url = $this->buildUrl(array(
                $this->type, "_search?q=" . $query
            ));
            $result = json_decode($this->conn->get($url), true);
            return $result;
        }
    }

    /**
     * Perform a request against the given path/method/payload combination
     * Example:
     * $es->request('/_status');
     *
     * @param string|array $path
     * @param string $method
     * @param array|bool $payload
     * @return array
     */
    public function request($path, $method="GET", $payload=false) {
        $url = $this->buildUrl($path);
        switch ($method) {
            case 'GET':
                $result = $this->conn->get($url);
                break;
            case 'DELETE':
                $result = $this->conn->delete($url);
                break;
        }
        return json_decode($result);
    }

    /**
     * Flush this index/type combination
     *
     * @return array
     * @param mixed $id
     * @param array $options Parameters to pass to delete action
     */
    public function delete($id=false, array $options = array()) {
        if ($id)
            return $this->request(array($this->type, $id), "DELETE");
        else
            return $this->request(false, "DELETE");
    }
}


<?php // vim:set ts=4 sw=4 et:

namespace ElasticSearch;

class Bulk {

	private $client;
	private $operations = array();

    /**
     * Construct a bulk operation
     *
     * @param \ElasticSearch\Client
     */

	public function __construct(Client $client) {
		$this->client = $client;
	}

    /**
     * commit this operation
     */
	public function commit() {
		return $this->client->request('/_bulk', 'POST', $this->createPayload());
	}

    /**
     * reset this operation
     */
	public function reset() {
		$this->operations = array();
	}

    /**
     * Index a new document or update it if existing
     *
     * @param array $document
     * @param mixed $id Optional
     * @param string $index Index
     * @param string $type Type
     * @param array $options Allow sending query parameters to control indexing further
     *        _refresh_ *bool* If set to true, immediately refresh the shard after indexing
     * @return \Elasticsearch\Bulk
     */
	public function index($document, $id=null, $index, $type, array $options = array()) {
		$params = array( '_id' => $id,
   					     '_index' => $index,
					     '_type' => $type);

		foreach ($options as $key => $value) {
			$params['_' . $key] = $value;
		}

		$operation = array(
			array('index' => $params),
			$document
		);
		$this->operations[] = $operation;
		return $this;
	}

    /**
     * Update a part of a document
     *
     * @param array  $partialDocument
     * @param mixed  $id
     * @param string $index    Index
     * @param string $type     Type
     * @param array  $options  Allow sending query parameters to control indexing further
     *                         _refresh_ *bool* If set to true, immediately refresh the shard after indexing
     *
     * @return \Elasticsearch\Bulk
     */
    public function update($partialDocument, $id, $index, $type, array $options = array()) {
        $params = array(
            '_id'    => $id,
            '_index' => $index,
            '_type'  => $type,
        );

        foreach ($options as $key => $value) {
            $params['_'.$key] = $value;
        }

        $operation = array(
            array('update' => $params),
            array('doc'    => $partialDocument),
        );
        $this->operations[] = $operation;

        return $this;
    }

	 /**
     * delete a document
     *
     * @param mixed $id
     * @param string $index Index
     * @param string $type Type
     * @param array $options Parameters to pass to delete action
     * @return \Elasticsearch\Bulk
     */
	public function delete($id=false, $index, $type, array $options = array()) {
		$params = array( '_id' => $id,
   					     '_index' => $index,
					     '_type' => $type);

		foreach ($options as $key => $value) {
			$params['_' . $key] = $value;
		}

		$operation = array(
			array('delete' => $params)
		);
		$this->operations[] = $operation;
		return $this;

	}

	 /**
	 * get all pending operations
	 * @return array
     */
	public function getOperations() {
		return $this->operations;
	}

	 /**
	 * count all pending operations
	 * @return int
     */
	public function count() {
		return count($this->operations);
	}

	 /**
	 * create a request payload with all pending operations
	 * @return string
     */
	public function createPayload()
	{
		$payloads = array();
		foreach ($this->operations as $operation) {
			foreach ($operation as $partial) {
				$payloads[] = json_encode($partial);
			}
		}
		return join("\n", $payloads)."\n";
	}
}


<?php // vim:set ts=4 sw=4 et:

namespace ElasticSearch\DSL;

/**
 * This file is part of the ElasticSearch PHP client
 *
 * (c) Raymond Julin <raymond.julin@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handle the query sub dsl
 *
 * @author Raymond Julin <raymond.julin@gmail.com>
 * @package ElasticSearchClient
 * @since 0.1
 * Created: 2010-07-24
 */
class Query {
    protected $term = null;
    /**
     * @var RangeQuery
     */
    protected $range;
    protected $prefix = null;
    protected $wildcard = null;
    protected $matchAll = null;
    protected $queryString = null;
    protected $bool = null;
    protected $disMax = null;
    protected $constantScore = null;
    protected $filteredQuery = null;

    public function __construct(array $options=array()) {
    }

    /**
     * Add a term to this query
     *
     * @return \ElasticSearch\DSL\Query
     * @param string $term
     * @param bool|string $field
     */
    public function term($term, $field=false) {
        $this->term = ($field)
            ? array($field => $term)
            : $term;
        return $this;
    }

    /**
     * Add a wildcard to this query
     *
     * @return \ElasticSearch\DSL\Query
     * @param $val
     * @param bool|string $field
     */
    public function wildcard($val, $field=false) {
        $this->wildcard = ($field)
            ? array($field => $val)
            : $val;
        return $this;
    }

    /**
     * Add a range query
     *
     * @return \ElasticSearch\DSL\RangeQuery
     * @param array $options
     */
    public function range(array $options=array()) {
        $this->range = new RangeQuery($options);
        return $this->range;
    }

    /**
     * Build the DSL as array
     *
     * @return array
     */
    public function build() {
        $built = array();
        if ($this->term)
            $built['term'] = $this->term;
        elseif ($this->range)
            $built['range'] = $this->range->build();
        elseif ($this->wildcard)
            $built['wildcard'] = $this->wildcard;
        return $built;
    }
}


<?php // vim:set ts=4 sw=4 et:

namespace ElasticSearch\DSL;
/**
 * This file is part of the ElasticSearch PHP client
 *
 * (c) Raymond Julin <raymond.julin@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Helper stuff for working with the ElasticSearch DSL
 * How to build a mildly complex query:
 * $dsl = new ElasticSearchDSL;
 * $bool = $dsl->bool(); // Return a new bool structure
 *
 * @author Raymond Julin <raymond.julin@gmail.com>
 * @package ElasticSearchClient
 * @since 0.1
 * Created: 2010-07-23
 */
class Builder {

    protected $dsl = array();

    private $explain = null;
    private $from = null;
    private $size = null;
    private $fields = null;
    private $query = null;
    private $facets = null;
    private $sort = null;

    /**
     * Construct DSL object
     *
     * @return \ElasticSearch\DSL\Builder
     * @param array $options
     */
    public function __construct(array $options=array()) {
        foreach ($options as $key => $value)
            $this->$key = $value;
    }

    /**
     * Add array clause, can only be one
     *
     * @return \ElasticSearch\DSL\Query
     * @param array $options
     */
    public function query(array $options=array()) {
        if (!($this->query instanceof Query))
            $this->query = new Query($options);
        return $this->query;
    }

    /**
     * Build the DSL as array
     *
     * @throws \ElasticSearch\Exception
     * @return array
     */
    public function build() {
        $built = array();
        if ($this->from != null)
            $built['from'] = $this->from;
        if ($this->size != null)
            $built['size'] = $this->size;
        if ($this->sort && is_array($this->sort))
            $built['sort'] = $this->sort;
        if (!$this->query)
            throw new \ElasticSearch\Exception("Query must be specified");
        else
            $built['query'] = $this->query->build();
        return $built;
    }
}


<?php // vim:set ts=4 sw=4 et:

namespace ElasticSearch\Transport;

/**
 * This file is part of the ElasticSearch PHP client
 *
 * (c) Raymond Julin <raymond.julin@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

if (!defined('CURLE_OPERATION_TIMEDOUT'))
    define('CURLE_OPERATION_TIMEDOUT', 28);

class HTTP extends Base {

    /**
     * How long before timing out CURL call
     */
    private $timeout = 5;

    /**
     * curl handler which is needed for reusing existing http connection to the server
     * @var resource
     */
    protected $ch;

    public function __construct($host='localhost', $port=9200, $timeout=null) {
        parent::__construct($host, $port);
        if(null !== $timeout) {
            $this->setTimeout($timeout);
        }
        $this->ch = curl_init();
    }

    /**
     * Index a new document or update it if existing
     *
     * @return array
     * @param array $document
     * @param mixed $id Optional
     * @param array $options
     */
    public function index($document, $id=false, array $options = array()) {
        $url = $this->buildUrl(array($this->type, $id), $options);
        $method = ($id == false) ? "POST" : "PUT";
        return $this->call($url, $method, $document);
    }

    /**
     * Update a part of a document
     *
     * @return array
     *
     * @param array $partialDocument
     * @param mixed $id
     * @param array $options
     */
    public function update($partialDocument, $id, array $options = array()) {
        $url = $this->buildUrl(array($this->type, $id), $options);

        return $this->call($url, "POST", array('doc' => $partialDocument));
    }

    /**
     * Search
     *
     * @return array
     * @param array|string $query
     * @param array $options
     */
    public function search($query, array $options = array()) {
        $result = false;
        if (is_array($query)) {
            /**
             * Array implies using the JSON query DSL
             */
            $arg = "_search";
            /**
             * $options may contain values like:
             * $options['routing'] = 'user123'
             * or
             * $options['preference'] = 'xyzabc123'
             */
            $url = $this->buildUrl(array($this->type, $arg), $options);

            $result = $this->call($url, "GET", $query);
        }
        elseif (is_string($query)) {
            /**
             * String based search means http query string search
             */
            $url = $this->buildUrl(array(
                $this->type, "_search?q=" . $query
            ));
            $result = $this->call($url, "POST", $options);
        }
        else {
            /**
             * no http query string search
             */
            $url = $this->buildUrl(array(
                $this->type, "_search?"
            ));
            $result = $this->call($url, "POST", $options);
        }
        return $result;
    }

    /**
     * Search
     *
     * @return array
     * @param mixed $query
     * @param array $options Parameters to pass to delete action
     */
    public function deleteByQuery($query, array $options = array()) {
        $options += array(
            'refresh' => true
        );
        if (is_array($query)) {
            /**
             * Array implies using the JSON query DSL
             */
            $url = $this->buildUrl(array($this->type, "_query"));
            $result = $this->call($url, "DELETE", $query);
        }
        elseif (is_string($query)) {
            /**
             * String based search means http query string search
             */
            $url = $this->buildUrl(array($this->type, "_query"), array('q' => $query));
            $result = $this->call($url, "DELETE");
        }
        if ($options['refresh']) {
            $this->request('_refresh', "POST");
        }
        return !isset($result['error']);
    }

    /**
     * Perform a request against the given path/method/payload combination
     * Example:
     * $es->request('/_status');
     *
     * @param string|array $path
     * @param string $method
     * @param array|bool $payload
     * @return array
     */
    public function request($path, $method="GET", $payload=false) {
        return $this->call($this->buildUrl($path), $method, $payload);
    }

    /**
     * Flush this index/type combination
     *
     * @return array
     * @param mixed $id Id of document to delete
     * @param array $options Parameters to pass to delete action
     */
    public function delete($id=false, array $options = array()) {
        if ($id)
            return $this->call($this->buildUrl(array($this->type, $id), $options), "DELETE");
        else
            return $this->request(false, "DELETE");
    }

    /**
     * Perform a http call against an url with an optional payload
     *
     * @return array
     * @param string $url
     * @param string $method (GET/POST/PUT/DELETE)
     * @param array|bool $payload The document/instructions to pass along
     * @throws HTTPException
     */
    protected function call($url, $method="GET", $payload=null) {
        $conn = $this->ch;
        $protocol = "http";
        $requestURL = $protocol . "://" . $this->host . $url;
        curl_setopt($conn, CURLOPT_URL, $requestURL);
        curl_setopt($conn, CURLOPT_TIMEOUT, $this->timeout);
        curl_setopt($conn, CURLOPT_PORT, $this->port);
        curl_setopt($conn, CURLOPT_CUSTOMREQUEST, strtoupper($method));
        curl_setopt($conn, CURLOPT_FORBID_REUSE , 0) ;

	$headers = array();
        $headers[] = 'Accept: application/json';
        $headers[] = 'Content-Type: application/json';

        curl_setopt($conn, CURLOPT_HTTPHEADER, $headers);

        if (is_array($payload) && count($payload) > 0)
            curl_setopt($conn, CURLOPT_POSTFIELDS, json_encode($payload)) ;
        else
	       	curl_setopt($conn, CURLOPT_POSTFIELDS, $payload);

        // cURL opt returntransfer leaks memory, therefore OB instead.
        ob_start();
        curl_exec($conn);
        $response = ob_get_clean();
        if ($response !== false) {
            $data = json_decode($response, true);
            if (!$data) {
                $data = array('error' => $response, "code" => curl_getinfo($conn, CURLINFO_HTTP_CODE));
            }
        }
        else {
            /**
             * cUrl error code reference can be found here:
             * http://curl.haxx.se/libcurl/c/libcurl-errors.html
             */
            $errno = curl_errno($conn);
            switch ($errno)
            {
                case CURLE_UNSUPPORTED_PROTOCOL:
                    $error = "Unsupported protocol [$protocol]";
                    break;
                case CURLE_FAILED_INIT:
                    $error = "Internal cUrl error?";
                    break;
                case CURLE_URL_MALFORMAT:
                    $error = "Malformed URL [$requestURL] -d " . json_encode($payload);
                    break;
                case CURLE_COULDNT_RESOLVE_PROXY:
                    $error = "Couldnt resolve proxy";
                    break;
                case CURLE_COULDNT_RESOLVE_HOST:
                    $error = "Couldnt resolve host";
                    break;
                case CURLE_COULDNT_CONNECT:
                    $error = "Couldnt connect to host [{$this->host}], ElasticSearch down?";
                    break;
                case CURLE_OPERATION_TIMEDOUT:
                    $error = "Operation timed out on [$requestURL]";
                    break;
                default:
                    $error = "Unknown error";
                    if ($errno == 0) {
                        $error .= ". Non-cUrl error";
                    } else {
                        $errstr = curl_error($conn);
                        $error .= " ($errstr)";
                    }
                    break;
            }
            $exception = new HTTPException($error);
            $exception->payload = $payload;
            $exception->port = $this->port;
            $exception->protocol = $protocol;
            $exception->host = $this->host;
            $exception->method = $method;
            throw $exception;
        }

        return $data;
    }

    public function setTimeout($timeout)
    {
        $this->timeout = $timeout;
    }

    public function getTimeout()
    {
        return $this->timeout;
    }
}


<?php // vim:set ts=4 sw=4 et:

namespace ElasticSearch\DSL;

/**
 * This file is part of the ElasticSearch PHP client
 *
 * (c) Raymond Julin <raymond.julin@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Range queries
 *
 * @author Raymond Julin <raymond.julin@gmail.com>
 * @package ElasticSearchClient
 * @since 0.1
 * Created: 2010-07-24
 */
class RangeQuery {
    protected $fieldname = null;
    protected $from = null;
    protected $to = null;
    protected $includeLower = null;
    protected $includeUpper = null;
    protected $boost = null;

    /**
     * Construct new RangeQuery component
     *
     * @return \ElasticSearch\DSL\RangeQuery
     * @param array $options
     */
    public function __construct(array $options=array()) {
        $this->fieldname = key($options);
        $values = current($options);
        if (is_array($values)) {
            foreach ($values as $key => $val)
                $this->$key = $val;
        }
    }

    /**
     * Setters
     *
     * @return \ElasticSearch\DSL\RangeQuery
     * @param mixed $value
     */
    public function fieldname($value) {
        $this->fieldname = $value;
        return $this;
    }

    /**
     * @param $value
     * @return \ElasticSearch\DSL\RangeQuery $this
     */
    public function from($value) {
        $this->from = $value;
        return $this;
    }
    /**
     * @param $value
     * @return \ElasticSearch\DSL\RangeQuery $this
     */
    public function to($value) {
        $this->to = $value;
        return $this;
    }
    /**
     * @param $value
     * @return \ElasticSearch\DSL\RangeQuery $this
     */
    public function includeLower($value) {
        $this->includeLower = $value;
        return $this;
    }
    /**
     * @param $value
     * @return \ElasticSearch\DSL\RangeQuery $this
     */
    public function includeUpper($value) {
        $this->includeUpper = $value;
        return $this;
    }
    /**
     * @param $value
     * @return \ElasticSearch\DSL\RangeQuery $this
     */
    public function boost($value) {
        $this->boost = $value;
        return $this;
    }

    /**
     * Build to array
     *
     * @throws \ElasticSearch\Exception
     * @return array
     */
    public function build() {
        $built = array();
        if ($this->fieldname) {
            $built[$this->fieldname] = array();
            foreach (array("from","to","includeLower","includeUpper", "boost") as $opt) {
                if ($this->$opt !== null)
                    $built[$this->fieldname][$opt] = $this->$opt;
            }
            if (count($built[$this->fieldname]) == 0)
                throw new \ElasticSearch\Exception("Empty RangeQuery cant be created");
        }
        return $built;
    }
}


<?php // vim:set ts=4 sw=4 et:

namespace ElasticSearch;

/**
 * This file is part of the ElasticSearch PHP client
 *
 * (c) Raymond Julin <raymond.julin@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class Mapping {

    protected $properties = array();
    protected $config = array();

    /**
     * Build mapping data
     *
     * @param array $properties
     * @param array $config
     * @return \ElasticSearch\Mapping
     */
    public function __construct(array $properties = array(), array $config = array()) {
        $this->properties = $properties;
        $this->config = $config;
    }

    /**
     * Export mapping data as a json-ready array
     *
     * @return string
     */
    public function export() {
        return array(
            'properties' => $this->properties
        );
    }

    /**
     * Add or overwrite existing field by name
     *
     * @param string $field
     * @param string|array $config
     * @return $this
     */
    public function field($field, $config = array()) {
        if (is_string($config)) $config = array('type' => $config);
        $this->properties[$field] = $config;
        return $this;
    }

    /**
     * Get or set a config
     *
     * @param string $key
     * @param mixed $value
     * @throws \Exception
     * @return array|void
     */
    public function config($key, $value = null) {
        if (is_array($key))
            $this->config = $key + $this->config;
        else {
            if ($value !== null) $this->config[$key] = $value;
            if (!isset($this->config[$key]))
                throw new \Exception("Configuration key `type` is not set");
            return $this->config[$key];
        }
    }
}


<?php // vim:set ts=4 sw=4 et:

namespace ElasticSearch;

/**
 * This file is part of the ElasticSearch PHP client
 *
 * (c) Raymond Julin <raymond.julin@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class Client {
    const DEFAULT_PROTOCOL = 'http';
    const DEFAULT_SERVER = '127.0.0.1:9200';
    const DEFAULT_INDEX = 'default-index';
    const DEFAULT_TYPE = 'default-type';

    protected $_config = array();

    protected static $_defaults = array(
        'protocol' => Client::DEFAULT_PROTOCOL,
        'servers' => Client::DEFAULT_SERVER,
        'index' => Client::DEFAULT_INDEX,
        'type' => Client::DEFAULT_TYPE,
        'timeout' => null,
    );

    protected static $_protocols = array(
        'http' => 'ElasticSearch\\Transport\\HTTP',
        'memcached' => 'ElasticSearch\\Transport\\Memcached',
    );

    private $transport, $index, $type, $bulk;

    /**
     * Construct search client
     *
     * @return \ElasticSearch\Client
     * @param \ElasticSearch\Transport\Base $transport
     * @param string $index
     * @param string $type
     */
    public function __construct($transport, $index = null, $type = null) {
        $this->transport = $transport;
        $this->setIndex($index)->setType($type);
    }

    /**
     * Get a client instance
     * Defaults to opening a http transport connection to 127.0.0.1:9200
     *
     * @param string|array $config Allow overriding only the configuration bits you desire
     *   - _transport_
     *   - _host_
     *   - _port_
     *   - _index_
     *   - _type_
     * @throws \Exception
     * @return \ElasticSearch\Client
     */
    public static function connection($config = array()) {
        if (!$config && ($url = getenv('ELASTICSEARCH_URL'))) {
            $config = $url;
        }
        if (is_string($config)) {
            $config = self::parseDsn($config);
        }

        $config += self::$_defaults;

        $protocol = $config['protocol'];
        if (!isset(self::$_protocols[$protocol])) {
            throw new \Exception("Tried to use unknown protocol: $protocol");
        }
        $class = self::$_protocols[$protocol];

        if (null !== $config['timeout'] && !is_numeric($config['timeout'])) {
            throw new \Exception("HTTP timeout should have a numeric value when specified.");
        }

        $server = is_array($config['servers']) ? $config['servers'][0] : $config['servers'];
        list($host, $port) = explode(':', $server);

        $transport = new $class($host, $port, $config['timeout']);

        $client = new self($transport, $config['index'], $config['type']);
        $client->config($config);
        return $client;
    }

    /**
     * @param array|null $config
     * @return array|void
     */
    public function config($config = null) {
        if (!$config)
            return $this->_config;
        if (is_array($config))
            $this->_config = $config + $this->_config;
    }

    /**
     * Change what index to go against
     * @return \ElasticSearch\Client
     * @param mixed $index
     */
    public function setIndex($index) {
        if (is_array($index))
            $index = implode(",", array_filter($index));
        $this->index = $index;
        $this->transport->setIndex($index);
        return $this;
    }

    /**
     * Change what types to act against
     * @return \ElasticSearch\Client
     * @param mixed $type
     */
    public function setType($type) {
        if (is_array($type))
            $type = implode(",", array_filter($type));
        $this->type = $type;
        $this->transport->setType($type);
        return $this;
    }

    /**
     * Fetch a document by its id
     *
     * @return array
     * @param mixed $id Optional
     * @param bool $verbose
     */
    public function get($id, $verbose=false) {
        return $this->request($id, "GET");
    }

    /**
     * Puts a mapping on index
     *
     * @param array|object $mapping
     * @param array $config
     * @throws Exception
     * @return array
     */
    public function map($mapping, array $config = array()) {
        if (is_array($mapping)) $mapping = new Mapping($mapping);
        $mapping->config($config);

        try {
            $type = $mapping->config('type');
        }
        catch (\Exception $e) {} // No type is cool
        if (isset($type) && !$this->passesTypeConstraint($type)) {
            throw new Exception("Cant create mapping due to type constraint mismatch");
        }

        return $this->request('_mapping', 'PUT', $mapping->export(), true);
    }

    protected function passesTypeConstraint($constraint) {
        if (is_string($constraint)) $constraint = array($constraint);
        $currentType = explode(',', $this->type);
        $includeTypes = array_intersect($constraint, $currentType);
        return ($constraint && count($includeTypes) === count($constraint));
    }

    /**
     * Perform a raw request
     *
     * Usage example
     *
     *     $response = $client->request('_status', 'GET');
     *
     * @return array
     * @param mixed $path Request path to use.
     *     `type` is prepended to this path inside request
     * @param string $method HTTP verb to use
     * @param mixed $payload Array of data to be json-encoded
     * @param bool $verbose Controls response data, if `false`
     *     only `_source` of response is returned
     */
    public function request($path, $method = 'GET', $payload = false, $verbose=false) {
        $response = $this->transport->request($this->expandPath($path), $method, $payload);
        return ($verbose || !isset($response['_source']))
            ? $response
            : $response['_source'];
    }

    /**
     * Index a new document or update it if existing
     *
     * @return array
     * @param array $document
     * @param mixed $id Optional
     * @param array $options Allow sending query parameters to control indexing further
     *        _refresh_ *bool* If set to true, immediately refresh the shard after indexing
     */
    public function index($document, $id=false, array $options = array()) {
        if ($this->bulk) {
            return $this->bulk->index($document, $id, $this->index, $this->type, $options);
        }
        return $this->transport->index($document, $id, $options);
    }

    /**
     * Update a part of a document
     *
     * @return array
     *
     * @param array $partialDocument
     * @param mixed $id
     * @param array $options  Allow sending query parameters to control indexing further
     *                        _refresh_ *bool* If set to true, immediately refresh the shard after indexing
     */
    public function update($partialDocument, $id, array $options = array()) {
        if ($this->bulk) {
            return $this->bulk->update($partialDocument, $id, $this->index, $this->type, $options);
        }
        return $this->transport->update($partialDocument, $id, $options);
    }

    /**
     * Perform search, this is the sweet spot
     *
     * @return array
     * @param $query
     * @param array $options
     */
    public function search($query, array $options = array()) {
        $start = microtime(true);
        $result = $this->transport->search($query, $options);
        $result['time'] = microtime(true) - $start;
        return $result;
    }

    /**
     * Flush this index/type combination
     *
     * @return array
     * @param mixed $id If id is supplied, delete that id for this index
     *                  if not wipe the entire index
     * @param array $options Parameters to pass to delete action
     */
    public function delete($id=false, array $options = array()) {
        if ($this->bulk) {
            return $this->bulk->delete($id, $this->index, $this->type, $options);
        }
        return $this->transport->delete($id, $options);
    }

    /**
     * Flush this index/type combination
     *
     * @return array
     * @param mixed $query Text or array based query to delete everything that matches
     * @param array $options Parameters to pass to delete action
     */
    public function deleteByQuery($query, array $options = array()) {
        return $this->transport->deleteByQuery($query, $options);
    }

    /**
     * Perform refresh of current indexes
     *
     * @return array
     */
    public function refresh() {
        return $this->transport->request(array('_refresh'), 'GET');
    }

    /**
     * Expand a given path (array or string)
     * If this is not an absolute path index + type will be prepended
     * If it is an absolute path it will be used as is
     *
     * @param mixed $path
     * @return array
     */
    protected function expandPath($path) {
        $path = (array) $path;
        $isAbsolute = $path[0][0] === '/';

        return $isAbsolute
            ? $path
            : array_merge((array) $this->type, $path);
    }

    /**
     * Parse a DSN string into an associative array
     *
     * @param string $dsn
     * @return array
     */
    protected static function parseDsn($dsn) {
        $parts = parse_url($dsn);
        $protocol = $parts['scheme'];
        $servers = $parts['host'] . ':' . $parts['port'];
        if (isset($parts['path'])) {
            $path = explode('/', $parts['path']);
            list($index, $type) = array_values(array_filter($path));
        }
        return compact('protocol', 'servers', 'index', 'type');
    }

    /**
     * Create a bulk-transaction
     *
     * @return \Elasticsearch\Bulk
     */

    public function createBulk() {
        return new Bulk($this);
    }

    /**
     * Begin a transparent bulk-transaction
     * if one is already running, return its handle
     * @return \Elasticsearch\Bulk
     */

    public function beginBulk() {
        if (!$this->bulk) {
            $this->bulk = $this->createBulk($this);
        }
        return $this->bulk;
    }

    /**
    * @see beginBulk
    */
    public function begin() {
        return $this->beginBulk();
    }

    /**
     * commit a bulk-transaction
     * @return array
     */

    public function commitBulk() {
        if ($this->bulk && $this->bulk->count()) {
            $result = $this->bulk->commit();
            $this->bulk = null;
            return $result;
        }
    }

    /**
    * @see commitBulk
    */
    public function commit() {
        return $this->commitBulk();
    }

}


<?php

/**
 * Template hints
 *
 * @author Fabrizio Branca
 */
class Aoe_TemplateHints_Model_Renderer_Opentip extends Aoe_TemplateHints_Model_Renderer_Abstract {

    /**
     * @var array
     */
    protected $aStatistics = array(
        Aoe_TemplateHints_Helper_BlockInfo::TYPE_CACHED => 0,
        Aoe_TemplateHints_Helper_BlockInfo::TYPE_IMPLICITLYCACHED => 0,
        Aoe_TemplateHints_Helper_BlockInfo::TYPE_NOTCACHED => 0,
    );

    /**
     * Init
     *
     * @param $wrappedHtml
     * @return string|void
     */
    public function init($wrappedHtml) {
        $helper = Mage::helper('aoe_templatehints'); /* @var $helper Aoe_TemplateHints_Helper_Data */

        $wrappedHtml .= '';
        $wrappedHtml .= '';
        $wrappedHtml .= '';
        $wrappedHtml .= '';
        $wrappedHtml .= '';

        return $wrappedHtml;
    }

    /**
     * Get CSS class for the hint
     *
     * @return string
     */
    protected function getHintClass()
    {
        return 'tpl-hint tpl-hint-border';
    }

    /**
     * Render template hint
     *
     * @param Mage_Core_Block_Abstract $block
     * @param $blockContent
     * @param $id
     * @return string|void
     */
    public function render(Mage_Core_Block_Abstract $block, $blockContent, $id) {

        $helper = Mage::helper('aoe_templatehints/blockInfo'); /* @var $helper Aoe_TemplateHints_Helper_BlockInfo */

        $path = $helper->getBlockPath($block);
        $blockInfo = $helper->getBlockInfo($block);

        $this->aStatistics[$blockInfo['cache-status']]++;

        $wrappedHtml = sprintf(
            '

                %3$s

%4$s

%5$s

',
            $id,
            $this->getHintClass() . ' ' . $blockInfo['cache-status'],
            $blockContent,
            $helper->renderTitle($blockInfo),
            $this->renderBox($blockInfo, $path)
        );

        $showStatistics = false; // experimental (want to add some styling here...)
        if ($showStatistics && $blockInfo['name'] == 'core_profiler') {
            $wrappedHtml .= '
'.print_r($this->aStatistics, true).'
';
        }

        return $wrappedHtml;
    }

    /**
     * Render box
     *
     * @param array $info
     * @param array $path
     * @return string
     */
    protected function renderBox(array $info, array $path) {

        $helper = Mage::helper('aoe_templatehints/blockInfo'); /* @var $helper Aoe_TemplateHints_Helper_BlockInfo */

        $output = '';

        $output .= '
';

        $output .= $this->arrayToDtDd($info, array('name', 'alias', 'cache-status'));

        if (count($path) > 0) {
            $output .= '
'.$helper->__('Block nesting').':

';
                $output .= '
';
                foreach ($path as $step) {
                    $output .= '
'.$helper->renderTitle($step).'
';
                }
                $output .= '
';
            $output .= '
';
        }

        $output .= '
';

        return $output;
    }

    /**
     * Render array as

     *
     * @param array $array
     * @param array $skipKeys
     * @return string
     */
    protected function arrayToDtDd(array $array, array $skipKeys=array()) {
        $output = '
';
        foreach ($array as $key => $value) {

            if (in_array($key, $skipKeys)) {
                continue;
            }

            if (is_array($value)) {
                $value = $this->arrayToDtDd($value);
            }
            if (is_int($key)) {
                $output .= $value . '
';
            } else {
                $output .= '
'.ucfirst($key).':

';
                $output .= $value;
                $output .= '
';
            }
        }
        $output .= '
';
        return $output;
    }

}


<?php

/**
 * Template hints
 *
 * @author Fabrizio Branca
 */
class Aoe_TemplateHints_Model_Observer {

    /**
     * @var bool
     */
    protected $showHints;

    /**
     * @var bool
     */
    protected $init = true;

    /**
     * @var bool
     */
    protected $afterHead = false;

    /**
     * @var int
     */
    protected $hintId = 0;

    /**
     * @var Aoe_TemplateHints_Model_Renderer_Abstract
     */
    protected $renderer;

    /**
     * Check if hints should be displayed
     *
     * @return bool
     */
    public function showHints() {
        if (is_null($this->showHints)) {
            $this->showHints = false;
            if (Mage::helper('core')->isDevAllowed()) {
                if (Mage::getSingleton('core/cookie')->get('ath') || Mage::app()->getRequest()->get('ath')) {
                    $this->showHints = true;
                }
            }
        }
        return $this->showHints;
    }

    /**
     * Get renderer
     *
     * @return Aoe_TemplateHints_Model_Renderer_Abstract
     */
    public function getRenderer() {
        if (is_null($this->renderer)) {
            $rendererClass = Mage::getStoreConfig('dev/aoe_templatehints/templateHintRenderer');
            if (empty($rendererClass)) {
                Mage::throwException('No renderer configured');
            }
            $this->renderer = Mage::getSingleton($rendererClass);
            if (!is_object($this->renderer) || !$this->renderer instanceof Aoe_TemplateHints_Model_Renderer_Abstract) {
                Mage::throwException('Render must be an instanceof Aoe_TemplateHints_Model_Renderer_Abstract');
            }
        }
        return $this->renderer;
    }

    /**
     * Event core_block_abstract_to_html_after
     *
     * @param Varien_Event_Observer $params
     * @return void
     * @author Fabrizio Branca
     * @since 2011-01-24
     */
    public function core_block_abstract_to_html_after(Varien_Event_Observer $params) {

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

        if (substr(trim($params->getTransport()->getHtml()), 0, 4) == 'http') {
            return;
        }

        $wrappedHtml = '';

        $block = $params->getBlock(); /* @var $block Mage_Core_Block_Abstract */

        // will only be called once and allows renderes to initialize themselves (e.g. adding js/css)
        if ($this->init && $this->afterHead && !Mage::getStoreConfigFlag('advanced/modules_disable_output/' . $block->getModuleName()) && $block->getNameInLayout()) {
            $wrappedHtml = '<!-- INIT AOE_TEMPLATEHINTS RENDERER START -->' . $this->getRenderer()->init($wrappedHtml) . '<!-- INIT AOE_TEMPLATEHINTS RENDERER STOP -->';
            $this->init = false;
        }

        if ($block->getNameInLayout() == 'head') {
            $this->afterHead = true;
        }

        $transport = $params->getTransport();

        $this->hintId++;

        $wrappedHtml .= $this->getRenderer()->render($block, $transport->getHtml(), $this->hintId);

        $transport->setHtml($wrappedHtml);
    }

}


<?php

/**
 * Data helper
 *
 * @author Fabrizio Branca
 */
class Aoe_TemplateHints_Helper_Data extends Mage_Core_Helper_Abstract {

    /**
     * Get skin file content
     *
     * @param $file
     * @return string
     */
    public function getSkinFileContent($file) {
        $package = Mage::getSingleton('core/design_package');
        $areaBackup = $package->getArea();
        $path = $package
            ->setArea('frontend')
            ->getFilename($file, array('_type' => 'skin'));
        $content = file_get_contents($path);
        $package->setArea($areaBackup);
        return $content;
    }

}


<?php
/**
 * Hint renderer
 *
 * @author Fabrizio Branca
 * @since 2013-2/19/13
 */
class Aoe_TemplateHints_Model_Adminhtml_System_Config_Source_HintRenderer {

    /**
     * Get option array
     *
     * @return array
     */
    public function toOptionArray() {
        $options = array();
        $options[] = array(
            'value'=> 'aoe_templatehints/renderer_comment',
            'label'=> Mage::helper('aoe_templatehints')->__('Comments')
        );
        $options[] = array(
            'value'=> 'aoe_templatehints/renderer_opentip',
            'label'=> Mage::helper('aoe_templatehints')->__('Popups')
        );
        $options[] = array(
            'value'=> 'aoe_templatehints/renderer_tipOnly',
            'label'=> Mage::helper('aoe_templatehints')->__('Popups (border initially invisible)')
        );

        Mage::dispatchEvent('aoetemplatehints_hintrenderer_options', array('options' => &$options));

        return $options;
    }
}


<?php

/**
 * Template hints
 *
 * @author Fabrizio Branca
 */
class Aoe_TemplateHints_Model_Renderer_Comment extends Aoe_TemplateHints_Model_Renderer_Abstract {

    /**
     * Render template hint
     *
     * @param Mage_Core_Block_Abstract $block
     * @param $blockContent
     * @param $id
     * @return string|void
     */
    public function render(Mage_Core_Block_Abstract $block, $blockContent, $id) {

        $helper = Mage::helper('aoe_templatehints/blockInfo'); /* @var $helper Aoe_TemplateHints_Helper_BlockInfo */

        $path = $helper->getBlockPath($block);
        $blockInfo = $helper->getBlockInfo($block);

        $wrappedHtml = sprintf(
            "\n".
            '<!-- [START: %1$s] %4$s'."\n".'%5$s -->'.
            "\n".
            '%3$s'.
            "\n".
            '<!-- [END: %1$s] %4$s -->'.
            "\n",
            $id,
            $blockInfo['cache-status'],
            $blockContent,
            $helper->renderTitle($blockInfo),
            $this->renderBox($blockInfo, $path)
        );

        return $wrappedHtml;
    }

    /**
     * Render box
     *
     * @param array $info
     * @param array $path
     * @return string
     */
    protected function renderBox(array $info, array $path) {

        $helper = Mage::helper('aoe_templatehints/blockInfo'); /* @var $helper Aoe_TemplateHints_Helper_BlockInfo */

        $output = '';

        $output .= $this->arrayToTabList($info, array('name', 'alias'));

        if (count($path) > 0) {
            $output .= "\t" . $helper->__('Block nesting').":\n";
            foreach ($path as $step) {
                $output .= "\t\t" . $helper->renderTitle($step) . "\n";
            }
        }

        return $output;
    }

    /**
    * convert nested array to tab indented list
    *
    * @param array $array
    * @param array $skipKeys
    * @param int $indentationLevel
    * @return string
    */
    protected function arrayToTabList(array $array, array $skipKeys=array(), $indentationLevel = 1) {
        $output = '';
        foreach($array as $key => $value) {
            if (in_array($key, $skipKeys, true)) {
                continue;
            }

            $output .= $this->tabsForIndentation($indentationLevel);

            if (!is_array($value)) {
                if (!is_int($key)) {
                    $output .= ucfirst($key) . ":\n";
                    $output .= $this->tabsForIndentation($indentationLevel+1);
                }
                $output .= $value . "\n";
            }
            else {
                $output .= ucfirst($key) . ":\n";
                $output .= $this->arrayToTabList($value, $skipKeys, $indentationLevel+1);
            }
        }

        return $output;
    }

    /**
    * Outputs Tabs
    *
    * @param int $indentationLevel
    * @return string
    */
    protected function tabsForIndentation($indentationLevel) {
        $output = '';
        for($i = 0; $i < $indentationLevel; $i++) {
            $output .= "\t";
        }

        return $output;
    }
}


<?php

/**
 * Class info helper
 *
 * @author Fabrizio Branca
 */
class Aoe_TemplateHints_Helper_ClassInfo extends Mage_Core_Helper_Abstract {

    /**
     * Find file and line of the class statement
     *
     * @param $className
     * @return bool|array
     */
    public function findFileAndLine($className) {
        $result = false;
        $fullPath = $this->searchFullPath($this->getFileFromClassName($className));
        if ($fullPath) {
            $result = array('file' => $fullPath, 'line' => 0);
            $lineNumber = $this->getLineNumber($fullPath, '/class\s+'.$className.'/');
            if ($lineNumber) {
                $result['line'] = $lineNumber;
            }
        }
        return $result;
    }

    /**
     * Get the line number of the first line in a file matching a given regex
     * Not the nicest solution, but probably the fastest
     *
     * @param $file
     * @param $regex
     * @return bool|int
     */
    public function getLineNumber($file, $regex) {
        $i = 0;
        $lineFound = false;
        $handle = @fopen($file, 'r');
        if ($handle) {
            while (($buffer = fgets($handle, 4096)) !== false) {
                $i++;
                if (preg_match($regex, $buffer)) {
                    $lineFound = true;
                    break;
                }
            }
            fclose($handle);
        }
        return $lineFound ? $i : false;
    }

    /**
     * Find a filename in the include path fallback
     *
     * @param $filename
     * @return bool|string
     */
    public function searchFullPath($filename) {
        $paths = explode(PATH_SEPARATOR, get_include_path());
        foreach ($paths as $path) {
            $fullPath = $path . DIRECTORY_SEPARATOR . $filename;
            if (file_exists($fullPath)) {
                return $fullPath;
            }
        }
        return false;
    }

    /**
     * Get php file from class name
     *
     * @param $className
     * @return string
     */
    public function getFileFromClassName($className) {
        return str_replace(' ', DIRECTORY_SEPARATOR, ucwords(str_replace('_', ' ', $className))) . '.php';
    }

}


<?php

/**
 * Block info data helper
 *
 * @author Fabrizio Branca
 */
class Aoe_TemplateHints_Helper_BlockInfo extends Mage_Core_Helper_Abstract {

    CONST TYPE_CACHED = 'cached';
    CONST TYPE_NOTCACHED = 'notcached';
    CONST TYPE_IMPLICITLYCACHED = 'implicitlycached';

    protected $classMethodCache = array();
    protected $remoteCallEnabled;
    protected $remoteCallUrlTemplate;

    /**
     * Get block information
     *
     * @param Mage_Core_Block_Abstract $block
     * @param bool $fullInfo
     * @return array
     */
    public function getBlockInfo(Mage_Core_Block_Abstract $block, $fullInfo=true) {
        $info = array(
            'name' => $block->getNameInLayout(),
            'alias' => $block->getBlockAlias(),
        );

        if (!$fullInfo) {
            return $info;
        }

        $info['class'] = get_class($block);

        if ($this->getRemoteCallEnabled()) {
            $fileAndLine = Mage::helper('aoe_templatehints/classInfo')->findFileAndLine($info['class']);
            if ($fileAndLine) {
                $url = sprintf($this->getRemoteCallUrlTemplate(), $fileAndLine['file'], $fileAndLine['line']);
                $info['class'] = sprintf($this->getRemoteCallLinkTemplate(),
                    $url,
                    $info['class']
                );
            }
        }

        $info['module'] = $block->getModuleName();

        if ($block instanceof Mage_Cms_Block_Block) {
            $info['cms-blockId'] = $block->getBlockId();
        }
        if ($block instanceof Mage_Cms_Block_Page) {
            $info['cms-pageId'] = $block->getPage()->getIdentifier();
        }
        $templateFile = $block->getTemplateFile();
        if ($templateFile) {
            $info['template'] = $templateFile;

            if ($this->getRemoteCallEnabled()) {
                $url = sprintf($this->getRemoteCallUrlTemplate(), Mage::getBaseDir('design') . DS . $templateFile, 0);
                $info['template'] = sprintf($this->getRemoteCallLinkTemplate(),
                    $url,
                    $templateFile
                );
            }

        }

        // cache information
        $info['cache-status'] = self::TYPE_NOTCACHED;

        $cacheLifeTime = $block->getCacheLifetime();
        if (!is_null($cacheLifeTime)) {

            $info['cache-lifetime'] = (intval($cacheLifeTime) == 0) ? 'forever' : intval($cacheLifeTime) . ' sec';
            $info['cache-key'] = $block->getCacheKey();
            $info['cache-key-info'] = is_array($block->getCacheKeyInfo())
                        ? implode(', ', $block->getCacheKeyInfo())
                        : $block->getCacheKeyInfo()
                    ;
            $info['tags'] = implode(',', $block->getCacheTags());

            $info['cache-status'] = self::TYPE_CACHED;
        } elseif ($this->isWithinCachedBlock($block)) {
            $info['cache-status'] = self::TYPE_IMPLICITLYCACHED; // not cached, but within cached
        }

        $info['methods'] = $this->getClassMethods(get_class($block));

        return $info;
    }

    /**
     * Check if remote call is enabled in configuration
     *
     * @return bool
     */
    public function getRemoteCallEnabled() {
        if (is_null($this->remoteCallEnabled)) {
            $this->remoteCallEnabled = Mage::getStoreConfigFlag('dev/aoe_templatehints/enablePhpstormRemoteCall');
        }
        return $this->remoteCallEnabled;
    }

    /**
     * Get remote call url template
     *
     * @return mixed
     */
    public function getRemoteCallUrlTemplate() {
        if (is_null($this->remoteCallUrlTemplate)) {
            $this->remoteCallUrlTemplate = Mage::getStoreConfig('dev/aoe_templatehints/remoteCallUrlTemplate');
        }
        return $this->remoteCallUrlTemplate;
    }

    /**
     * Get link template for remote calls
     *
     * @return string
     */
    public function getRemoteCallLinkTemplate() {
        return '%s';
    }

    /**
     * Get block methods (incl. methods of parent classes)
     *
     * @param string $className
     * @return array
     */
    public function getClassMethods($className) {

        if (!isset($this->classMethodCache[$className])) {

            $info = array();

            $rClass = new ReflectionClass($className);

            $currentClass = $rClass;
            $currentClassName = $currentClass->getName();
            $currentMethods = get_class_methods($currentClass->getName());
            $parentClass = $currentClass->getParentClass();

            $level = 1;
            while ($parentClass && $level < 6) {

                $parentClassName = $parentClass->getName();

                if (!in_array($currentClassName, array('Mage_Core_Block_Abstract', 'Mage_Core_Block_Template'))) {
                    $parentMethods = get_class_methods($parentClassName);
                    $tmp = array_diff($currentMethods, $parentMethods);
                    $info[$currentClassName] = array();

                    // render methods to "methodName($paramter1, $parameter2, ...)"
                    foreach ($tmp as $methodName) {

                        $parameters = array();
                        foreach ($currentClass->getMethod($methodName)->getParameters() as $parameter) { /* @var $parameter ReflectionParameter */
                            $parameters[] = '$'. $parameter->getName();
                        }

                        if (count($parameters) > 3) {
                            $parameters = array_slice($parameters, 0, 2);
                            $parameters[] = '...';
                        }

                        $info[$currentClassName][] = $methodName . '(' . implode(', ', $parameters) . ')';
                    }
                } else {
                    $info[$currentClassName] = array('(skipping)');
                    $parentMethods = array();
                }

                $level++;

                $currentClass = $parentClass;
                $currentClassName = $currentClass->getName();
                $currentMethods = $parentMethods;
                $parentClass = $currentClass->getParentClass();
            }

            $this->classMethodCache[$className] = $info;
        }

        return $this->classMethodCache[$className];
    }

    /**
     * Get path information of a block
     *
     * @param Mage_Core_Block_Abstract $block
     * @return string
     * @author Fabrizio Branca
     * @since 2011-01-24
     */
    public function getBlockPath(Mage_Core_Block_Abstract $block) {
        $blockPath = array();
        $step = $block->getParentBlock();
	$i = 0;
        while ($i++ < 20 && $step instanceof Mage_Core_Block_Abstract) {
            $blockPath[] = $this->getBlockInfo($step, false);
            $step = $step->getParentBlock();
        }
        return $blockPath;
    }

    /**
     * Check if a block is within another one that is cached
     *
     * @param Mage_Core_Block_Abstract $block
     * @return bool
     * @author Fabrizio Branca
     * @since 2011-01-24
     */
    public function isWithinCachedBlock(Mage_Core_Block_Abstract $block) {
        $step = $block;
	$i = 0;
        while ($i++ < 20 && $step instanceof Mage_Core_Block_Abstract) {
            if (!is_null($step->getCacheLifetime())) {
                return true;
            }
            $step = $step->getParentBlock();
        }
        return false;
    }

    /**
     * Render title
     *
     * @param array $info
     * @return string
     */
    public function renderTitle(array $info) {
        $title = $info['name'];
        if ($info['name'] != $info['alias'] && $info['alias']) {
            $title .= ' (alias: ' . $info['alias'] . ')';
        }
        return $title;
    }

}