Issues (186)

src/Search/Variants/SearchVariantSubsites.php (6 issues)

Labels
Severity
1
<?php
2
3
namespace SilverStripe\FullTextSearch\Search\Variants;
4
5
use SilverStripe\Assets\File;
6
use SilverStripe\CMS\Model\SiteTree;
7
use SilverStripe\FullTextSearch\Search\Indexes\SearchIndex;
8
use SilverStripe\FullTextSearch\Search\SearchIntrospection;
9
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
10
use SilverStripe\ORM\Queries\SQLSelect;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\Security\Permission;
13
use SilverStripe\Subsites\Model\Subsite;
0 ignored issues
show
The type SilverStripe\Subsites\Model\Subsite was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
use SilverStripe\Subsites\Extensions\SiteTreeSubsites;
0 ignored issues
show
The type SilverStripe\Subsites\Extensions\SiteTreeSubsites was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
use SilverStripe\Subsites\Extensions\GroupSubsites;
0 ignored issues
show
The type SilverStripe\Subsites\Extensions\GroupSubsites was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use SilverStripe\Subsites\Extensions\FileSubsites;
0 ignored issues
show
The type SilverStripe\Subsites\Extensions\FileSubsites was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use SilverStripe\Subsites\Extensions\SiteConfigSubsites;
0 ignored issues
show
The type SilverStripe\Subsites\Ex...ions\SiteConfigSubsites was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
use SilverStripe\Subsites\State\SubsiteState;
0 ignored issues
show
The type SilverStripe\Subsites\State\SubsiteState was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
20
if (!class_exists(Subsite::class)) {
21
    return;
22
}
23
24
class SearchVariantSubsites extends SearchVariant
25
{
26
    public function appliesToEnvironment()
27
    {
28
        return class_exists(Subsite::class) && parent::appliesToEnvironment();
29
    }
30
31
    public function appliesTo($class, $includeSubclasses)
32
    {
33
        if (!$this->appliesToEnvironment()) {
34
            return false;
35
        }
36
37
        // Include all DataExtensions that contain a SubsiteID.
38
        // TODO: refactor subsites to inherit a common interface, so we can run introspection once only.
39
        $appliesTo = SearchIntrospection::has_extension($class, SiteTreeSubsites::class, $includeSubclasses)
40
            || SearchIntrospection::has_extension($class, GroupSubsites::class, $includeSubclasses)
41
            || SearchIntrospection::has_extension($class, FileSubsites::class, $includeSubclasses)
42
            || SearchIntrospection::has_extension($class, SiteConfigSubsites::class, $includeSubclasses);
43
44
        $this->extend('updateAppliesTo', $appliesTo, $class, $includeSubclasses);
45
46
        return $appliesTo;
47
    }
48
49
    public function currentState()
50
    {
51
        return (string) SubsiteState::singleton()->getSubsiteId();
52
    }
53
54
    public function reindexStates()
55
    {
56
        static $ids = null;
57
58
        if ($ids === null) {
59
            $ids = ['0'];
60
            foreach (Subsite::get() as $subsite) {
61
                $ids[] = (string) $subsite->ID;
62
            }
63
        }
64
65
        return $ids;
66
    }
67
68
    public function activateState($state)
69
    {
70
        if (!$this->appliesToEnvironment()) {
71
            return;
72
        }
73
74
        // Note: Setting directly to the SubsiteState because we don't want the subsite ID to be persisted
75
        // like Subsite::changeSubsite would do.
76
        SubsiteState::singleton()->setSubsiteId($state);
77
        Permission::reset();
78
    }
79
80
    public function alterDefinition($class, $index)
81
    {
82
        $self = get_class($this);
83
84
        if (!$this->appliesTo($class, true)) {
85
            return;
86
        }
87
88
        // Add field to root
89
        $this->addFilterField($index, '_subsite', [
90
            'name' => '_subsite',
91
            'field' => '_subsite',
92
            'fullfield' => '_subsite',
93
            'base' => DataObject::getSchema()->baseDataClass($class),
94
            'origin' => $class,
95
            'type' => 'Int',
96
            'lookup_chain' => [['call' => 'variant', 'variant' => $self, 'method' => 'currentState']],
97
        ]);
98
    }
99
100
    /**
101
     * This field has been altered to allow a user to obtain search results for a particular subsite
102
     * When attempting to do this in project code, SearchVariantSubsites kicks and overwrites any filter you've applied
103
     * This fix prevents the module from doing this if a filter is applied on the index or the query, or if a field is
104
     * being excluded specifically before being executed.
105
     *
106
     * A pull request has been raised for this issue. Once accepted this forked module can be deleted and the parent
107
     * project should be used instead.
108
     *
109
     * @param SearchQuery $query
110
     * @param SearchIndex $index
111
     */
112
    public function alterQuery($query, $index)
113
    {
114
        if ($this->isFieldFiltered('_subsite', $query) || !$this->appliesToEnvironment()) {
115
            return;
116
        }
117
118
        $subsite = $this->currentState();
119
        $query->addFilter('_subsite', [$subsite, SearchQuery::$missing]);
120
    }
121
122
    /**
123
     * We need _really_ complicated logic to find just the changed subsites (because we use versions there's no explicit
124
     * deletes, just new versions with different members) so just always use all of them
125
     */
126
    public function extractManipulationWriteState(&$writes)
127
    {
128
        $self = get_class($this);
129
        $tableName = DataObject::getSchema()->tableName(Subsite::class);
130
        $query = SQLSelect::create('"ID"', '"' . $tableName . '"');
131
        $subsites = array_merge(['0'], $query->execute()->column());
132
133
        foreach ($writes as $key => $write) {
134
            $applies = $this->appliesTo($write['class'], true);
135
            if (!$applies) {
136
                continue;
137
            }
138
139
            if (isset($write['fields'][SiteTree::class . ':SubsiteID'])) {
140
                $subsitesForWrite = [$write['fields'][SiteTree::class . ':SubsiteID']];
141
            } elseif (isset($write['fields'][File::class . ':SubsiteID'])
142
                && (int) $write['fields'][File::class . ':SubsiteID'] !== 0
143
            ) {
144
                // files in subsite 0 should be in all subsites as they are global
145
                $subsitesForWrite = [$write['fields'][File::class . ':SubsiteID']];
146
            } else {
147
                $subsitesForWrite = $subsites;
148
            }
149
150
            $next = [];
151
            foreach ($write['statefulids'] as $i => $statefulid) {
152
                foreach ($subsitesForWrite as $subsiteID) {
153
                    $next[] = [
154
                        'id' => $statefulid['id'],
155
                        'state' => array_merge(
156
                            $statefulid['state'],
157
                            [$self => (string) $subsiteID]
158
                        ),
159
                    ];
160
                }
161
            }
162
            $writes[$key]['statefulids'] = $next;
163
        }
164
    }
165
166
    /**
167
     * Determine if a field with a certain name is filtered by the search query or on the index
168
     * This is the equivalent of saying "show me the results that do ONLY contain this value"
169
     * @param $field string name of the field being filtered
170
     * @param $query SearchQuery currently being executed
171
     * @param $index SearchIndex which specifies a filter field
172
     * @return bool true if $field is being filtered, false if it is not being filtered
173
     */
174
    protected function isFieldFiltered($field, $query)
175
    {
176
        $queryHasFilter = !empty($query->require[$field]);
177
178
        return $queryHasFilter;
179
    }
180
}
181