summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/SemanticMediaWiki/src/DataUpdater.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/SemanticMediaWiki/src/DataUpdater.php')
-rw-r--r--www/wiki/extensions/SemanticMediaWiki/src/DataUpdater.php430
1 files changed, 430 insertions, 0 deletions
diff --git a/www/wiki/extensions/SemanticMediaWiki/src/DataUpdater.php b/www/wiki/extensions/SemanticMediaWiki/src/DataUpdater.php
new file mode 100644
index 00000000..b9aef739
--- /dev/null
+++ b/www/wiki/extensions/SemanticMediaWiki/src/DataUpdater.php
@@ -0,0 +1,430 @@
+<?php
+
+namespace SMW;
+
+use Title;
+use User;
+use WikiPage;
+
+/**
+ * This function takes care of storing the collected semantic data and
+ * clearing out any outdated entries for the processed page. It assumes
+ * that parsing has happened and that all relevant information are
+ * contained and provided for.
+ *
+ * Optionally, this function also takes care of triggering indirect updates
+ * that might be needed for an overall database consistency. If the saved page
+ * describes a property or data type, the method checks whether the property
+ * type, the data type, the allowed values, or the conversion factors have
+ * changed.
+ *
+ * @license GNU GPL v2+
+ * @since 1.9
+ *
+ * @author mwjames
+ */
+class DataUpdater {
+
+ /**
+ * @var Store
+ */
+ private $store;
+
+ /**
+ * @var SemanticData
+ */
+ private $semanticData;
+
+ /**
+ * @var TransactionalCallableUpdate
+ */
+ private $transactionalCallableUpdate;
+
+ /**
+ * @var boolean|null
+ */
+ private $canCreateUpdateJob = null;
+
+ /**
+ * @var boolean
+ */
+ private $processSemantics = false;
+
+ /**
+ * @var boolean
+ */
+ private $isCommandLineMode = false;
+
+ /**
+ * @var boolean|string
+ */
+ private $isChangeProp = false;
+
+ /**
+ * @var boolean
+ */
+ private $isDeferrableUpdate = false;
+
+ /**
+ * @var string
+ */
+ private $origin = '';
+
+ /**
+ * @since 1.9
+ *
+ * @param Store $store
+ * @param SemanticData $semanticData
+ */
+ public function __construct( Store $store, SemanticData $semanticData ) {
+ $this->store = $store;
+ $this->semanticData = $semanticData;
+ $this->transactionalCallableUpdate = ApplicationFactory::getInstance()->newDeferredTransactionalCallableUpdate();
+ }
+
+ /**
+ * @see https://www.mediawiki.org/wiki/Manual:$wgCommandLineMode
+ * Indicates whether MW is running in command-line mode.
+ *
+ * @since 3.0
+ *
+ * @param boolean $isCommandLineMode
+ */
+ public function isCommandLineMode( $isCommandLineMode ) {
+ $this->isCommandLineMode = $isCommandLineMode;
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @param boolean $isChangeProp
+ */
+ public function isChangeProp( $isChangeProp ) {
+ $this->isChangeProp = (bool)$isChangeProp;
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @param boolean $isChangeProp
+ */
+ public function isDeferrableUpdate( $isDeferrableUpdate ) {
+ $this->isDeferrableUpdate = (bool)$isDeferrableUpdate;
+ }
+
+ /**
+ * @since 2.5
+ *
+ * @param string $origin
+ */
+ public function setOrigin( $origin ) {
+ $this->origin = $origin;
+ }
+
+ /**
+ * @since 1.9
+ *
+ * @return DIWikiPage
+ */
+ public function getSubject() {
+ return $this->semanticData->getSubject();
+ }
+
+ /**
+ * @since 1.9
+ *
+ * @param boolean $canCreateUpdateJob
+ */
+ public function canCreateUpdateJob( $canCreateUpdateJob ) {
+ $this->canCreateUpdateJob = (bool)$canCreateUpdateJob;
+ }
+
+ /**
+ * @since 1.9
+ *
+ * @return boolean
+ */
+ public function doUpdate() {
+
+ if ( !$this->canPerformUpdate() ) {
+ return false;
+ }
+
+ DeferredCallableUpdate::releasePendingUpdates();
+
+ if ( $this->isDeferrableUpdate === false || $this->isCommandLineMode ) {
+ return $this->performUpdate();
+ }
+
+ $this->transactionalCallableUpdate->setCallback( function() {
+ $this->performUpdate();
+ } );
+
+ $this->transactionalCallableUpdate->setOrigin(
+ [
+ __METHOD__,
+ $this->origin,
+ $this->getSubject()->getHash()
+ ]
+ );
+
+ $this->transactionalCallableUpdate->isDeferrableUpdate(
+ $this->isDeferrableUpdate
+ );
+
+ $this->transactionalCallableUpdate->commitWithTransactionTicket();
+ $this->transactionalCallableUpdate->pushUpdate();
+
+ return true;
+ }
+
+ private function canPerformUpdate() {
+
+ $title = $this->getSubject()->getTitle();
+
+ // Protect against null and namespace -1 see Bug 50153
+ if ( $title === null || $title->isSpecialPage() ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @note Make sure to have a valid revision (null means delete etc.) and
+ * check if semantic data should be processed and displayed for a page in
+ * the given namespace
+ */
+ private function performUpdate() {
+
+ $applicationFactory = ApplicationFactory::getInstance();
+
+ if ( $this->canCreateUpdateJob === null ) {
+ $this->canCreateUpdateJob( $applicationFactory->getSettings()->get( 'smwgEnableUpdateJobs' ) );
+ }
+
+ $title = $this->getSubject()->getTitle();
+ $wikiPage = $applicationFactory->newPageCreator()->createPage( $title );
+
+ $revision = $wikiPage->getRevision();
+ $user = $revision !== null ? User::newFromId( $revision->getUser() ) : null;
+
+ $this->addAnnotations( $title, $wikiPage, $revision, $user );
+
+ // In case of a restricted update, only the protection update is required
+ // hence the process bails-out early to avoid unnecessary DB connections
+ // or updates
+ if ( $this->checkUpdateEditProtection( $wikiPage, $user ) === true ) {
+ return true;
+ }
+
+ $this->checkChangePropagation();
+ $this->updateData();
+
+ if ( $this->semanticData->getOption( Enum::PURGE_ASSOC_PARSERCACHE ) === true ) {
+ $jobQueue = $applicationFactory->getJobQueue();
+ $jobQueue->runFromQueue( [ 'SMW\ParserCachePurgeJob' => 2 ] );
+ }
+
+ return true;
+ }
+
+ private function addAnnotations( Title $title, WikiPage $wikiPage, $revision, $user ) {
+
+ $applicationFactory = ApplicationFactory::getInstance();
+
+ if ( $revision !== null ) {
+ $this->processSemantics = $applicationFactory->getNamespaceExaminer()->isSemanticEnabled( $title->getNamespace() );
+ }
+
+ if ( !$this->processSemantics ) {
+ return $this->semanticData = new SemanticData( $this->getSubject() );
+ }
+
+ $pageInfoProvider = $applicationFactory->newMwCollaboratorFactory()->newPageInfoProvider(
+ $wikiPage,
+ $revision,
+ $user
+ );
+
+ $this->semanticData->setExtensionData( 'revision_id', $revision->getId() );
+
+ $propertyAnnotatorFactory = $applicationFactory->singleton( 'PropertyAnnotatorFactory' );
+
+ $propertyAnnotator = $propertyAnnotatorFactory->newNullPropertyAnnotator(
+ $this->semanticData
+ );
+
+ $propertyAnnotator = $propertyAnnotatorFactory->newPredefinedPropertyAnnotator(
+ $propertyAnnotator,
+ $pageInfoProvider
+ );
+
+ // Standard text hooks are not run through a JSON content object therefore
+ // we attach possible annotations at this point
+ if ( $title->getNamespace() === SMW_NS_SCHEMA ) {
+
+ $schemaFactory = $applicationFactory->singleton( 'SchemaFactory' );
+
+ try {
+ $schema = $schemaFactory->newSchema(
+ $title->getDBKey(),
+ $pageInfoProvider->getNativeData()
+ );
+ } catch ( \Exception $e ) {
+ $schema = null;
+ }
+
+ $propertyAnnotator = $propertyAnnotatorFactory->newSchemaPropertyAnnotator(
+ $propertyAnnotator,
+ $schema
+ );
+ }
+
+ $propertyAnnotator->addAnnotation();
+
+ \Hooks::run(
+ 'SMW::DataUpdater::ContentProcessor',
+ [
+ $this->semanticData,
+ $wikiPage->getContent()
+ ]
+ );
+ }
+
+ private function checkUpdateEditProtection( $wikiPage, $user ) {
+
+ $applicationFactory = ApplicationFactory::getInstance();
+
+ $editProtectionUpdater = $applicationFactory->create( 'EditProtectionUpdater',
+ $wikiPage,
+ $user
+ );
+
+ $editProtectionUpdater->doUpdateFrom( $this->semanticData );
+
+ return $editProtectionUpdater->isRestrictedUpdate();
+ }
+
+ /**
+ * @note Comparison must happen *before* the storage update;
+ * even finding uses of a property fails after its type changed.
+ */
+ private function checkChangePropagation() {
+
+ // canCreateUpdateJob: if it is not enabled there's not much to do here
+ // isChangeProp: means the update is part of the ChangePropagationDispatchJob
+ // therefore skip
+ if ( !$this->canCreateUpdateJob || $this->isChangeProp ) {
+ return;
+ }
+
+ $namespace = $this->semanticData->getSubject()->getNamespace();
+
+ if ( $namespace !== SMW_NS_PROPERTY && $namespace !== NS_CATEGORY ) {
+ return;
+ }
+
+ $applicationFactory = ApplicationFactory::getInstance();
+
+ $propertyChangePropagationNotifier = new PropertyChangePropagationNotifier(
+ $this->store,
+ $applicationFactory->newSerializerFactory()
+ );
+
+ $propertyChangePropagationNotifier->setPropertyList(
+ $applicationFactory->getSettings()->get( 'smwgChangePropagationWatchlist' )
+ );
+
+ $propertyChangePropagationNotifier->isCommandLineMode(
+ $this->isCommandLineMode
+ );
+
+ $propertyChangePropagationNotifier->checkAndNotify(
+ $this->semanticData
+ );
+ }
+
+ private function updateData() {
+
+ $this->store->setOption(
+ Store::OPT_CREATE_UPDATE_JOB,
+ $this->canCreateUpdateJob
+ );
+
+ $semanticData = $this->checkOnRequiredRedirectUpdate(
+ $this->semanticData
+ );
+
+ $subject = $semanticData->getSubject();
+
+ if ( $this->processSemantics ) {
+ $this->store->updateData( $semanticData );
+ } elseif ( $this->store->getObjectIds()->exists( $subject ) ) {
+ // Only clear the data where it is know that "exists" is true otherwise
+ // an empty entity is created and later being removed by the
+ // "PropertyTableOutdatedReferenceDisposer" since it is an entity that is
+ // empty == has no reference
+ $this->store->clearData( $subject );
+ }
+
+ return true;
+ }
+
+ private function checkOnRequiredRedirectUpdate( SemanticData $semanticData ) {
+
+ // Check only during online-mode so that when a user operates Special:MovePage
+ // or #redirect the same process is applied
+ if ( !$this->canCreateUpdateJob ) {
+ return $semanticData;
+ }
+
+ $redirects = $semanticData->getPropertyValues(
+ new DIProperty( '_REDI' )
+ );
+
+ if ( $redirects !== [] && !$semanticData->getSubject()->equals( end( $redirects ) ) ) {
+ return $this->doUpdateUnknownRedirectTarget( $semanticData, end( $redirects ) );
+ }
+
+ return $semanticData;
+ }
+
+ private function doUpdateUnknownRedirectTarget( SemanticData $semanticData, DIWikiPage $target ) {
+
+ // Only keep the reference to safeguard that even in case of a text keeping
+ // its annotations there are removed from the Store. A redirect is not
+ // expected to contain any other annotation other than that of the redirect
+ // target
+ $subject = $semanticData->getSubject();
+ $semanticData = new SemanticData( $subject );
+
+ $semanticData->addPropertyObjectValue(
+ new DIProperty( '_REDI' ),
+ $target
+ );
+
+ // Force a manual changeTitle before the general update otherwise
+ // #redirect can cause an inconsistent data container as observed in #895
+ $source = $subject->getTitle();
+ $target = $target->getTitle();
+
+ $this->store->changeTitle(
+ $source,
+ $target,
+ $source->getArticleID(),
+ $target->getArticleID()
+ );
+
+ $dispatchContext = EventHandler::getInstance()->newDispatchContext();
+ $dispatchContext->set( 'title', $subject->getTitle() );
+
+ EventHandler::getInstance()->getEventDispatcher()->dispatch(
+ 'factbox.cache.delete',
+ $dispatchContext
+ );
+
+ return $semanticData;
+ }
+
+}