<commit_before><?php
/**
 * JSONAPI extension for Bolt.
 *
 * @author <NAME> <<EMAIL>>
 * @author <NAME> <<EMAIL>>
 * @author <NAME> <<EMAIL>>
 */

namespace JSONAPI;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class Extension extends \Bolt\BaseExtension
{

    private $base = '/json';
    private $basePath;
    private $paginationNumberKey = 'page'; // todo: page[number]
    private $paginationSizeKey = 'limit';  // todo: page[size]

    public function getName()
    {
        return "JSONAPI";
    }

    //
    // Examples:
    //
    // Basic:
    //     /{contenttype}
    //     /{contenttype}/{id}
    //     /{contenttype}?page={x}&limit={y}
    //     /{contenttype}?include={relationship1,relationship2}
    //     /{contenttype}?fields[{contenttype1}]={field1,field2} -- Note: taxonomies and relationships are fields as well.
    //
    // Relationships:
    //     /{contenttype}/{id}/relationships/{relationship} -- Note: this "relationship" is useful for handling the relationship between two instances.
    //     /{contenttype}/{id}/{relationship} -- Note: this "related resource" is useful for fetching the related data. These are not "self" links.
    //
    // Filters:
    //     /{contenttype}?filter[{contenttype1}]={value1,value2}
    //     /{contenttype}?filter[{field1}]={value1,value2}&filter[{field2}]={value3,value4} -- For Bolt, this seems the most logical (similar to a `where` clause)
    //
    // Search:
    //     /{contenttype}?q={query} -- search within a contenttype
    //     /search/q={query} -- search in all contenttypes
    //
    // sources: http://jsonapi.org/examples/
    //          http://jsonapi.org/recommendations/
    //
    public function initialize()
    {
        if(isset($this->config['base'])) {
            $this->base = $this->config['base'];
        }
        $this->basePath = $this->app['paths']['canonical'] . $this->base;

        $this->app->get($this->base."/search", [$this, 'search'])
                  ->bind('jsonapi_search_mixed');
        $this->app->get($this->base."/{contenttype}/search", [$this, 'search'])
                  ->bind('jsonapi_search');
        $this->app->get($this->base."/{contenttype}/{slug}/{relatedContenttype}", [$this, 'json'])
                  ->value('relatedContenttype', null)
                  ->assert('slug', '[a-zA-Z0-9_\-]+')
                  ->bind('jsonapi');
        $this->app->get($this->base."/{contenttype}", [$this, 'json_list'])
                  ->bind('jsonapi_list');
    }

    public function json_list(Request $request, $contenttype)
    {
        $this->request = $request;

        if (!array_key_exists($contenttype, $this->config['contenttypes'])) {
            return $this->notfound();
        }
        $options = [];
        // if ($limit = $request->get('page')['size']) { // breaks things in src/Storage.php at executeGetContentQueries
        if ($limit = $request->get($this->paginationSizeKey)) {
            $limit = intval($limit);
            if ($limit >= 1) {
                $options['limit'] = $limit;
            }
        }
        // if ($page = $request->get('page')['number']) { // breaks things in src/Storage.php at executeGetContentQueries
        if ($page = $request->get($this->paginationNumberKey)) {
            $page = intval($page);
            if ($page >= 1) {
                $options['page'] = $page;
            }
        }
        if ($order = $request->get('order')) {
            if (!preg_match('/^([a-zA-Z][a-zA-Z0-9_\\-]*)\\s*(ASC|DESC)?$/', $order, $matches)) {
                $this->invalidrequest();
            }
            $options['order'] = $order;
        }

        // todo: handle "include"
        // $included = [];
        // if ($include = $request->get('include')) {
        //     $where = [];
        // }

        // todo: handle "fields[]"
        // if ($fields = $request->get('fields')) {
        //     foreach($fields as $key => $value) {
        //         $fields[$key] = explode($value);
        //     }
        // }

        // Enable pagination
        $options['paging'] = true;
        $pager = [];
        $where = [];

        // Use the where clause defined in the contenttype config
        if (isset($this->config['contenttypes'][$contenttype]['where-clause'])) {
            $where = $this->config['contenttypes'][$contenttype]['where-clause'];
        }

        $items = $this->app['storage']->getContent($contenttype, $options, $pager, $where);

        // If we don't have any items, this can mean one of two things: either
        // the content type does not exist (in which case we'll get a non-array
        // response), or it exists, but no content has been added yet.
        if (!is_array($items)) {
            throw new \Exception("Configuration error: $contenttype is configured as a JSON end-point, but doesn't exist as a content type.");
        }
        if (empty($items)) {
            $items = [];
        }

        $meta = [
            "count" => count($items),
            "total" => intval($pager['count'])
        ];

        $items = array_values($items);
        $items = array_map([$this, 'clean_list_item'], $items); // todo: relationships are lost

        return $this->response([
            'links' => $this->makeLinks($contenttype, $pager['current'], intval($pager['totalpages']), $limit),
            'meta' => $meta,
            'data' => $items,
            'related' => [],
            'jsonapi' => $this->makeJsonapi(),
            // 'included' => $included // included related objects
        ]);

    }

    public function json(Request $request, $contenttype, $slug, $relatedContenttype)
    {
        $this->request = $request;

        if (!array_key_exists($contenttype, $this->config['contenttypes'])) {
            return $this->notfound();
        }

        $item = $this->app['storage']->getContent("$contenttype/$slug");
        if (!$item) {
            return $this->notfound();
        }

        // If a related entity name is given, we fetch its content instead
        if ($relatedContenttype !== null)
        {
            $items = $item->related($relatedContenttype);
            if (!$items) {
                return $this->notfound();
            }
            $items = array_map([$this, 'clean_list_item'], $items);
            $response = $this->response([$relatedContenttype => $items]);
<commit_msg>Enable filters.<commit_after><?php
/**
 * JSONAPI extension for Bolt.
 *
 * @author <NAME> <<EMAIL>>
 * @author <NAME> <<EMAIL>>
 * @author <NAME> <<EMAIL>>
 */

namespace JSONAPI;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class Extension extends \Bolt\BaseExtension
{

    private $base = '/json';
    private $basePath;
    private $paginationNumberKey = 'page'; // todo: page[number]
    private $paginationSizeKey = 'limit';  // todo: page[size]

    public function getName()
    {
        return "JSONAPI";
    }

    //
    // Examples:
    //
    // Basic:
    //     /{contenttype}
    //     /{contenttype}/{id}
    //     /{contenttype}?page={x}&limit={y}
    //     /{contenttype}?include={relationship1,relationship2}
    //     /{contenttype}?fields[{contenttype1}]={field1,field2} -- Note: taxonomies and relationships are fields as well.
    //
    // Relationships:
    //     /{contenttype}/{id}/relationships/{relationship} -- Note: this "relationship" is useful for handling the relationship between two instances.
    //     /{contenttype}/{id}/{relationship} -- Note: this "related resource" is useful for fetching the related data. These are not "self" links.
    //
    // Filters:
    //     /{contenttype}?filter[{contenttype1}]={value1,value2}
    //     /{contenttype}?filter[{field1}]={value1,value2}&filter[{field2}]={value3,value4} -- For Bolt, this seems the most logical (similar to a `where` clause)
    //
    // Search:
    //     /{contenttype}?q={query} -- search within a contenttype
    //     /search/q={query} -- search in all contenttypes
    //
    // sources: http://jsonapi.org/examples/
    //          http://jsonapi.org/recommendations/
    //
    public function initialize()
    {
        if(isset($this->config['base'])) {
            $this->base = $this->config['base'];
        }
        $this->basePath = $this->app['paths']['canonical'] . $this->base;

        $this->app->get($this->base."/search", [$this, 'search'])
                  ->bind('jsonapi_search_mixed');
        $this->app->get($this->base."/{contenttype}/search", [$this, 'search'])
                  ->bind('jsonapi_search');
        $this->app->get($this->base."/{contenttype}/{slug}/{relatedContenttype}", [$this, 'json'])
                  ->value('relatedContenttype', null)
                  ->assert('slug', '[a-zA-Z0-9_\-]+')
                  ->bind('jsonapi');
        $this->app->get($this->base."/{contenttype}", [$this, 'json_list'])
                  ->bind('jsonapi_list');
    }

    public function json_list(Request $request, $contenttype)
    {
        $this->request = $request;

        if (!array_key_exists($contenttype, $this->config['contenttypes'])) {
            return $this->notfound();
        }
        $options = [];
        // if ($limit = $request->get('page')['size']) { // breaks things in src/Storage.php at executeGetContentQueries
        if ($limit = $request->get($this->paginationSizeKey)) {
            $limit = intval($limit);
            if ($limit >= 1) {
                $options['limit'] = $limit;
            }
        }
        // if ($page = $request->get('page')['number']) { // breaks things in src/Storage.php at executeGetContentQueries
        if ($page = $request->get($this->paginationNumberKey)) {
            $page = intval($page);
            if ($page >= 1) {
                $options['page'] = $page;
            }
        }
        if ($order = $request->get('order')) {
            if (!preg_match('/^([a-zA-Z][a-zA-Z0-9_\\-]*)\\s*(ASC|DESC)?$/', $order, $matches)) {
                $this->invalidrequest();
            }
            $options['order'] = $order;
        }

        // Enable pagination
        $options['paging'] = true;
        $pager = [];
        $where = [];

        // todo: handle "include"
        // $included = [];
        // if ($include = $request->get('include')) {
        //     $where = [];
        // }

        // todo: handle "fields[]"
        // if ($fields = $request->get('fields')) {
        //     foreach($fields as $key => $value) {
        //         $fields[$key] = explode(',', $value);
        //     }
        // }

        // Use the where clause defined in the contenttype config.
        if (isset($this->config['contenttypes'][$contenttype]['where-clause'])) {
            $where = $this->config['contenttypes'][$contenttype]['where-clause'];
        }

        // Handle "filter[]"
        $basekeys = \Bolt\Content::getBaseColumns();
        $definedkeys = array_keys($this->app['config']->get("contenttypes/$contenttype/fields"));
        $validkeys = array_merge($basekeys, $definedkeys);

        if ($filters = $request->get('filter')) {
            foreach($filters as $key => $value) {
                if (!in_array($key, $validkeys)) {
                    return $this->invalidrequest();
                }
                $where[$key] = str_replace(',', ' || ', $value);
            }
        }

        $items = $this->app['storage']->getContent($contenttype, $options, $pager, $where);

        // If we don't have any items, this can mean one of two things: either
        // the content type does not exist (in which case we'll get a non-array
        // response), or it exists, but no content has been added yet.
        if (!is_array($items)) {
            throw new \Exception("Configuration error: $contenttype is configured as a JSON end-point, but doesn't exist as a content type.");
        }
        if (empty($items)) {
            $items = [];
        }

        $meta = [
            "count" => count($items),
            "total" => intval($pager['count'])
        ];

        $items = array_values($items);
        $items = array_map([$this, 'clean_list_item'], $items); // todo: relationships are lost

        return $this->response([
            'links' => $this->makeLinks($contenttype, $pager['current'], intval($pager['totalpages']), $limit),
            'meta' => $meta,
            'data' => $items,
            'related' => [],
            'jsonapi' => $this->makeJsonapi(),
            // 'included' => $included // included related objects
        ]);

    }

    public function json(Request $request, $contenttype, $slug, $relatedContenttype)
    {
        $this->request = $request;

        if (!array_key_exists($contenttype, $this->config['contenttypes'])) {
            return $this->notfound();
        }

        $item = $this->app['storage']->getContent("$contenttype/$slug");
        if (!$item) {
            return $this->notfound();
        }

        // If a related entity name is given, we fetch its content instead
        if ($relatedContenttype !== null)
        {
            $items = $item->related($relatedContenttype);
            if (!$items) {
                return $this->notfound();
            }
            $items = array_map([$this, 'clean_list_item'], $items);
            $response = $this->response([
                'data' => $items
            ]);

{'fraction_non_alphanumeric': 0.18083841236806897, 'fraction_numerical': 0.002973093503790694, 'mean_word_length': 2.055870997047468, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 156, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '2792445', 'n_tokens_mistral': 3954, 'n_tokens_neox': 3800, 'n_words': 819}