parserOutput = $parserOutput; $this->cache = $cache; } /** * @since 3.0 * * @param boolean $isEnabled */ public function isEnabled( $isEnabled ) { $this->isEnabled = (bool)$isEnabled; } /** * @since 3.0 * * @param array $options */ public function setOptions( array $options ) { $this->options = $options; } /** * @since 3.0 * * @param string $key * @param mixed $default * * @return mixed */ public function getOption( $key, $default = false ) { if ( isset( $this->options[$key] ) ) { return $this->options[$key]; } return $default; } /** * @since 3.0 * * @return array|string */ public function getModules() { return 'ext.smw.postproc'; } /** * @since 3.0 * * @param Title $title * @param WebRequest $webRequest * * @return string */ public function getHtml( Title $title, WebRequest $webRequest ) { if ( $this->isEnabled === false ) { return ''; } $subject = DIWikiPage::newFromTitle( $title ); $attributes = [ 'class' => 'smw-postproc', 'data-subject' => $subject->getHash() ]; // Ensure to detect the post edit process to distinguish between an edit // event and any other post, get request in order to only sent a html // fragment once on the edit request and avoid an infinite loop when the // page is reloaded using an API request // @see Article::view $postEdit = $webRequest->getCookie( \EditPage::POST_EDIT_COOKIE_KEY_PREFIX . $title->getLatestRevID() ); $jobs = []; if ( $postEdit !== null && isset( $this->options['run-jobs'] ) ) { $jobs = $this->find_jobs( $this->options['run-jobs'] ); } if ( $jobs !== [] ) { $attributes['data-jobs'] = json_encode( $jobs ); } // Was the edit SMW specific or contains it an unrelated (e.g altered // some text unrelated to any property/value annotation) change? if ( $postEdit !== null && ( $changeDiff = ChangeDiff::fetch( $this->cache, $subject ) ) !== false ) { $postEdit = $this->checkDiff( $changeDiff ); } // Is `@annotation` available as part of a #ask query? $refs = $this->parserOutput->getExtensionData( self::POST_EDIT_UPDATE ); if ( $refs !== null && $refs !== [] ) { $postEdit = $this->checkRef( $title, $postEdit ); } if ( $postEdit !== null && $refs !== null && $refs !== [] ) { $attributes['data-ref'] = json_encode( array_keys( $refs ) ); } if ( $postEdit !== null && isset( $this->options['check-query'] ) && ( $queries = $this->parserOutput->getExtensionData( self::POST_EDIT_CHECK ) ) !== null ) { $attributes['data-query'] = json_encode( $queries ); } // The element is only added temporarily in the event of a postEdit, a // reload of the page will not have the cookie being set and is therefore // neglected if ( $postEdit !== null || $jobs !== [] ) { return Html::rawElement( 'div', $attributes ); } return ''; } /** * @since 3.0 * * @param Query $query */ public function addUpdate( Query $query ) { // Query:getHash returns a hash based on a fingerprint // (when $smwgQueryResultCacheType is set) that eliminates duplicate // queries, yet for the post processing it is necessary to know each // single query (same-condition, different printout) to allow running // alternating updates as in case of cascading value dependencies $queryRef = HashBuilder::createFromArray( $query->toArray() ); $data = $this->parserOutput->getExtensionData( self::POST_EDIT_UPDATE ); if ( $data === null ) { $data = []; } $data[$queryRef] = true; $this->parserOutput->setExtensionData( self::POST_EDIT_UPDATE, $data ); } /** * @since 3.0 * * @param Query $query */ public function addCheck( Query $query ) { if ( !isset( $this->options['check-query'] ) || $this->options['check-query'] === false ) { return; } $q_array = $query->toArray(); // Build a concatenated hash from the query and the result_hash $hash = md5( json_encode( $q_array ) ) . '#'; $data = $this->parserOutput->getExtensionData( self::POST_EDIT_CHECK ); if ( $data === null ) { $data = []; } // Use the result hash to determine whether results differ during the // post-edit examination when running the same query if ( $query->getOption( 'result_hash' ) ) { $hash .= $query->getOption( 'result_hash' ); } $data[$hash] = $q_array; $this->parserOutput->setExtensionData( self::POST_EDIT_CHECK, $data ); } private function checkRef( $title, $postEdit ) { $key = DependencyLinksUpdateJournal::makeKey( $title ); // Is a postEdit, mark the update to avoid running in circles // when the pageCache is purged, use the latestRevID to distinguish // content changes if ( $postEdit !== null ) { $record = [ $title->getLatestRevID() => true ]; $this->cache->save( $key . ':post', $record, self::POST_UPDATE_TTL ); return $postEdit; } // Run outside of a postEdit, check if the dependency journal contains an // active reference to the article and run once (== hash that set by the // dependency journal which is == revID that initiated the change) $hash = $this->cache->fetch( $key ); $record = $this->cache->fetch( $key . ':post' ); if ( $hash !== false && ( $record === false || !isset( $record[$hash] ) ) ) { $postEdit = true; if ( !is_array( $record ) ) { $record = []; } $record[$hash] = true; // Add an update marker (1h) to avoid running twice in case the // journal reference hasn't been deleted yet as result of an existing // PostProcHandler update request. $this->cache->save( $key . ':post', $record, self::POST_UPDATE_TTL ); } return $postEdit; } private function checkDiff( $changeDiff ) { $propertyList = $changeDiff->getPropertyList( 'flip' ); // Investigate whether the changeDiff contains a user invoked modification // and if so, allow the postEdit process to continue in order to act // on SMW data and not on text that doesn't involve changes to a property // value pair. foreach ( $changeDiff->getTableChangeOps() as $tableChangeOp ) { foreach ( $tableChangeOp->getFieldChangeOps() as $fieldChangeOp ) { $pid = $fieldChangeOp->get( 'p_id' ); if ( !isset( $propertyList[$pid] ) ) { continue; } // Does the change involve an operation with a user defined // property? // // Some data were altered but since we cannot (within the request // framework and without further computation) anticipate whether // this influences a query or not, it is a good enough heuristic // to allow to continue the postProc. if ( $propertyList[$pid]{0} !== '_' ) { return true; } if ( $propertyList[$pid] === '_INST' || $propertyList[$pid] === '_ASK' ) { return true; } } } // Avoid any update since the condition of the diff containing any altered // SMW data was not meet. return null; } private function find_jobs( $jobs ) { // Not enabled, no need to invoke a job! if ( isset( $this->options['smwgEnabledQueryDependencyLinksStore'] ) && $this->options['smwgEnabledQueryDependencyLinksStore'] === false ) { unset( $jobs['smw.parserCachePurge'] ); } if ( isset( $this->options['smwgEnabledFulltextSearch'] ) && $this->options['smwgEnabledFulltextSearch'] === false ) { unset( $jobs['smw.fulltextSearchTableUpdate'] ); } return $jobs; } }