Passed
Pull Request — master (#2705)
by Rafael
07:35
created

SearchRequest::getArgumentNamespace()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
1
<?php
2
3
namespace ApacheSolrForTypo3\Solr\Domain\Search;
4
5
/***************************************************************
6
 *  Copyright notice
7
 *
8
 *  (c) 2015-2016 Timo Schmidt <[email protected]>
9
 *  All rights reserved
10
 *
11
 *  This script is part of the TYPO3 project. The TYPO3 project is
12
 *  free software; you can redistribute it and/or modify
13
 *  it under the terms of the GNU General Public License as published by
14
 *  the Free Software Foundation; either version 3 of the License, or
15
 *  (at your option) any later version.
16
 *
17
 *  The GNU General Public License can be found at
18
 *  http://www.gnu.org/copyleft/gpl.html.
19
 *
20
 *  This script is distributed in the hope that it will be useful,
21
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 *  GNU General Public License for more details.
24
 *
25
 *  This copyright notice MUST APPEAR in all copies of the script!
26
 ***************************************************************/
27
28
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\Facets\UrlFacetContainer;
29
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
30
use ApacheSolrForTypo3\Solr\System\Util\ArrayAccessor;
31
use TYPO3\CMS\Core\Utility\ArrayUtility;
32
33
/**
34
 * The searchRequest is used to act as an api to the arguments that have been passed
35
 * with GET and POST.
36
 *
37
 * @author Timo Schmidt <[email protected]>
38
 */
39
class SearchRequest
40
{
41
    /**
42
     * The default plugin namespace.
43
     *
44
     * @var string
45
     */
46
    const DEFAULT_PLUGIN_NAMESPACE = 'tx_solr';
47
48
    /**
49
     * @var string
50
     */
51
    protected $id;
52
53
    /**
54
     * Default namespace overwritten with the configured plugin namespace.
55
     *
56
     * @var string
57
     */
58
    protected $argumentNameSpace = self::DEFAULT_PLUGIN_NAMESPACE;
59
60
    /**
61
     * Arguments that should be kept for sub requests.
62
     *
63
     * Default values, overwritten in the constructor with the namespaced arguments
64
     *
65
     * @var array
66
     */
67
    protected $persistentArgumentsPaths = ['tx_solr:q', 'tx_solr:filter', 'tx_solr:sort'];
68
69
    /**
70
     * @var bool
71
     */
72
    protected $stateChanged = false;
73
74
    /**
75
     * @var ArrayAccessor
76
     */
77
    protected $argumentsAccessor;
78
79
    /**
80
     * The sys_language_uid that was used in the context where the request was build.
81
     * This could be different from the "L" parameter and and not relevant for urls,
82
     * because typolink itself will handle it.
83
     *
84
     * @var int
85
     */
86
    protected $contextSystemLanguageUid;
87
88
    /**
89
     * The page_uid that was used in the context where the request was build.
90
     *
91
     * The pageUid is not relevant for the typolink additionalArguments and therefore
92
     * a separate property.
93
     *
94
     * @var int
95
     */
96
    protected $contextPageUid;
97
98
    /**
99
     * @var TypoScriptConfiguration
100
     */
101
    protected $contextTypoScriptConfiguration;
102
103
    /**
104
     * Container for all active facets inside of the URL(TYPO3/FE)
105
     *
106 46
     * @var UrlFacetContainer
107
     */
108 46
    protected $activeFacetContainer;
109 46
110 46
    /**
111 46
     * @var array
112 46
     */
113 46
    protected $persistedArguments = [];
114
115
    /**
116 46
     * @param array $argumentsArray
117 44
     * @param int $pageUid
118
     * @param int $sysLanguageUid
119
     * @param TypoScriptConfiguration|null $typoScriptConfiguration
120 46
     */
121
    public function __construct(array $argumentsArray = [], int $pageUid = 0, int $sysLanguageUid = 0, TypoScriptConfiguration $typoScriptConfiguration = null)
122 46
    {
123 44
        $this->stateChanged = true;
124 44
        $this->persistedArguments = $argumentsArray;
125
        $this->contextPageUid = $pageUid;
126
        $this->contextSystemLanguageUid = $sysLanguageUid;
127 44
        $this->contextTypoScriptConfiguration = $typoScriptConfiguration;
128
        $this->id = spl_object_hash($this);
129
130 46
        // overwrite the plugin namespace and the persistentArgumentsPaths
131 46
        if (!is_null($typoScriptConfiguration)) {
132
            $this->argumentNameSpace = $typoScriptConfiguration->getSearchPluginNamespace() ?? self::DEFAULT_PLUGIN_NAMESPACE;
133
        }
134
135
        $this->persistentArgumentsPaths = [$this->argumentNameSpace . ':q', $this->argumentNameSpace . ':filter', $this->argumentNameSpace . ':sort', $this->argumentNameSpace . ':groupPage'];
136 31
137
        if (!is_null($typoScriptConfiguration)) {
138 31
            $additionalPersistentArgumentsNames = $typoScriptConfiguration->getSearchAdditionalPersistentArgumentNames();
139
            foreach ($additionalPersistentArgumentsNames ?? [] as $additionalPersistentArgumentsName) {
140
                $this->persistentArgumentsPaths[] = $this->argumentNameSpace . ':' . $additionalPersistentArgumentsName;
141
            }
142
            $this->persistentArgumentsPaths = array_unique($this->persistentArgumentsPaths);
143
        }
144
145
        $this->reset();
146
    }
147
148
    /**
149
     * @return string
150
     */
151
    public function getId()
152
    {
153
        return $this->id;
154
    }
155
156
    /**
157
     * Can be used do merge arguments into the request arguments
158
     *
159
     * @param array $argumentsToMerge
160
     * @return SearchRequest
161
     */
162
    public function mergeArguments(array $argumentsToMerge)
163
    {
164
        ArrayUtility::mergeRecursiveWithOverrule(
165 44
            $this->persistedArguments,
166
            $argumentsToMerge
167 44
        );
168
169
        $this->reset();
170
171
        return $this;
172
    }
173 30
174
    /**
175 30
     * Helper method to prefix an accessor with the arguments namespace.
176 30
     *
177
     * @param string $path
178
     * @return string
179 2
     */
180 30
    protected function prefixWithNamespace($path)
181
    {
182 30
        return $this->argumentNameSpace . ':' . $path;
183
    }
184
185
    /**
186
     * @return array
187
     */
188
    public function getActiveFacetNames()
189
    {
190 35
        return $this->activeFacetContainer->getActiveFacetNames();
191
    }
192 35
193 35
    /**
194
     * Returns all facet values for a certain facetName
195
     * @param string $facetName
196 4
     * @return array
197 4
     */
198 4
    public function getActiveFacetValuesByName(string $facetName)
199
    {
200 35
        return $this->activeFacetContainer->getActiveFacetValuesByName($facetName);
201
    }
202 35
203
    /**
204
     * @return array
205
     */
206
    public function getActiveFacets()
207
    {
208 35
        return $this->activeFacetContainer->getActiveFacets();
209
    }
210 35
211 35
    /**
212
     * Enable sorting of URL parameters
213 35
     */
214
    public function sortActiveFacets(): void
215
    {
216
        $this->activeFacetContainer->enableSort();
217
    }
218
219
    /**
220
     * @return bool
221
     */
222
    public function isActiveFacetsSorted(): bool
223
    {
224
        return $this->activeFacetContainer->isSorted();
225
    }
226
227
    /**
228
     * @return string
229 31
     */
230
    public function getActiveFacetsUrlParameterStyle(): string
231 31
    {
232 31
        return $this->activeFacetContainer->getParameterStyle();
233
    }
234 31
235
    /**
236
     * Returns the active count of facets
237
     *
238
     * @return int
239
     */
240
    public function getActiveFacetCount()
241
    {
242
        return $this->activeFacetContainer->count();
243
    }
244
245 30
    /**
246
     * @param array $activeFacets
247 30
     *
248 3
     * @return SearchRequest
249
     */
250
    protected function setActiveFacets($activeFacets = [])
251 30
    {
252 30
        $this->activeFacetContainer->setActiveFacets($activeFacets);
253 30
254
        return $this;
255 30
    }
256 30
257
    /**
258
     * Adds a facet value to the request.
259
     *
260
     * @param string $facetName
261
     * @param mixed $facetValue
262
     *
263
     * @return SearchRequest
264
     */
265
    public function addFacetValue(string $facetName, $facetValue)
266
    {
267 4
        $this->activeFacetContainer->addFacetValue($facetName, $facetValue);
268
269 4
        if ($this->activeFacetContainer->hasChanged()) {
270
            $this->stateChanged = true;
271
            $this->activeFacetContainer->acknowledgeChange();
272 4
        }
273 4
274
        return $this;
275 4
    }
276 4
277 4
    /**
278 4
     * Removes a facet value from the request.
279
     *
280
     * @param string $facetName
281
     * @param mixed $facetValue
282 4
     *
283 4
     * @return SearchRequest
284 4
     */
285
    public function removeFacetValue(string $facetName, $facetValue)
286
    {
287
        $this->activeFacetContainer->removeFacetValue($facetName, $facetValue);
288
        if ($this->activeFacetContainer->hasChanged()) {
289
            $this->stateChanged = true;
290
            $this->activeFacetContainer->acknowledgeChange();
291
        }
292
293
        return $this;
294 1
    }
295
296 1
    /**
297
     * Removes all facet values from the request by a certain facet name
298
     *
299
     * @param string $facetName
300 1
     *
301
     * @return SearchRequest
302 1
     */
303 1
    public function removeAllFacetValuesByName(string $facetName)
304 1
    {
305
        $this->activeFacetContainer->removeAllFacetValuesByName($facetName);
306
        if ($this->activeFacetContainer->hasChanged()) {
307
            $this->stateChanged = true;
308
            $this->activeFacetContainer->acknowledgeChange();
309
        }
310
        return $this;
311
    }
312 4
313
    /**
314 4
     * Removes all active facets from the request.
315 4
     *
316 4
     * @return SearchRequest
317 4
     */
318
    public function removeAllFacets()
319
    {
320
        $this->activeFacetContainer->removeAllFacets();
321
        if ($this->activeFacetContainer->hasChanged()) {
322
            $this->stateChanged = true;
323
            $this->activeFacetContainer->acknowledgeChange();
324
        }
325 33
        return $this;
326
    }
327 33
328 33
    /**
329
     * Check if an active facet has a given value
330
     *
331
     * @param string $facetName
332
     * @param mixed $facetValue
333
     * @return bool
334 41
     */
335
    public function getHasFacetValue(string $facetName, $facetValue): bool
336 41
    {
337 41
        return $this->activeFacetContainer->hasFacetValue($facetName, $facetValue);
338
    }
339
340
    /**
341
     * @return bool
342
     */
343
    public function getHasSorting()
344
    {
345 41
        $path = $this->prefixWithNamespace('sort');
346
        return $this->argumentsAccessor->has($path);
347 41
    }
348 41
349
    /**
350
     * Returns the sorting string in the url e.g. title asc.
351
     *
352
     * @return string
353
     */
354
    public function getSorting()
355
    {
356
        $path = $this->prefixWithNamespace('sort');
357 41
        return $this->argumentsAccessor->get($path, '');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->argumentsAccessor->get($path, '') returns the type array which is incompatible with the documented return type string.
Loading history...
358
    }
359 41
360 41
    /**
361 41
     * Helper function to get the sorting configuration name or direction.
362
     *
363
     * @param int $index
364
     * @return string
365
     */
366
    protected function getSortingPart($index)
367
    {
368
        $sorting = $this->getSorting();
369
        if ($sorting === '') {
370
            return null;
371
        }
372
373 41
        $parts = explode(' ', $sorting);
374
        return isset($parts[$index]) ? $parts[$index] : null;
375 41
    }
376
377
    /**
378
     * Returns the sorting configuration name that is currently used.
379
     *
380
     * @return string
381
     */
382
    public function getSortingName()
383 41
    {
384
        return $this->getSortingPart(0);
385 41
    }
386
387
    /**
388
     * Returns the sorting direction that is currently used.
389
     *
390
     * @return string
391 32
     */
392
    public function getSortingDirection()
393 32
    {
394 32
        return mb_strtolower($this->getSortingPart(1));
395 32
    }
396 32
397
    /**
398
     * @return SearchRequest
399
     */
400
    public function removeSorting()
401
    {
402
        $path = $this->prefixWithNamespace('sort');
403
        $this->argumentsAccessor->reset($path);
404
        $this->stateChanged = true;
405 32
        return $this;
406
    }
407 32
408 32
    /**
409 32
     * @param string $sortingName
410 32
     * @param string $direction (asc or desc)
411 32
     *
412
     * @return SearchRequest
413
     */
414
    public function setSorting($sortingName, $direction = 'asc')
415
    {
416
        $value = $sortingName . ' ' . $direction;
417
        $path = $this->prefixWithNamespace('sort');
418
        $this->argumentsAccessor->set($path, $value);
419
        $this->stateChanged = true;
420 13
        return $this;
421
    }
422 13
423 13
    /**
424 13
     * Method to set the paginated page of the search
425
     *
426 13
     * @param int $page
427 13
     * @return SearchRequest
428
     */
429 13
    public function setPage($page)
430
    {
431
        $this->stateChanged = true;
432
        $path = $this->prefixWithNamespace('page');
433
        $this->argumentsAccessor->set($path, $page);
434
        // use initial url by switching back to page 0
435
        if ($page === 0) {
436
            $this->argumentsAccessor->reset($path);
437 43
        }
438
        return $this;
439 43
    }
440 43
441
    /**
442
     * Returns the passed page.
443
     *
444
     * @return int|null
445
     */
446
    public function getPage()
447
    {
448 31
        $path = $this->prefixWithNamespace('page');
449
        return $this->argumentsAccessor->get($path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->argumentsAccessor->get($path) also could return the type array which is incompatible with the documented return type integer|null.
Loading history...
450 31
    }
451 31
452
    /**
453 31
     * Can be used to reset all groupPages.
454
     *
455
     * @return SearchRequest
456
     */
457
    public function removeAllGroupItemPages(): SearchRequest
458
    {
459
        $path = $this->prefixWithNamespace('groupPage');
460
        $this->argumentsAccessor->reset($path);
461
462
        return $this;
463
    }
464
465
    /**
466
     * Can be used to paginate within a groupItem.
467
     *
468
     * @param string $groupName e.g. type
469
     * @param string $groupItemValue e.g. pages
470
     * @param int $page
471
     * @return SearchRequest
472
     */
473
    public function setGroupItemPage(string $groupName, string $groupItemValue, int $page): SearchRequest
474
    {
475
        $this->stateChanged = true;
476
        $escapedValue = $this->getEscapedGroupItemValue($groupItemValue);
477
        $path = $this->prefixWithNamespace('groupPage:' . $groupName . ':' . $escapedValue);
478
        $this->argumentsAccessor->set($path, $page);
479
        return $this;
480
    }
481
482
    /**
483
     * Retrieves the current page for this group item.
484
     *
485
     * @param string $groupName
486
     * @param string $groupItemValue
487
     * @return int
488
     */
489
    public function getGroupItemPage(string $groupName, string $groupItemValue): int
490
    {
491
        $escapedValue = $this->getEscapedGroupItemValue($groupItemValue);
492
        $path = $this->prefixWithNamespace('groupPage:' . $groupName . ':' . $escapedValue);
493
        return max(1, (int)$this->argumentsAccessor->get($path));
494
    }
495
496
    /**
497
     * Removes all non alphanumeric values from the groupItem value to have a valid array key.
498
     *
499
     * @param string $groupItemValue
500
     * @return string
501
     */
502
    protected function getEscapedGroupItemValue(string $groupItemValue)
503
    {
504
        return preg_replace("/[^A-Za-z0-9]/", '', $groupItemValue);
505
    }
506
507
    /**
508
     * Retrieves the highest page of the groups.
509
     *
510
     * @return int
511
     */
512
    public function getHighestGroupPage()
513
    {
514
        $max = 1;
515
        $path = $this->prefixWithNamespace('groupPage');
516
        $groupPages = $this->argumentsAccessor->get($path, []);
517
        foreach ($groupPages as $groups) {
518
            if (!is_array($groups)) continue;
519
            foreach ($groups as $groupItemPage) {
520
                if ($groupItemPage > $max) {
521
                    $max = $groupItemPage;
522
                }
523
            }
524
        }
525
526 41
        return $max;
527
    }
528 41
529 41
    /**
530 41
     * Method to overwrite the query string.
531 41
     *
532
     * @param string $rawQueryString
533
     * @return SearchRequest
534
     */
535
    public function setRawQueryString($rawQueryString)
536
    {
537
        $this->stateChanged = true;
538
        $path = $this->prefixWithNamespace('q');
539 43
        $this->argumentsAccessor->set($path, $rawQueryString);
540
        return $this;
541 43
    }
542 43
543 43
    /**
544
     * Returns the passed rawQueryString.
545
     *
546
     * @return string|null
547
     */
548
    public function getRawUserQuery()
549
    {
550
        $path = $this->prefixWithNamespace('q');
551
        $query = $this->argumentsAccessor->get($path, null);
552
        return is_null($query) ? $query : (string)$query;
553 42
    }
554
555 42
    /**
556 42
     * Method to check if the query string is an empty string
557
     * (also empty string or whitespaces only are handled as empty).
558 42
     *
559 4
     * When no query string is set (null) the method returns false.
560
     * @return bool
561
     */
562 38
    public function getRawUserQueryIsEmptyString()
563
    {
564
        $path = $this->prefixWithNamespace('q');
565
        $query = $this->argumentsAccessor->get($path, null);
566 38
567
        if ($query === null) {
568
            return false;
569
        }
570
571
        if (trim($query) === '') {
0 ignored issues
show
Bug introduced by
$query of type array is incompatible with the type string expected by parameter $string of trim(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

571
        if (trim(/** @scrutinizer ignore-type */ $query) === '') {
Loading history...
572
            return true;
573
        }
574
575 43
        return false;
576
    }
577 43
578 43
    /**
579 43
     * This method returns true when no querystring is present at all.
580
     * Which means no search by the user was triggered
581
     *
582
     * @return bool
583
     */
584
    public function getRawUserQueryIsNull()
585
    {
586
        $path = $this->prefixWithNamespace('q');
587
        $query = $this->argumentsAccessor->get($path, null);
588 43
        return $query === null;
589
    }
590 43
591 43
    /**
592 43
     * Sets the results per page that are used during search.
593
     *
594 43
     * @param int $resultsPerPage
595
     * @return SearchRequest
596
     */
597
    public function setResultsPerPage($resultsPerPage)
598
    {
599
        $path = $this->prefixWithNamespace('resultsPerPage');
600
        $this->argumentsAccessor->set($path, $resultsPerPage);
601
        $this->stateChanged = true;
602
603
        return $this;
604
    }
605
606
    /**
607
     * @return bool
608
     */
609 43
    public function getStateChanged()
610
    {
611 43
        return $this->stateChanged;
612 43
    }
613
614
    /**
615
     * Returns the passed resultsPerPage value
616
     * @return int|null
617
     */
618
    public function getResultsPerPage()
619
    {
620
        $path = $this->prefixWithNamespace('resultsPerPage');
621 2
        return $this->argumentsAccessor->get($path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->argumentsAccessor->get($path) also could return the type array which is incompatible with the documented return type integer|null.
Loading history...
622
    }
623 2
624 2
    /**
625 2
     * Allows to set additional filters that are used on time and not transported during the request.
626
     *
627 2
     * @param array $additionalFilters
628
     * @return SearchRequest
629
     */
630
    public function setAdditionalFilters($additionalFilters)
631
    {
632
        $path = $this->prefixWithNamespace('additionalFilters');
633
        $this->argumentsAccessor->set($path, $additionalFilters);
634
        $this->stateChanged = true;
635 42
636
        return $this;
637 42
    }
638 42
639
    /**
640
     * Retrieves the addtional filters that have been set
641
     *
642
     * @return array
643
     */
644 34
    public function getAdditionalFilters()
645
    {
646 34
        $path = $this->prefixWithNamespace('additionalFilters');
647
        return $this->argumentsAccessor->get($path, []);
648
    }
649
650
    /**
651
     * @return int
652 34
     */
653
    public function getContextSystemLanguageUid()
654 34
    {
655
        return $this->contextSystemLanguageUid;
656
    }
657
658
    /**
659
     * @return int
660
     */
661
    public function getContextPageUid()
662 44
    {
663
        return $this->contextPageUid;
664 44
    }
665
666
    /**
667
     * Get contextTypoScriptConfiguration
668
     *
669
     * @return TypoScriptConfiguration
670
     */
671
    public function getContextTypoScriptConfiguration(): ?TypoScriptConfiguration
672 44
    {
673
        return $this->contextTypoScriptConfiguration;
674 44
    }
675 44
676 44
    /**
677
     * Assigns the last known persistedArguments and restores their state.
678
     *
679
     * @return SearchRequest
680
     */
681
    public function reset(): SearchRequest
682
    {
683
        $this->argumentsAccessor = new ArrayAccessor($this->persistedArguments);
684
        $this->stateChanged = false;
685 35
        $this->activeFacetContainer = new UrlFacetContainer(
686
            $this->argumentsAccessor,
687 35
            $this->argumentNameSpace ?? self::DEFAULT_PLUGIN_NAMESPACE,
688
            $this->contextTypoScriptConfiguration === null ?
689
                UrlFacetContainer::PARAMETER_STYLE_INDEX :
690
                $this->contextTypoScriptConfiguration->getSearchFacetingUrlParameterStyle()
691
        );
692
693
        // If the default of sorting parameter should be true, a modification of this condition is needed.
694
        // If instance of contextTypoScriptConfiguration is not TypoScriptConfiguration the sort should be enabled too
695
        if ($this->contextTypoScriptConfiguration instanceof TypoScriptConfiguration &&
696
                $this->contextTypoScriptConfiguration->getSearchFacetingUrlParameterSort(false)) {
697
            $this->activeFacetContainer->enableSort();
698 35
        }
699 35
700 35
        return $this;
701 31
    }
702
703
    /**
704
     * This can be used to start a new sub request, e.g. for a faceted search.
705 35
     *
706 35
     * @param bool $onlyPersistentArguments
707 35
     * @return SearchRequest
708 35
     */
709 35
    public function getCopyForSubRequest(bool $onlyPersistentArguments = true): SearchRequest
710
    {
711
        if (!$onlyPersistentArguments) {
712
            // create a new request with all data
713
            $argumentsArray = $this->argumentsAccessor->getData();
714
            return new SearchRequest(
715
                $argumentsArray,
716 28
                $this->contextPageUid,
717
                $this->contextSystemLanguageUid,
718 28
                $this->contextTypoScriptConfiguration
719
            );
720
        }
721
722
        $arguments = new ArrayAccessor();
723
        foreach ($this->persistentArgumentsPaths as $persistentArgumentPath) {
724 35
            if ($this->argumentsAccessor->has($persistentArgumentPath)) {
725
                $arguments->set($persistentArgumentPath, $this->argumentsAccessor->get($persistentArgumentPath));
726 35
            }
727
        }
728
729
        return new SearchRequest(
730
            $arguments->getData(),
731
            $this->contextPageUid,
732
            $this->contextSystemLanguageUid,
733
            $this->contextTypoScriptConfiguration
734 36
        );
735 36
    }
736
737
    /**
738
     * @return string
739
     */
740
    public function getArgumentNamespace(): string
741
    {
742
        return $this->argumentNameSpace;
743
    }
744
745
    /**
746
     * @return array
747
     */
748
    public function getAsArray(): array
749
    {
750
        return $this->argumentsAccessor->getData();
751
    }
752
753
    /**
754
     * Returns only the arguments as array.
755
     *
756
     * @return array
757
     */
758
    public function getArguments(): array
759
    {
760
        return $this->argumentsAccessor->get($this->argumentNameSpace) ?? [];
761
    }
762
}
763