Completed
Push — master ( 44f7bc...3e4910 )
by
unknown
12s
created

SearchVariantSubsites::appliesTo()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 7
nc 5
nop 2
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 23 and the first side effect is on line 20.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
namespace SilverStripe\FullTextSearch\Search\Variants;
4
5
use SilverStripe\Assets\File;
6
use SilverStripe\CMS\Model\SiteTree;
7
use SilverStripe\FullTextSearch\Search\SearchIntrospection;
8
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
9
use SilverStripe\ORM\Queries\SQLSelect;
10
use SilverStripe\ORM\DataObject;
11
use SilverStripe\Security\Permission;
12
use SilverStripe\Subsites\Model\Subsite;
13
use SilverStripe\Subsites\Extensions\SiteTreeSubsites;
14
use SilverStripe\Subsites\Extensions\GroupSubsites;
15
use SilverStripe\Subsites\Extensions\FileSubsites;
16
use SilverStripe\Subsites\Extensions\SiteConfigSubsites;
17
use SilverStripe\Subsites\State\SubsiteState;
18
19
if (!class_exists(Subsite::class)) {
20
    return;
21
}
22
23
class SearchVariantSubsites extends SearchVariant
24
{
25
    public function appliesToEnvironment()
26
    {
27
        return class_exists(Subsite::class) && parent::appliesToEnvironment();
28
    }
29
30
    public function appliesTo($class, $includeSubclasses)
31
    {
32
        if (!$this->appliesToEnvironment()) {
33
            return false;
34
        }
35
36
        // Include all DataExtensions that contain a SubsiteID.
37
        // TODO: refactor subsites to inherit a common interface, so we can run introspection once only.
38
        return SearchIntrospection::has_extension($class, SiteTreeSubsites::class, $includeSubclasses)
39
            || SearchIntrospection::has_extension($class, GroupSubsites::class, $includeSubclasses)
40
            || SearchIntrospection::has_extension($class, FileSubsites::class, $includeSubclasses)
41
            || SearchIntrospection::has_extension($class, SiteConfigSubsites::class, $includeSubclasses);
42
    }
43
44
    public function currentState()
45
    {
46
        return (string) SubsiteState::singleton()->getSubsiteId();
47
    }
48
49
    public function reindexStates()
50
    {
51
        static $ids = null;
52
53
        if ($ids === null) {
54
            $ids = ['0'];
55
            foreach (Subsite::get() as $subsite) {
56
                $ids[] = (string) $subsite->ID;
57
            }
58
        }
59
60
        return $ids;
61
    }
62
63
    public function activateState($state)
64
    {
65
        if (!$this->appliesToEnvironment()) {
66
            return;
67
        }
68
69
        // Note: Setting directly to the SubsiteState because we don't want the subsite ID to be persisted
70
        // like Subsite::changeSubsite would do.
71
        SubsiteState::singleton()->setSubsiteId($state);
72
        Permission::reset();
73
    }
74
75 View Code Duplication
    public function alterDefinition($class, $index)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
76
    {
77
        $self = get_class($this);
78
79
        if (!$this->appliesTo($class, true)) {
80
            return;
81
        }
82
83
        // Add field to root
84
        $this->addFilterField($index, '_subsite', [
85
            'name' => '_subsite',
86
            'field' => '_subsite',
87
            'fullfield' => '_subsite',
88
            'base' => DataObject::getSchema()->baseDataClass($class),
89
            'origin' => $class,
90
            'type' => 'Int',
91
            'lookup_chain' => [['call' => 'variant', 'variant' => $self, 'method' => 'currentState']],
92
        ]);
93
    }
94
95
    /**
96
     * This field has been altered to allow a user to obtain search results for a particular subsite
97
     * When attempting to do this in project code, SearchVariantSubsites kicks and overwrites any filter you've applied
98
     * This fix prevents the module from doing this if a filter is applied on the index or the query, or if a field is
99
     * being excluded specifically before being executed.
100
     *
101
     * A pull request has been raised for this issue. Once accepted this forked module can be deleted and the parent
102
     * project should be used instead.
103
     */
104
    public function alterQuery($query, $index)
105
    {
106
        if ($this->isFieldFiltered('_subsite', $query) || !$this->appliesToEnvironment()) {
107
            return;
108
        }
109
110
        $subsite = $this->currentState();
111
        $query->filter('_subsite', [$subsite, SearchQuery::$missing]);
112
    }
113
114
    /**
115
     * We need _really_ complicated logic to find just the changed subsites (because we use versions there's no explicit
116
     * deletes, just new versions with different members) so just always use all of them
117
     */
118
    public function extractManipulationWriteState(&$writes)
119
    {
120
        $self = get_class($this);
121
        $tableName = DataObject::getSchema()->tableName(Subsite::class);
122
        $query = SQLSelect::create('"ID"', '"' . $tableName . '"');
123
        $subsites = array_merge(['0'], $query->execute()->column());
124
125
        foreach ($writes as $key => $write) {
126
            $applies = $this->appliesTo($write['class'], true);
127
            if (!$applies) {
128
                continue;
129
            }
130
131
            if (isset($write['fields'][SiteTree::class . ':SubsiteID'])) {
132
                $subsitesForWrite = [$write['fields'][SiteTree::class . ':SubsiteID']];
133
            } elseif (isset($write['fields'][File::class . ':SubsiteID'])
134
                && (int) $write['fields'][File::class . ':SubsiteID'] !== 0
135
            ) {
136
                // files in subsite 0 should be in all subsites as they are global
137
                $subsitesForWrite = [$write['fields'][File::class . ':SubsiteID']];
138
            } else {
139
                $subsitesForWrite = $subsites;
140
            }
141
142
            $next = [];
143
            foreach ($write['statefulids'] as $i => $statefulid) {
144
                foreach ($subsitesForWrite as $subsiteID) {
145
                    $next[] = [
146
                        'id' => $statefulid['id'],
147
                        'state' => array_merge(
148
                            $statefulid['state'],
149
                            [$self => (string) $subsiteID]
150
                        ),
151
                    ];
152
                }
153
            }
154
            $writes[$key]['statefulids'] = $next;
155
        }
156
    }
157
158
    /**
159
     * Determine if a field with a certain name is filtered by the search query or on the index
160
     * This is the equivalent of saying "show me the results that do ONLY contain this value"
161
     * @param $field string name of the field being filtered
162
     * @param $query SearchQuery currently being executed
163
     * @param $index SearchIndex which specifies a filter field
164
     * @return bool true if $field is being filtered, false if it is not being filtered
165
     */
166
    protected function isFieldFiltered($field, $query)
167
    {
168
        $queryHasFilter = !empty($query->require[$field]);
169
170
        return $queryHasFilter;
171
    }
172
}
173