Current File : /home/jvzmxxx/wiki/extensions/Flow/maintenance/FlowFixWorkflowLastUpdateTimestamp.php
<?php

use Flow\Container;
use Flow\Data\ManagerGroup;
use Flow\Exception\FlowException;
use Flow\Model\AbstractRevision;
use Flow\Model\PostRevision;
use Flow\Model\UUID;
use Flow\Model\Workflow;
use Flow\Repository\RootPostLoader;

$IP = getenv( 'MW_INSTALL_PATH' );
if ( $IP === false ) {
	$IP = dirname( __FILE__ ) . '/../../..';
}

require_once( "$IP/maintenance/Maintenance.php" );
require_once( "$IP/includes/utils/RowUpdateGenerator.php" );
require_once( "$IP/includes/utils/BatchRowWriter.php" );

/**
 * @ingroup Maintenance
 */
class FlowFixWorkflowLastUpdateTimestamp extends Maintenance {
	public function __construct() {
		parent::__construct();

		$this->mDescription = 'Fixes any incorrect workflow_last_update_timestamp for topics';

		$this->setBatchSize( 10 );
	}

	public function execute() {
		global $wgFlowCluster;

		$dbFactory = Container::get( 'db.factory' );
		$storage = Container::get( 'storage' );
		$rootPostLoader = Container::get( 'loader.root_post' );

		$iterator = new BatchRowIterator( $dbFactory->getDB( DB_SLAVE ), 'flow_workflow', 'workflow_id', $this->mBatchSize );
		$iterator->setFetchColumns( array( 'workflow_id', 'workflow_type', 'workflow_last_update_timestamp' ) );
		$iterator->addConditions( array( 'workflow_wiki' => wfWikiID() ) );

		$updater = new BatchRowUpdate(
			$iterator,
			new UpdateWorkflowLastUpdateTimestampWriter( $storage, $wgFlowCluster ),
			new UpdateWorkflowLastUpdateTimestampGenerator( $storage, $rootPostLoader )
		);
		$updater->setOutput( array( $this, 'output' ) );
		$updater->execute();
	}

	/**
	 * parent::output() is a protected method, only way to access it from a
	 * callback in php5.3 is to make a public function. In 5.4 can replace with
	 * a Closure.
	 *
	 * @param string $out
	 * @param mixed $channel
	 */
	public function output( $out, $channel = null ) {
		parent::output( $out, $channel );
	}
}

class UpdateWorkflowLastUpdateTimestampGenerator implements RowUpdateGenerator {
	/**
	 * @var ManagerGroup
	 */
	protected $storage;

	/**
	 * @var RootPostLoader
	 */
	protected $rootPostLoader;

	/**
	 * @param ManagerGroup $storage
	 * @param RootPostLoader $rootPostLoader
	 */
	public function __construct( ManagerGroup $storage, RootPostLoader $rootPostLoader ) {
		$this->storage = $storage;
		$this->rootPostLoader = $rootPostLoader;
	}

	/**
	 * @param stdClass $row
	 * @return array
	 * @throws TimestampException
	 * @throws \Flow\Exception\FlowException
	 * @throws \Flow\Exception\InvalidInputException
	 */
	public function update( $row ) {
		$uuid = UUID::create( $row->workflow_id );

		switch ( $row->workflow_type ) {
			case 'discussion':
				$revision = $this->storage->get( 'Header', $uuid );
				break;

			case 'topic':
				// fetch topic (has same id as workflow) via RootPostLoader so
				// all children are populated
				$revision = $this->rootPostLoader->get( $uuid );
				break;

			default:
				throw new FlowException( 'Unknown workflow type: ' . $row->workflow_type );
		}

		if ( !$revision) {
			return array();
		}

		$timestamp = $this->getUpdateTimestamp( $revision )->getTimestamp( TS_MW );
		if ( $timestamp === $row->workflow_last_update_timestamp ) {
			// correct update timestamp already, nothing to update
			return array();
		}

		return array( 'workflow_last_update_timestamp' => $timestamp );
	}

	/**
	 * @param AbstractRevision $revision
	 * @return MWTimestamp
	 * @throws Exception
	 * @throws TimestampException
	 * @throws \Flow\Exception\DataModelException
	 */
	protected function getUpdateTimestamp( AbstractRevision $revision ) {
		$timestamp = $revision->getRevisionId()->getTimestampObj();

		if ( !$revision instanceof PostRevision ) {
			return $timestamp;
		}

		foreach ( $revision->getChildren() as $child ) {
			// go recursive, find timestamp of most recent child post
			$comparison = $this->getUpdateTimestamp( $child );
			$diff = $comparison->diff( $timestamp );

			// invert will be 1 if the diff is a negative time period from
			// child timestamp ($comparison) to $timestamp, which means that
			// $comparison is more recent than our current $timestamp
			if ( $diff->invert ) {
				$timestamp = $comparison;
			}
		}

		return $timestamp;
	}
}

class UpdateWorkflowLastUpdateTimestampWriter extends BatchRowWriter {
	/**
	 * @var ManagerGroup
	 */
	protected $storage;

	/**
	 * @param ManagerGroup $storage
	 * @param bool $clusterName
	 */
	public function __construct( ManagerGroup $storage, $clusterName = false ) {
		$this->storage = $storage;
		$this->clusterName = $clusterName;
	}

	/**
	 * Overwriting default writer because I want to use Flow storage methods so
	 * the updates also affect cache, not just DB.
	 *
	 * @param array[] $updates
	 */
	public function write( array $updates ) {
		/*
		 * from:
		 * array(
		 *     'primaryKey' => array( 'workflow_id' => $id ),
		 *     'updates' => array( 'workflow_last_update_timestamp' => $timestamp ),
		 * )
		 * to:
		 * array( $id => $timestamp );
		 */
		$timestamps = array_combine(
			$this->arrayColumn( $this->arrayColumn( $updates, 'primaryKey' ), 'workflow_id' ),
			$this->arrayColumn( $this->arrayColumn( $updates, 'changes' ), 'workflow_last_update_timestamp' )
		);

		/** @var UUID[] $uuids */
		$uuids = array_map( array( 'Flow\\Model\\UUID', 'create' ), array_keys( $timestamps ) );

		/** @var Workflow[] $workflows */
		$workflows = $this->storage->getMulti( 'Workflow', $uuids );
		foreach ( $workflows as $workflow ) {
			$timestamp = $timestamps[$workflow->getId()->getBinary()->__toString()];
			$workflow->updateLastUpdated( UUID::getComparisonUUID( $timestamp ) );
		}

		$this->storage->multiPut( $workflows );

		// prevent memory from filling up
		$this->storage->clear();

		wfWaitForSlaves( false, false, $this->clusterName );
	}

	/**
	 * PHP<5.5-compatible array_column alternative.
	 *
	 * @param array $array
	 * @param string $key
	 * @return array
	 */
	protected function arrayColumn( array $array, $key ) {
		return array_map( function( $item ) use ( $key ) {
			return $item[$key];
		}, $array );
	}
}

$maintClass = 'FlowFixWorkflowLastUpdateTimestamp';
require_once RUN_MAINTENANCE_IF_MAIN;