<commit_before><?php

/**
 * This file is part of the Superdesk Web Publisher Content Bundle.
 *
 * Copyright 2015 Sourcefabric z.u. and contributors.
 *
 * For the full copyright and license information, please see the
 * AUTHORS and LICENSE files distributed with this source code.
 *
 * @copyright 2015 Sourcefabric z.ú.
 * @license http://www.superdesk.org/license
 */
namespace SWP\Bundle\ContentBundle\Loader;

use PHPCR\Query\QueryInterface;
use SWP\Component\TemplatesSystem\Gimme\Loader\LoaderInterface;
use SWP\Component\TemplatesSystem\Gimme\Meta\Meta;
use Symfony\Component\Yaml\Parser;
use Symfony\Cmf\Bundle\CoreBundle\PublishWorkflow\PublishWorkflowChecker;
use Doctrine\ODM\PHPCR\DocumentManager;
use Doctrine\Common\Cache\CacheProvider;
use SWP\Component\MultiTenancy\PathBuilder\TenantAwarePathBuilderInterface;

class ArticleLoader implements LoaderInterface
{
    /**
     * @var PublishWorkflowChecker
     */
    protected $publishWorkflowChecker;

    /**
     * @var DocumentManager
     */
    protected $dm;

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

    /**
     * @var CacheProvider
     */
    protected $metadataCache;

    /**
     * @var TenantAwarePathBuilderInterface
     */
    protected $pathBuilder;

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

    public function __construct(
        PublishWorkflowChecker $publishWorkflowChecker,
        DocumentManager $dm,
        $configurationPath,
        CacheProvider $metadataCache,
        TenantAwarePathBuilderInterface $pathBuilder,
        $routeBasepaths
    ) {
        $this->publishWorkflowChecker = $publishWorkflowChecker;
        $this->dm = $dm;
        $this->configurationPath = $configurationPath.'/Resources/meta/article.yml';
        $this->metadataCache = $metadataCache;
        $this->pathBuilder = $pathBuilder;
        $this->routeBasepaths = $routeBasepaths;
    }

    /**
     * Load meta object by provided type and parameters.
     *
     * @MetaLoaderDoc(
     *     description="Article Loader loads articles from Content Repository",
     *     parameters={
     *         contentPath="SINGLE|required content path",
     *         slug="SINGLE|required content slug",
     *         pageName="COLLECTiON|name of Page for required articles"
     *     }
     * )
     *
     * @param string $type         object type
     * @param array  $parameters   parameters needed to load required object type
     * @param int    $responseType response type: single meta (LoaderInterface::SINGLE) or collection of metas (LoaderInterface::COLLECTION)
     *
     * @return Meta|Meta[]|bool false if meta cannot be loaded, a Meta instance otherwise
     */
    public function load($type, $parameters, $responseType = LoaderInterface::SINGLE)
    {
        $article = null;
        if (empty($parameters)) {
            $parameters = [];
        }

        // Cache meta configuration
        $cacheKey = md5($this->configurationPath);
        if (!$this->metadataCache->contains($cacheKey)) {
            if (!is_readable($this->configurationPath)) {
                throw new \InvalidArgumentException('Configuration file is not readable for parser');
            }
            $yaml = new Parser();
            $configuration = $yaml->parse(file_get_contents($this->configurationPath));
            $this->metadataCache->save($cacheKey, $configuration);
        } else {
            $configuration = $this->metadataCache->fetch($cacheKey);
        }

        if ($responseType === LoaderInterface::SINGLE) {
            if (array_key_exists('contentPath', $parameters)) {
                $article = $this->dm->find('SWP\Bundle\ContentBundle\Doctrine\ODM\PHPCR\Article', $parameters['contentPath']);
            } elseif (array_key_exists('article', $parameters)) {
                $article = $parameters['article'];
            } elseif (array_key_exists('slug', $parameters)) {
                $article = $this->dm->getRepository('SWP\Bundle\ContentBundle\Doctrine\ODM\PHPCR\Article')
                    ->findOneBy(['slug' => $parameters['slug']]);
            }

            return $this->getArticleMeta($configuration, $article);
        } elseif ($responseType === LoaderInterface::COLLECTION) {
            if (array_key_exists('route', $parameters)) {
                $route = $this->dm->find(null, $this->pathBuilder->build(
                    $this->routeBasepaths[0].$parameters['route']
                ));

                if ($route) {
                    $node = $this->dm->getNodeForDocument($route);
                    $identifier = $node->getIdentifier();

                    $queryStr = sprintf("SELECT * FROM [nt:unstructured] as S WHERE S.phpcr:class='SWP\Bundle\ContentBundle\Doctrine\ODM\PHPCR\Article' AND S.route=%s", $identifier);

                    if (isset($parameters['order'])) {
                        $order = $parameters['order'];
                        if (!is_array($order) || count($order) !== 2 || (strtoupper($order[1]) != 'ASC' && strtoupper($order[1]) != 'DESC')) {
                            throw new \Exception('Order filter must have two parameters with second one asc or desc, e.g. order(id, desc)');
                        }
                        if ($order[0] === 'id') {
                            $order[0] = 'jcr:uuid';
                        }
                        $queryStr .= ' ORDER BY S.'.$order[0].' '.$order[1];
                    }

                    $query = $this->dm->createPhpcrQuery($queryStr, QueryInterface::JCR_SQL2);

                    if (isset($parameters['limit'])) {
                        $query->setLimit($parameters['limit']);
                    }

                    if (isset($parameters['start'])) {
                        $query->setOffset($parameters['start']);
                    }

                    $articles = $this->dm->getDocumentsByPhpcrQuery($query);

                    $metas = [];
                    foreach ($articles as $article) {
                        $articleMeta = $this->getArticleMeta($configuration, $article);
                        if ($articleMeta) {
                            $metas[] = $articleMeta;
                        }
                    }

                    return $metas;
                }
            }
        }

        return false;
    }

    /**
     * Checks if Loader supports provided type.
     *
     * @param string $type
     *
     * @return bool
     */
    public function isSupported($type)
    {
        return in_array($type, ['articles', 'article']);
    }

    private function getArticleMeta($configuration, $article)
    {
        if (!is_null($article) && $this->publishWorkflowChecker->isGranted(PublishWorkflowChecker::VIEW_ATTRIBUTE, $article)) {
            return new Meta($configuration, $article);
        }

        return false;
    }
}
<commit_msg>Prevented sql injection in ArticleLoader (#114)<commit_after><?php

/**
 * This file is part of the Superdesk Web Publisher Content Bundle.
 *
 * Copyright 2015 Sourcefabric z.u. and contributors.
 *
 * For the full copyright and license information, please see the
 * AUTHORS and LICENSE files distributed with this source code.
 *
 * @copyright 2015 Sourcefabric z.ú.
 * @license http://www.superdesk.org/license
 */
namespace SWP\Bundle\ContentBundle\Loader;

use PHPCR\Query\QueryInterface;
use SWP\Component\TemplatesSystem\Gimme\Loader\LoaderInterface;
use SWP\Component\TemplatesSystem\Gimme\Meta\Meta;
use Symfony\Component\Yaml\Parser;
use Symfony\Cmf\Bundle\CoreBundle\PublishWorkflow\PublishWorkflowChecker;
use Doctrine\ODM\PHPCR\DocumentManager;
use Doctrine\Common\Cache\CacheProvider;
use SWP\Component\MultiTenancy\PathBuilder\TenantAwarePathBuilderInterface;

class ArticleLoader implements LoaderInterface
{
    /**
     * @var PublishWorkflowChecker
     */
    protected $publishWorkflowChecker;

    /**
     * @var DocumentManager
     */
    protected $dm;

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

    /**
     * @var CacheProvider
     */
    protected $metadataCache;

    /**
     * @var TenantAwarePathBuilderInterface
     */
    protected $pathBuilder;

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

    public function __construct(
        PublishWorkflowChecker $publishWorkflowChecker,
        DocumentManager $dm,
        $configurationPath,
        CacheProvider $metadataCache,
        TenantAwarePathBuilderInterface $pathBuilder,
        $routeBasepaths
    ) {
        $this->publishWorkflowChecker = $publishWorkflowChecker;
        $this->dm = $dm;
        $this->configurationPath = $configurationPath.'/Resources/meta/article.yml';
        $this->metadataCache = $metadataCache;
        $this->pathBuilder = $pathBuilder;
        $this->routeBasepaths = $routeBasepaths;
    }

    /**
     * Load meta object by provided type and parameters.
     *
     * @MetaLoaderDoc(
     *     description="Article Loader loads articles from Content Repository",
     *     parameters={
     *         contentPath="SINGLE|required content path",
     *         slug="SINGLE|required content slug",
     *         pageName="COLLECTiON|name of Page for required articles"
     *     }
     * )
     *
     * @param string $type         object type
     * @param array  $parameters   parameters needed to load required object type
     * @param int    $responseType response type: single meta (LoaderInterface::SINGLE) or collection of metas (LoaderInterface::COLLECTION)
     *
     * @return Meta|Meta[]|bool false if meta cannot be loaded, a Meta instance otherwise
     */
    public function load($type, $parameters, $responseType = LoaderInterface::SINGLE)
    {
        $article = null;
        if (empty($parameters)) {
            $parameters = [];
        }

        // Cache meta configuration
        $cacheKey = md5($this->configurationPath);
        if (!$this->metadataCache->contains($cacheKey)) {
            if (!is_readable($this->configurationPath)) {
                throw new \InvalidArgumentException('Configuration file is not readable for parser');
            }
            $yaml = new Parser();
            $configuration = $yaml->parse(file_get_contents($this->configurationPath));
            $this->metadataCache->save($cacheKey, $configuration);
        } else {
            $configuration = $this->metadataCache->fetch($cacheKey);
        }

        if ($responseType === LoaderInterface::SINGLE) {
            if (array_key_exists('contentPath', $parameters)) {
                $article = $this->dm->find('SWP\Bundle\ContentBundle\Doctrine\ODM\PHPCR\Article', $parameters['contentPath']);
            } elseif (array_key_exists('article', $parameters)) {
                $article = $parameters['article'];
            } elseif (array_key_exists('slug', $parameters)) {
                $article = $this->dm->getRepository('SWP\Bundle\ContentBundle\Doctrine\ODM\PHPCR\Article')
                    ->findOneBy(['slug' => $parameters['slug']]);
            }

            return $this->getArticleMeta($configuration, $article);
        } elseif ($responseType === LoaderInterface::COLLECTION) {
            if (array_key_exists('route', $parameters)) {
                $route = $this->dm->find(null, $this->pathBuilder->build(
                    $this->routeBasepaths[0].$parameters['route']
                ));

                if ($route) {
                    $node = $this->dm->getNodeForDocument($route);
                    $identifier = $node->getIdentifier();

                    $queryStr = sprintf("SELECT * FROM [nt:unstructured] as S WHERE S.phpcr:class='SWP\Bundle\ContentBundle\Doctrine\ODM\PHPCR\Article' AND S.route=%s", $identifier);

                    if (isset($parameters['order'])) {
                        $order = $parameters['order'];
                        if (!is_array($order) || count($order) !== 2 || (strtoupper($order[1]) != 'ASC' && strtoupper($order[1]) != 'DESC')) {
                            throw new \Exception('Order filter must have two parameters with second one asc or desc, e.g. order(id, desc)');
                        }
                        if ($order[0] === 'id') {
                            $order[0] = 'jcr:uuid';
                        } else {
                            $order[0] = mysqli_real_escape_string($order[0]);
                        }
                        $queryStr .= sprintf(' ORDER BY S.%s %s', $order[0], $order[1]);
                    }

                    $query = $this->dm->createPhpcrQuery($queryStr, QueryInterface::JCR_SQL2);

                    if (isset($parameters['limit'])) {
                        $query->setLimit($parameters['limit']);
                    }

                    if (isset($parameters['start'])) {
                        $query->setOffset($parameters['start']);
                    }

                    $articles = $this->dm->getDocumentsByPhpcrQuery($query);

                    $metas = [];
                    foreach ($articles as $article) {
                        $articleMeta = $this->getArticleMeta($configuration, $article);
                        if ($articleMeta) {
                            $metas[] = $articleMeta;
                        }
                    }

                    return $metas;
                }
            }
        }

        return false;
    }

    /**
     * Checks if Loader supports provided type.
     *
     * @param string $type
     *
     * @return bool
     */
    public function isSupported($type)
    {
        return in_array($type, ['articles', 'article']);
    }

    private function getArticleMeta($configuration, $article)
    {
        if (!is_null($article) && $this->publishWorkflowChecker->isGranted(PublishWorkflowChecker::VIEW_ATTRIBUTE, $article)) {
            return new Meta($configuration, $article);
        }

        return false;
    }
}

{'fraction_non_alphanumeric': 0.12454265011837291, 'fraction_numerical': 0.0029413874739938305, 'mean_word_length': 2.0590300636383585, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 83, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6198034', 'n_tokens_mistral': 3741, 'n_tokens_neox': 3596, 'n_words': 755}