Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 133
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
GenerisTreeFactory
0.00% covered (danger)
0.00%
0 / 133
0.00% covered (danger)
0.00%
0 / 11
1482
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 buildTree
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 classToNode
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
56
 buildChildNodes
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
30
 buildClassNode
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 getSignatureGenerator
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getQueryBuilder
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
132
 getSearchService
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSubClasses
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 mustFilterTranslations
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
12
 getFeatureFlagChecker
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/*
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2002-2008 (original work) Public Research Centre Henri Tudor & University of Luxembourg
19 *                         (under the project TAO & TAO2);
20 *               2008-2010 (update and modification) Deutsche Institut für Internationale Pädagogische Forschung
21 *                         (under the project TAO-TRANSFER);
22 *               2009-2012 (update and modification) Public Research Centre Henri Tudor
23 *                         (under the project TAO-SUSTAIN & TAO-DEV);
24 *               2017     (update and modification) Open Assessment Technologies SA (under the project TAO-PRODUCT);
25 *
26 *
27 * Factory to prepare the ontology data for the
28 * javascript generis tree
29 *
30 * @access public
31 * @author Joel Bout, <joel@taotesting.com>
32 * @package tao
33 */
34
35namespace oat\tao\model;
36
37use core_kernel_classes_Class;
38use core_kernel_classes_Resource;
39use oat\generis\model\kernel\persistence\smoothsql\search\filter\Filter;
40use oat\generis\model\OntologyRdfs;
41use oat\oatbox\service\ServiceManager;
42use oat\tao\helpers\TreeHelper;
43use oat\tao\model\featureFlag\FeatureFlagChecker;
44use oat\tao\model\featureFlag\FeatureFlagCheckerInterface;
45use oat\tao\model\security\SignatureGenerator;
46use tao_helpers_Uri;
47use oat\generis\model\kernel\persistence\smoothsql\search\ComplexSearchService;
48use oat\search\helper\SupportedOperatorHelper;
49use oat\generis\model\OntologyAwareTrait;
50
51class GenerisTreeFactory
52{
53    use OntologyAwareTrait;
54
55    /**
56     * All instances of those classes loaded, independent of current limit ( Contain uris only )
57     * @var array
58     */
59    private $browsableTypes = [];
60
61    /**
62     * @var int
63     */
64    private $limit;
65
66    /**
67      * @var int
68      */
69    private $offset;
70
71    /**
72      * @var array
73      */
74    private $openNodes = [];
75
76    /**
77      * @var bool
78      */
79    private $showResources;
80
81    /**
82      * @var array contains filters to apply to searchInstances
83      */
84    private $propertyFilter = [];
85
86    /**
87     * @var array
88     */
89    private $optionsFilter = [];
90
91    /** @var array  */
92    private $extraProperties = [];
93    /**
94     * @param boolean $showResources If `true` resources will be represented in thee. Otherwise only classes.
95     * @param array $openNodes Class uris for which children array should be build as well
96     * @param int $limit Limit of resources to be shown in one class
97     * @param int $offset Offset for resources in one class
98     * @param array $resourceUrisToShow All siblings of this resources will be loaded, independent of current limit
99     * @param array $propertyFilter Additional property filters to apply to the tree
100     * @param array $optionsFilter
101     * @param array $extraProperties
102     */
103    public function __construct(
104        $showResources,
105        array $openNodes = [],
106        $limit = 10,
107        $offset = 0,
108        array $resourceUrisToShow = [],
109        array $propertyFilter = [],
110        array $optionsFilter = [],
111        array $extraProperties = []
112    ) {
113        $this->limit          = (int) $limit;
114        $this->offset         = (int) $offset;
115        $this->openNodes      = $openNodes;
116        $this->showResources  = $showResources;
117        $this->propertyFilter = $propertyFilter;
118        $this->optionsFilter  = $optionsFilter;
119        $this->extraProperties = $extraProperties;
120
121        $types = [];
122        foreach ($resourceUrisToShow as $uri) {
123            $resource = new core_kernel_classes_Resource($uri);
124            $types[]  = $resource->getTypes();
125        }
126
127        if ($types) {
128            $this->browsableTypes = array_keys(call_user_func_array('array_merge', $types));
129        }
130    }
131
132    /**
133     * builds the data for a generis tree
134     * @param core_kernel_classes_Class $class
135     * @return array
136     */
137    public function buildTree(core_kernel_classes_Class $class)
138    {
139        return $this->classToNode($class, null);
140    }
141
142    /**
143     * Builds a class node including it's content
144     *
145     * @param core_kernel_classes_Class $class
146     * @param core_kernel_classes_Class $parent
147     * @return array
148     * @throws
149     */
150    private function classToNode(core_kernel_classes_Class $class, core_kernel_classes_Class $parent = null)
151    {
152        $returnValue = $this->buildClassNode($class, $parent);
153
154        // allow the class to be opened if it contains either instances or subclasses
155        $subclasses = $this->getSubClasses($class);
156
157        if ($this->showResources) {
158            $options = array_merge(['recursive' => false], $this->optionsFilter);
159            $queryBuilder = $this->getQueryBuilder($class, $this->propertyFilter, $options);
160            $search = $this->getSearchService();
161            $search->setLanguage($queryBuilder, \common_session_SessionManager::getSession()->getDataLanguage());
162            $instancesCount = $search->getGateway()->count($queryBuilder);
163
164            if ($instancesCount > 0 || count($subclasses) > 0) {
165                if (in_array($class->getUri(), $this->openNodes)) {
166                    $returnValue['state']   = 'open';
167                    $returnValue['children'] = $this->buildChildNodes($class, $subclasses);
168                } else {
169                    $returnValue['state']   = 'closed';
170                }
171
172                // only show the resources count if we allow resources to be viewed
173                $returnValue['count'] = $instancesCount;
174            }
175        } else {
176            if (count($subclasses) > 0) {
177                if (in_array($class->getUri(), $this->openNodes)) {
178                    $returnValue['state'] = 'open';
179                    $returnValue['children'] = $this->buildChildNodes($class, $subclasses);
180                } else {
181                    $returnValue['state'] = 'closed';
182                }
183
184                $returnValue['count'] = 0;
185            }
186        }
187        return $returnValue;
188    }
189
190    /**
191     * Builds the content of a class node including it's content
192     *
193     * @param core_kernel_classes_Class $class
194     * @param core_kernel_classes_Class[] $subclasses
195     * @return array
196     * @throws
197     */
198    private function buildChildNodes(core_kernel_classes_Class $class, $subclasses)
199    {
200        $children = [];
201
202        // subclasses
203        foreach ($subclasses as $subclass) {
204            $children[] = $this->classToNode($subclass, $class);
205        }
206        // resources
207        if ($this->showResources) {
208            $limit = $this->limit;
209
210            if (in_array($class->getUri(), $this->browsableTypes)) {
211                $limit = 0;
212            }
213
214            $options = array_merge([
215                'limit'     => $limit,
216                'offset'    => $this->offset,
217                'recursive' => false,
218                'order'     => [OntologyRdfs::RDFS_LABEL => 'asc'],
219            ], $this->optionsFilter);
220
221            $queryBuilder = $this->getQueryBuilder($class, $this->propertyFilter, $options);
222            $search = $this->getSearchService();
223            $search->setLanguage($queryBuilder, \common_session_SessionManager::getSession()->getDataLanguage());
224            $searchResult = $search->getGateway()->search($queryBuilder);
225            foreach ($searchResult as $instance) {
226                $children[] = TreeHelper::buildResourceNode($instance, $class, $this->extraProperties);
227            }
228        }
229        return $children;
230    }
231
232    /**
233     * generis tree representation of a class node
234     * without it's content
235     *
236     * @param core_kernel_classes_Class $class
237     * @param core_kernel_classes_Class $parent
238     *
239     * @return array
240     */
241    private function buildClassNode(core_kernel_classes_Class $class, core_kernel_classes_Class $parent = null)
242    {
243        $label = $class->getLabel();
244        $label = empty($label) ? __('no label') : $label;
245
246        return [
247            'data'  => _dh($label),
248            'type'  => 'class',
249            'attributes' => [
250                'id' => tao_helpers_Uri::encode($class->getUri()),
251                'class' => 'node-class',
252                'data-uri' => $class->getUri(),
253                'data-classUri' => is_null($parent) ? null : $parent->getUri(),
254                'data-signature' => $this->getSignatureGenerator()->generate($class->getUri()),
255            ]
256        ];
257    }
258
259    /**
260     * @return SignatureGenerator
261     */
262    private function getSignatureGenerator()
263    {
264        return ServiceManager::getServiceManager()->get(SignatureGenerator::class);
265    }
266
267    /**
268     * @param $class
269     * @param $propertyFilter
270     * @param $options
271     * @return \oat\search\QueryBuilder
272     * @throws
273     */
274    private function getQueryBuilder($class, $propertyFilter, $options)
275    {
276        $search = $this->getSearchService();
277        $queryBuilder = $search->query();
278        $search->setLanguage($queryBuilder, \common_session_SessionManager::getSession()->getDataLanguage());
279        $query = $search->searchType($queryBuilder, $class->getUri(), $options['recursive']);
280
281        if ($this->mustFilterTranslations($class)) {
282            $query->addCriterion(
283                TaoOntology::PROPERTY_TRANSLATION_TYPE,
284                SupportedOperatorHelper::IN,
285                [
286                    TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_ORIGINAL
287                ]
288            );
289        }
290
291        foreach ($propertyFilter as $filterProp => $filterVal) {
292            if ($filterVal instanceof Filter) {
293                $query->addCriterion($filterVal->getKey(), $filterVal->getOperator(), $filterVal->getValue());
294                continue;
295            }
296            if (!is_array($filterVal)) {
297                $filterVal = [];
298            }
299            foreach ($filterVal as $values) {
300                if (is_array($values)) {
301                    $query->addCriterion($filterProp, SupportedOperatorHelper::IN, $values);
302                } elseif (is_string($values)) {
303                    $query->addCriterion($filterProp, SupportedOperatorHelper::CONTAIN, $values);
304                }
305            }
306        }
307
308        $queryBuilder->setCriteria($query);
309        if (isset($options['limit'])) {
310            $queryBuilder->setLimit($options['limit']);
311        }
312        if (isset($options['offset'])) {
313            $queryBuilder->setOffset($options['offset']);
314        }
315        if (isset($options['order'])) {
316            $queryBuilder->sort($options['order']);
317        }
318        return $queryBuilder;
319    }
320
321    /**
322     * @return ComplexSearchService
323     */
324    private function getSearchService()
325    {
326        return $this->getModel()->getSearchInterface();
327    }
328
329    /**
330     * @param core_kernel_classes_Class $class
331     * @return core_kernel_classes_Class[]
332     * @throws
333     */
334    private function getSubClasses(core_kernel_classes_Class $class)
335    {
336        $search = $this->getSearchService();
337        $queryBuilder = $search->query();
338        $query = $queryBuilder->newQuery()->add(OntologyRdfs::RDFS_SUBCLASSOF)->equals($class->getUri());
339        $queryBuilder->setCriteria($query);
340        //classes always sorted by label
341        $order = [RDFS_LABEL => 'asc'];
342        $queryBuilder->sort($order);
343        $result = [];
344        $search->setLanguage($queryBuilder, \common_session_SessionManager::getSession()->getDataLanguage());
345        foreach ($search->getGateway()->search($queryBuilder) as $subclass) {
346            $result[] = $this->getClass($subclass->getUri());
347        }
348        return $result;
349    }
350
351    private function mustFilterTranslations(core_kernel_classes_Class $class): bool
352    {
353        $featureFlagChecker = $this->getFeatureFlagChecker();
354
355        if ($featureFlagChecker->isEnabled('FEATURE_FLAG_TRANSLATION_DEVELOPER_MODE')) {
356            return false;
357        }
358
359        if (!$featureFlagChecker->isEnabled('FEATURE_FLAG_TRANSLATION_ENABLED')) {
360            return false;
361        }
362
363        $parentClassIds = $class->getParentClassesIds();
364        $mainClass = array_pop($parentClassIds);
365
366        return in_array(
367            $mainClass ?? $class->getUri(),
368            [
369                TaoOntology::CLASS_URI_ITEM,
370                TaoOntology::CLASS_URI_TEST
371            ],
372            true
373        );
374    }
375
376    private function getFeatureFlagChecker(): FeatureFlagCheckerInterface
377    {
378        return ServiceManager::getServiceManager()->getContainer()->get(FeatureFlagChecker::class);
379    }
380}