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\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) |
|
|
|
|
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
|
|
|
|
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.