1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SMW; |
4
|
|
|
|
5
|
|
|
use Title; |
6
|
|
|
use User; |
7
|
|
|
use WikiPage; |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* Initiates an update of the Store |
11
|
|
|
* |
12
|
|
|
* @license GNU GPL v2+ |
13
|
|
|
* @since 1.9 |
14
|
|
|
* |
15
|
|
|
* @author mwjames |
16
|
|
|
*/ |
17
|
|
|
class StoreUpdater { |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* @var Store |
21
|
|
|
*/ |
22
|
|
|
private $store; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @var SemanticData |
26
|
|
|
*/ |
27
|
|
|
private $semanticData; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var boolean|null |
31
|
|
|
*/ |
32
|
|
|
private $isEnabledWithUpdateJob = null; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var boolean|null |
36
|
|
|
*/ |
37
|
|
|
private $processSemantics = null; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var ApplicationFactory |
41
|
|
|
*/ |
42
|
|
|
private $applicationFactory = null; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @since 1.9 |
46
|
|
|
* |
47
|
|
|
* @param Store $store |
48
|
|
|
* @param SemanticData $semanticData |
49
|
|
|
*/ |
50
|
228 |
|
public function __construct( Store $store, SemanticData $semanticData ) { |
51
|
228 |
|
$this->store = $store; |
52
|
228 |
|
$this->semanticData = $semanticData; |
53
|
228 |
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @since 1.9 |
57
|
|
|
* |
58
|
|
|
* @return DIWikiPage |
59
|
|
|
*/ |
60
|
227 |
|
public function getSubject() { |
61
|
227 |
|
return $this->semanticData->getSubject(); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @since 1.9 |
66
|
|
|
* |
67
|
|
|
* @param boolean $isEnabledWithUpdateJob |
68
|
|
|
*/ |
69
|
225 |
|
public function isEnabledWithUpdateJob( $isEnabledWithUpdateJob ) { |
70
|
225 |
|
$this->isEnabledWithUpdateJob = (bool)$isEnabledWithUpdateJob; |
71
|
225 |
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* This function takes care of storing the collected semantic data and |
75
|
|
|
* clearing out any outdated entries for the processed page. It assumes |
76
|
|
|
* that parsing has happened and that all relevant information are |
77
|
|
|
* contained and provided for. |
78
|
|
|
* |
79
|
|
|
* Optionally, this function also takes care of triggering indirect updates |
80
|
|
|
* that might be needed for an overall database consistency. If the saved page |
81
|
|
|
* describes a property or data type, the method checks whether the property |
82
|
|
|
* type, the data type, the allowed values, or the conversion factors have |
83
|
|
|
* changed. If so, it triggers UpdateDispatcherJob for the relevant articles, |
84
|
|
|
* which then asynchronously undergoes an update. |
85
|
|
|
* |
86
|
|
|
* @since 1.9 |
87
|
|
|
* |
88
|
|
|
* @return boolean |
89
|
|
|
*/ |
90
|
|
|
public function doUpdate() { |
91
|
227 |
|
|
92
|
227 |
|
if ( !$this->canPerformUpdate() ) { |
93
|
|
|
return false; |
94
|
|
|
} |
95
|
227 |
|
|
96
|
|
|
$this->doPerformUpdate(); |
97
|
227 |
|
|
98
|
|
|
return true; |
99
|
|
|
} |
100
|
227 |
|
|
101
|
2 |
|
private function canPerformUpdate() { |
102
|
|
|
|
103
|
|
|
$title = $this->getSubject()->getTitle(); |
104
|
225 |
|
|
105
|
|
|
// Protect against null and namespace -1 see Bug 50153 |
106
|
|
|
if ( $title === null || $title->isSpecialPage() ) { |
107
|
|
|
return false; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
return true; |
111
|
|
|
} |
112
|
225 |
|
|
113
|
|
|
/** |
114
|
225 |
|
* @note Make sure to have a valid revision (null means delete etc.) and |
115
|
|
|
* check if semantic data should be processed and displayed for a page in |
116
|
225 |
|
* the given namespace |
117
|
1 |
|
*/ |
118
|
|
|
private function doPerformUpdate() { |
119
|
|
|
|
120
|
225 |
|
$this->applicationFactory = ApplicationFactory::getInstance(); |
121
|
225 |
|
|
122
|
225 |
|
if ( $this->isEnabledWithUpdateJob === null ) { |
123
|
|
|
$this->isEnabledWithUpdateJob( $this->applicationFactory->getSettings()->get( 'smwgEnableUpdateJobs' ) ); |
124
|
225 |
|
} |
125
|
225 |
|
|
126
|
225 |
|
$title = $this->getSubject()->getTitle(); |
127
|
|
|
$wikiPage = $this->applicationFactory->newPageCreator()->createPage( $title ); |
|
|
|
|
128
|
225 |
|
|
129
|
|
|
$revision = $wikiPage->getRevision(); |
130
|
|
|
$user = $revision !== null ? User::newFromId( $revision->getUser() ) : null; |
131
|
225 |
|
|
132
|
|
|
$this->addFinalAnnotations( $title, $wikiPage, $revision, $user ); |
|
|
|
|
133
|
225 |
|
|
134
|
|
|
// In case of a restricted update, only the protection update is required |
135
|
225 |
|
// hence the process bails-out early to avoid unnecessary DB connections |
136
|
42 |
|
// or updates |
137
|
|
|
if ( $this->doUpdateEditProtection( $wikiPage, $user ) === true ) { |
138
|
|
|
return true; |
139
|
220 |
|
} |
140
|
|
|
|
141
|
|
|
$this->inspectPropertySpecification(); |
142
|
220 |
|
$this->doRealUpdate(); |
143
|
|
|
} |
144
|
|
|
|
145
|
220 |
|
private function addFinalAnnotations( Title $title, WikiPage $wikiPage, $revision, $user ) { |
146
|
|
|
|
147
|
220 |
|
$this->processSemantics = $revision !== null && $this->isSemanticEnabledNamespace( $title ); |
148
|
220 |
|
|
149
|
|
|
if ( !$this->processSemantics ) { |
150
|
|
|
return $this->semanticData = new SemanticData( $this->getSubject() ); |
151
|
220 |
|
} |
152
|
|
|
|
153
|
|
|
$pageInfoProvider = $this->applicationFactory->newMwCollaboratorFactory()->newPageInfoProvider( |
154
|
|
|
$wikiPage, |
155
|
|
|
$revision, |
156
|
220 |
|
$user |
157
|
220 |
|
); |
158
|
|
|
|
159
|
|
|
$propertyAnnotatorFactory = $this->applicationFactory->singleton( 'PropertyAnnotatorFactory' ); |
160
|
|
|
|
161
|
|
|
$propertyAnnotator = $propertyAnnotatorFactory->newNullPropertyAnnotator( |
162
|
|
|
$this->semanticData |
163
|
225 |
|
); |
164
|
|
|
|
165
|
225 |
|
$propertyAnnotator = $propertyAnnotatorFactory->newPredefinedPropertyAnnotator( |
166
|
24 |
|
$propertyAnnotator, |
167
|
|
|
$pageInfoProvider |
168
|
|
|
); |
169
|
220 |
|
|
170
|
220 |
|
$propertyAnnotator->addAnnotation(); |
171
|
220 |
|
} |
172
|
|
|
|
173
|
|
|
private function doUpdateEditProtection( $wikiPage, $user ) { |
174
|
220 |
|
|
175
|
220 |
|
$editProtectionUpdater = $this->applicationFactory->create( 'EditProtectionUpdater', |
176
|
|
|
$wikiPage, |
177
|
220 |
|
$user |
178
|
|
|
); |
179
|
225 |
|
|
180
|
|
|
$editProtectionUpdater->doUpdateFrom( $this->semanticData ); |
181
|
225 |
|
|
182
|
|
|
return $editProtectionUpdater->isRestrictedUpdate(); |
183
|
225 |
|
} |
184
|
225 |
|
|
185
|
|
|
/** |
186
|
|
|
* @note Comparison must happen *before* the storage update; |
187
|
225 |
|
* even finding uses of a property fails after its type changed. |
188
|
|
|
*/ |
189
|
225 |
|
private function inspectPropertySpecification() { |
190
|
220 |
|
|
191
|
42 |
|
if ( !$this->isEnabledWithUpdateJob ) { |
|
|
|
|
192
|
|
|
return; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
$propertySpecificationChangeNotifier = new PropertySpecificationChangeNotifier( |
196
|
13 |
|
$this->store |
197
|
|
|
); |
198
|
|
|
|
199
|
225 |
|
$propertySpecificationChangeNotifier->setPropertyList( |
200
|
|
|
$this->applicationFactory->getSettings()->get( 'smwgDeclarationProperties' ) |
201
|
|
|
); |
202
|
220 |
|
|
203
|
220 |
|
$propertySpecificationChangeNotifier->detectChangesOn( $this->semanticData ); |
204
|
|
|
$propertySpecificationChangeNotifier->notify(); |
205
|
|
|
} |
206
|
225 |
|
|
207
|
|
|
private function doRealUpdate() { |
208
|
|
|
|
209
|
|
|
$this->store->setUpdateJobsEnabledState( $this->isEnabledWithUpdateJob ); |
|
|
|
|
210
|
225 |
|
|
211
|
24 |
|
$semanticData = $this->checkOnRequiredRedirectUpdate( |
212
|
|
|
$this->semanticData |
213
|
|
|
); |
214
|
220 |
|
|
215
|
220 |
|
$subject = $semanticData->getSubject(); |
216
|
|
|
|
217
|
|
|
if ( $this->processSemantics ) { |
218
|
220 |
|
$this->store->updateData( $semanticData ); |
219
|
26 |
|
} elseif ( $this->store->getObjectIds()->hasIDFor( $subject ) ) { |
|
|
|
|
220
|
|
|
// Only clear the data where it is know that "hasIDFor" is true otherwise |
221
|
|
|
// an empty entity is created and later being removed by the |
222
|
219 |
|
// "PropertyTableOutdatedReferenceDisposer" since it is an entity that is |
223
|
|
|
// empty == has no reference |
224
|
|
|
$this->store->clearData( $subject ); |
225
|
26 |
|
} |
226
|
|
|
|
227
|
|
|
return true; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
private function isSemanticEnabledNamespace( Title $title ) { |
231
|
26 |
|
return $this->applicationFactory->getNamespaceExaminer()->isSemanticEnabled( $title->getNamespace() ); |
232
|
26 |
|
} |
233
|
|
|
|
234
|
26 |
|
private function checkOnRequiredRedirectUpdate( SemanticData $semanticData ) { |
235
|
26 |
|
|
236
|
|
|
// Check only during online-mode so that when a user operates Special:MovePage |
237
|
|
|
// or #redirect the same process is applied |
238
|
|
|
if ( !$this->isEnabledWithUpdateJob ) { |
|
|
|
|
239
|
|
|
return $semanticData; |
240
|
|
|
} |
241
|
26 |
|
|
242
|
26 |
|
$redirects = $semanticData->getPropertyValues( |
243
|
26 |
|
new DIProperty( '_REDI' ) |
244
|
26 |
|
); |
245
|
26 |
|
|
246
|
|
|
if ( $redirects !== array() && !$semanticData->getSubject()->equals( end( $redirects ) ) ) { |
247
|
|
|
return $this->doUpdateUnknownRedirectTarget( $semanticData, end( $redirects ) ); |
248
|
26 |
|
} |
249
|
26 |
|
|
250
|
|
|
return $semanticData; |
251
|
26 |
|
} |
252
|
26 |
|
|
253
|
|
|
private function doUpdateUnknownRedirectTarget( SemanticData $semanticData, DIWikiPage $target ) { |
254
|
|
|
|
255
|
|
|
// Only keep the reference to safeguard that even in case of a text keeping |
256
|
26 |
|
// its annotations there are removed from the Store. A redirect is not |
257
|
|
|
// expected to contain any other annotation other than that of the redirect |
258
|
|
|
// target |
259
|
|
|
$subject = $semanticData->getSubject(); |
260
|
|
|
$semanticData = new SemanticData( $subject ); |
261
|
|
|
|
262
|
|
|
$semanticData->addPropertyObjectValue( |
263
|
|
|
new DIProperty( '_REDI' ), |
264
|
|
|
$target |
265
|
|
|
); |
266
|
|
|
|
267
|
|
|
// Force a manual changeTitle before the general update otherwise |
268
|
|
|
// #redirect can cause an inconsistent data container as observed in #895 |
269
|
|
|
$source = $subject->getTitle(); |
270
|
|
|
$target = $target->getTitle(); |
271
|
|
|
|
272
|
|
|
$this->store->changeTitle( |
273
|
|
|
$source, |
|
|
|
|
274
|
|
|
$target, |
|
|
|
|
275
|
|
|
$source->getArticleID(), |
276
|
|
|
$target->getArticleID() |
277
|
|
|
); |
278
|
|
|
|
279
|
|
|
$dispatchContext = EventHandler::getInstance()->newDispatchContext(); |
280
|
|
|
$dispatchContext->set( 'title', $subject->getTitle() ); |
281
|
|
|
|
282
|
|
|
EventHandler::getInstance()->getEventDispatcher()->dispatch( |
283
|
|
|
'factbox.cache.delete', |
284
|
|
|
$dispatchContext |
285
|
|
|
); |
286
|
|
|
|
287
|
|
|
return $semanticData; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
} |
291
|
|
|
|
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: