Current File : /home/jvzmxxx/wiki1/extensions/Flow/includes/Block/Header.php
<?php

namespace Flow\Block;

use Flow\Container;
use Flow\Exception\FlowException;
use Flow\Exception\InvalidActionException;
use Flow\Exception\InvalidInputException;
use Flow\Formatter\HeaderViewQuery;
use Flow\Formatter\RevisionDiffViewFormatter;
use Flow\Formatter\RevisionViewFormatter;
use Flow\Formatter\RevisionViewQuery;
use Flow\Formatter\FormatterRow;
use Flow\Model\Header;
use Flow\Model\UUID;
use Flow\RevisionActionPermissions;
use Flow\UrlGenerator;
use IContextSource;

class HeaderBlock extends AbstractBlock {
	/**
	 * @var Header|null
	 */
	protected $header;

	/**
	 * New revision created via submission.
	 *
	 * @var Header|null
	 */
	protected $newRevision;

	/**
	 * @var array Map of data to be passed on as
	 *  commit metadata for event handlers
	 */
	protected $extraCommitMetadata = array();

	/**
	 * @var string[]
	 */
	protected $supportedPostActions = array( 'edit-header', 'undo-edit-header' );

	/**
	 * @var string[]
	 */
	protected $supportedGetActions = array( 'view', 'compare-header-revisions', 'edit-header', 'view-header', 'undo-edit-header' );

	// @Todo - fill in the template names
	protected $templates = array(
		'view' => '',
		'compare-header-revisions' => 'diff_view',
		'edit-header' => 'edit',
		'undo-edit-header' => 'undo_edit',
		'view-header' => 'single_view',
	);

	/**
	 * @var RevisionActionPermissions Allows or denies actions to be performed
	 */
	protected $permissions;

	public function init( IContextSource $context, $action ) {
		parent::init( $context, $action );

		// Basic initialisation done -- now, load data if applicable
		if ( $this->workflow->isNew() ) {
			return;
		}

		// Get the latest revision attached to this workflow
		$found = $this->storage->find(
			'Header',
			array( 'rev_type_id' => $this->workflow->getId() ),
			array( 'sort' => 'rev_id', 'order' => 'DESC', 'limit' => 1 )
		);

		if ( $found ) {
			$this->header = reset( $found );
		}
	}

	protected function validate() {
		// @todo T113902: some sort of restriction along the lines of article protection
		if ( !isset( $this->submitted['content'] ) ) {
			$this->addError( 'content', $this->context->msg( 'flow-error-missing-header-content' ) );
		}

		if ( $this->header ) {
			$this->validateNextRevision();
		} else {
			// simpler case
			$this->validateFirstRevision();
		}
	}

	protected function validateNextRevision() {
		if ( !$this->permissions->isAllowed( $this->header, 'edit-header' ) ) {
			$this->addError( 'permissions', $this->context->msg( 'flow-error-not-allowed' ) );
			return;
		}

		if ( empty( $this->submitted['prev_revision'] ) ) {
			$this->addError( 'prev_revision', $this->context->msg( 'flow-error-missing-prev-revision-identifier' ) );
		} elseif ( $this->header->getRevisionId()->getAlphadecimal() !== $this->submitted['prev_revision'] ) {
			// This is a reasonably effective way to ensure prev revision matches, but for guarantees against race
			// conditions there also exists a unique index on rev_prev_revision in mysql, meaning if someone else inserts against the
			// parent we and the submitter think is the latest, our insert will fail.
			// TODO: Catch whatever exception happens there, make sure the most recent revision is the one in the cache before
			// handing user back to specific dialog indicating race condition
			$this->addError(
				'prev_revision',
				$this->context->msg( 'flow-error-prev-revision-mismatch' )->params(
					$this->submitted['prev_revision'],
					$this->header->getRevisionId()->getAlphadecimal(),
					$this->context->getUser()->getName()
				),
				array( 'revision_id' => $this->header->getRevisionId()->getAlphadecimal() ) // save current revision ID
			);
		}

		// this isn't really part of validate, but we want the error-rendering template to see the users edited header
		$this->newRevision = $this->header->newNextRevision(
			$this->context->getUser(),
			$this->submitted['content'],
			// default to wikitext when not specified, for old API requests
			isset( $this->submitted['format'] ) ? $this->submitted['format'] : 'wikitext',
			'edit-header',
			$this->workflow->getArticleTitle()
		);

		if ( $this->newRevision->getRevisionId()->equals( $this->header->getRevisionId() ) ) {
			$this->extraCommitMetadata['null-edit'] = true;
		} elseif ( !$this->checkSpamFilters( $this->header, $this->newRevision ) ) {
			return;
		}
	}

	protected function validateFirstRevision() {
		if (
			!$this->permissions->isRevisionAllowed( null, 'create-header' ) ||
			!$this->permissions->isBoardAllowed( $this->workflow, 'create-header' )
		) {
			$this->addError( 'permissions', $this->context->msg( 'flow-error-not-allowed' ) );
			return;
		}
		if ( isset( $this->submitted['prev_revision'] ) && $this->submitted['prev_revision'] ) {
			// User submitted a previous revision, but we couldn't find one.  This is likely
			// an internal error and not a user error, consider better handling
			// is this even worth checking?
			$this->addError( 'prev_revision', $this->context->msg( 'flow-error-prev-revision-does-not-exist' ) );
			return;
		}

		$this->newRevision = Header::create(
			$this->workflow,
			$this->context->getUser(),
			$this->submitted['content'],
			// default to wikitext when not specified, for old API requests
			isset( $this->submitted['format'] ) ? $this->submitted['format'] : 'wikitext',
			'create-header'
		);

		if ( !$this->checkSpamFilters( null, $this->newRevision ) ) {
			return;
		}
	}

	public function commit() {
		$metadata = $this->extraCommitMetadata;

		switch( $this->action ) {
			case 'undo-edit-header':
			case 'edit-header':
				// store data, unless we're dealing with a null-edit (in which case
				// is storing the same thing not only pointless, it can even be
				// incorrect, since listeners will run & generate notifications etc)
				if ( !isset( $this->extraCommitMetadata['null-edit'] ) ) {
					$this->workflow->updateLastUpdated( $this->newRevision->getRevisionId() );
					$this->storage->put( $this->workflow, $metadata ); // 'discussion' workflow
					$this->storage->put( $this->newRevision, $metadata + array(
						'workflow' => $this->workflow,
					) );
				}

				// Reload $this->header for renderApi() after save
				$this->header = $this->newRevision;
				return array(
					'header-revision-id' => $this->newRevision->getRevisionId(),
				);

			default:
				throw new InvalidActionException( 'Unrecognized commit action', 'invalid-action' );
		}
	}

	public function renderApi( array $options ) {
		$output = array(
			'type' => $this->getName(),
			'editToken' => $this->getEditToken(),
		);

		switch ( $this->action ) {
			case 'view':
				$format = isset( $options['format'] ) ? $options['format'] : 'fixed-html';
				$output += $this->renderRevisionApi( $format );
				break;

			case 'edit-header':
				// default to wikitext for no-JS
				$format = isset( $options['format'] ) ? $options['format'] : 'wikitext';
				$output += $this->renderRevisionApi( $format );
				break;

			case 'undo-edit-header':
				$output = $this->renderUndoApi( $options ) + $output;
				break;

			case 'view-header':
				if ( isset( $options['revId'] ) && $options['revId'] ) {
					$output += $this->renderSingleViewApi( $options['revId'] );
				} else {
					$format = isset( $options['format'] ) ? $options['format'] : 'fixed-html';
					$output += $this->renderRevisionApi( $format );
				}
				break;

			case 'compare-header-revisions':
				$output += $this->renderDiffviewApi( $options );
				break;
		}

		$output['submitted'] = $this->wasSubmitted() ? $this->submitted : array();
		$output['errors'] = $this->errors;
		return $output;
	}

	// @Todo - duplicated logic in other diff view block
	protected function renderDiffviewApi( array $options ) {
		if ( !isset( $options['newRevision'] ) ) {
			throw new InvalidInputException( 'A revision must be provided for comparison', 'revision-comparison' );
		}
		$oldRevision = null;
		if ( isset( $options['oldRevision'] ) ) {
			$oldRevision = $options['oldRevision'];
		}
		/** @var HeaderViewQuery $query */
		$query = Container::get( 'query.header.view' );
		list( $new, $old ) = $query->getDiffViewResult( UUID::create( $options['newRevision'] ), UUID::create( $oldRevision ) );
		/** @var RevisionDiffViewFormatter $formatter */
		$formatter = Container::get( 'formatter.revision.diff.view' );

		return array(
			'revision' => $formatter->formatApi( $new, $old, $this->context )
		);
	}

	// @Todo - duplicated logic in other single view block
	protected function renderSingleViewApi( $revId ) {
		/** @var HeaderViewQuery $query */
		$query = Container::get( 'query.header.view' );
		$row = $query->getSingleViewResult( $revId );
		/** @var RevisionViewFormatter $formatter */
		$formatter = Container::get( 'formatter.revisionview' );

		if ( !$this->permissions->isAllowed( $row->revision, 'view' ) ) {
			$this->addError( 'permissions', $this->context->msg( 'flow-error-not-allowed' ) );
			return array();
		}

		return array(
			'revision' => $formatter->formatApi( $row, $this->context )
		);
	}

	/**
	 * @param string $format Content format (html|wikitext)
	 * @return array
	 */
	protected function renderRevisionApi( $format ) {
		$output = array();
		if ( $this->header === null ) {
			if (
				!$this->permissions->isRevisionAllowed( null, 'view' ) ||
				!$this->permissions->isBoardAllowed( $this->workflow, 'view' )
			) {
				$this->addError( 'permissions', $this->context->msg( 'flow-error-not-allowed' ) );
				return array();
			}

			/** @var UrlGenerator $urlGenerator */
			$urlGenerator = Container::get( 'url_generator' );

			$title = $this->workflow->getArticleTitle();
			$user = $this->context->getUser();

			$actions = array();

			if ( $this->workflow->userCan( 'edit', $user ) ) {
				$actions['edit'] = $urlGenerator
					->createHeaderAction( $this->workflow->getArticleTitle() );
			}

			$output['revision'] = array(
				'actions' => $actions,
				'links' => array(
				),
			);
		} else {
			$row = new FormatterRow;
			$row->workflow = $this->workflow;
			$row->revision = $this->header;
			$row->currentRevision = $this->header;

			if ( !$this->permissions->isAllowed( $row->revision, 'view' ) ) {
				$this->addError( 'permissions', $this->context->msg( 'flow-error-not-allowed' ) );
				return array();
			}

			$serializer = Container::get( 'formatter.revision' );
			$serializer->setContentFormat( $format );

			// For flow-description-last-modified-at
			$serializer->setIncludeHistoryProperties( true );

			$output['revision'] = $serializer->formatApi( $row, $this->context );
		}

		$output['copyrightMessage'] = $this->context->getSkin()->getCopyright();

		return $output;
	}

	protected function renderUndoApi( array $options ) {
		if ( $this->workflow->isNew() ) {
			throw new FlowException( 'No header exists to undo' );
		}

		if ( !isset( $options['startId'], $options['endId'] ) ) {
			throw new InvalidInputException( 'Both startId and endId must be provided' );
		}

		/** @var RevisionViewQuery */
		$query = Container::get( 'query.header.view' );
		$rows = $query->getUndoDiffResult( $options['startId'], $options['endId'] );
		if ( !$rows ) {
			throw new InvalidInputException( 'Could not load revision to undo' );
		}

		$serializer = Container::get( 'formatter.undoedit' );
		return $serializer->formatApi( $rows[0], $rows[1], $rows[2], $this->context );
	}

	public function getName() {
		return 'header';
	}
}