Current File : /home/jvzmxxx/wiki/extensions/Wikibase/repo/tests/phpunit/includes/Actions/EditEntityActionTest.php
<?php

namespace Wikibase\Test;

use MWException;
use Title;
use User;
use Wikibase\EditEntityAction;
use Wikibase\Repo\WikibaseRepo;
use Wikibase\SubmitEntityAction;
use WikiPage;

/**
 * @covers Wikibase\EditEntityAction
 * @covers Wikibase\SubmitEntityAction
 *
 * @license GPL-2.0+
 * @author Daniel Kinzler
 *
 * @group Action
 * @group Wikibase
 * @group WikibaseAction
 * @group WikibaseRepo
 *
 * @group Database
 * @group medium
 */
class EditEntityActionTest extends ActionTestCase {

	protected function setUp() {
		parent::setUp();

		static $user = null;

		if ( !$user ) {
			$user = User::newFromId( 0 );
			$user->setName( '127.0.0.1' );
		}

		$this->setMwGlobals( 'wgUser', $user );

		// Remove handlers for the "OutputPageParserOutput" hook
		$this->mergeMwGlobalArrayValue( 'wgHooks', array( 'OutputPageParserOutput' => array() ) );
	}

	public function testActionForPage() {
		$page = $this->getTestItemPage( 'Berlin' );

		$action = $this->createAction( 'edit', $page );
		$this->assertInstanceOf( EditEntityAction::class, $action );

		$action = $this->createAction( 'submit', $page );
		$this->assertInstanceOf( SubmitEntityAction::class, $action );
	}

	protected function adjustRevisionParam( $key, array &$params, WikiPage $page ) {
		if ( !isset( $params[$key] ) || ( is_int( $params[$key] ) && $params[$key] > 0 ) ) {
			return;
		}

		if ( is_array( $params[$key] ) ) {
			$page = $this->getTestItemPage( $params[$key][0] );
			$ofs = (int)$params[$key][1];

			$params[$key] = 0;
		} else {
			$ofs = (int)$params[$key];
		}

		$rev = $page->getRevision();

		if ( !$rev ) {
			return;
		}

		for ( $i = abs( $ofs ); $i > 0; $i -= 1 ) {
			$rev = $rev->getPrevious();
			if ( !$rev ) {
				throw new MWException( 'Page ' . $page->getTitle()->getPrefixedDBkey()
					. ' does not have ' . ( abs( $ofs ) + 1 ) . ' revisions' );
			}
		}

		$params[ $key ] = $rev->getId();
	}

	public function provideUndoForm() {
		// based upon well known test items defined in ActionTestCase::makeTestItemData

		$cases = array(
			array( //0: edit, no parameters
				'edit', // action
				'Berlin', // handle
				array(), // params
				false, // post
				null, // user
				'/id="[^"]*\bwb-item\b[^"]*"/', // htmlPattern: should show an item
			),

			array( //1: submit, no parameters
				'submit', // action
				'Berlin', // handle
				array(), // params
				false, // post
				null, // user
				'/id="[^"]*\bwb-item\b[^"]*"/', // htmlPattern: should show an item
			),

			// -- show undo form -----------------------------------
			array( //2: // undo form with legal undo
				'edit', // action
				'Berlin', // handle
				array( // params
					'undo' => 0, // current revision
				),
				false, // post
				null, // user
				'/undo-success/', // htmlPattern: should be a success
			),

			array( //3: // undo form with legal undo and undoafter
				'edit', // action
				'Berlin', // handle
				array( // params
					'undo' => 0, // current revision
					'undoafter' => -1, // previous revision
				),
				false, // post
				null, // user
				'/undo-success/', // htmlPattern: should be a success
			),

			array( //4: // undo form with illegal undo == undoafter
				'edit', // action
				'Berlin', // handle
				array( // params
					'undo' => -1, // previous revision
					'undoafter' => -1, // previous revision
				),
				false, // post
				null, // user
				'/wikibase-undo-samerev/', // htmlPattern: should contain error
			),

			array( //5: // undo form with legal undoafter
				'edit', // action
				'Berlin', // handle
				array( // params
					'undoafter' => -1, // previous revision
				),
				false, // post
				null, // user
				'/undo-success/', // htmlPattern: should be a success
			),

			array( //6: // undo form with illegal undo
				'edit', // action
				'Berlin', // handle
				array( // params
					'undo' => -2, // first revision
				),
				false, // post
				null, // user
				'/wikibase-undo-firstrev/', // htmlPattern: should contain error
			),

			array( //7: // undo form with illegal undoafter
				'edit', // action
				'Berlin', // handle
				array( // params
					'undoafter' => 0, // current revision
				),
				false, // post
				null, // user
				'/wikibase-undo-samerev/', // htmlPattern: should contain error
			),

			// -- show restore form -----------------------------------
			array( //8: // restore form with legal restore
				'edit', // action
				'Berlin', // handle
				array( // params
					'restore' => -1, // previous revision
				),
				false, // post
				null, // user
				'/class="diff/', // htmlPattern: should be a success and contain a diff (undo-success is not shown for restore)
			),

			array( //9: // restore form with illegal restore
				'edit', // action
				'Berlin', // handle
				array( // params
					'restore' => 0, // current revision
				),
				false, // post
				null, // user
				'/wikibase-undo-samerev/', // htmlPattern: should contain error
			),

			// -- bad revision -----------------------------------
			array( //10: // undo bad revision
				'edit', // action
				'Berlin', // handle
				array( // params
					'undo' => 12345678, // bad revision
				),
				false, // post
				null, // user
				'/undo-norev/', // htmlPattern: should contain error
			),

			array( //11: // undoafter bad revision with good undo
				'edit', // action
				'Berlin', // handle
				array( // params
					'undo' => 0, // current revision
					'undoafter' => 12345678, // bad revision
				),
				false, // post
				null, // user
				'/undo-norev/', // htmlPattern: should contain error
			),

			array( //12: // undoafter bad revision
				'edit', // action
				'Berlin', // handle
				array( // params
					'undoafter' => 12345678, // bad revision
				),
				false, // post
				null, // user
				'/undo-norev/', // htmlPattern: should contain error
			),

			array( //13: // restore bad revision
				'edit', // action
				'Berlin', // handle
				array( // params
					'restore' => 12345678, // bad revision
				),
				false, // post
				null, // user
				'/undo-norev/', // htmlPattern: should contain error
			),

			// -- bad page -----------------------------------
			array( //14: // non-existing page
				'edit', // action
				Title::newFromText( 'XXX', $this->getItemNamespace() ),
				array( // params
					'restore' => array( 'London', 0 ), // ok revision
				),
				false, // post
				null, // user
				'/missing-article/', // htmlPattern: should contain error
			),

			array( //15: // undo revision from different pages
				'edit', // action class
				'Berlin', // handle
				array( // params
					'undo' => array( 'London', 0 ), // wrong page
				),
				false, // post
				null, // user
				'/wikibase-undo-badpage/', // htmlPattern: should contain error
			),

			array( //16: // undoafter revision from different pages
				'edit', // action class
				'Berlin', // handle
				array( // params
					'undoafter' => array( 'London', -1 ), // wrong page
				),
				false, // post
				null, // user
				'/wikibase-undo-badpage/', // htmlPattern: should contain error
			),

			array( //17: // restore revision from different pages
				'edit', // action class
				'Berlin', // handle
				array( // params
					'restore' => array( 'London', -1 ), // wrong page
				),
				false, // post
				null, // user
				'/wikibase-undo-badpage/', // htmlPattern: should contain error
			),

		);

		// -- show undo form for redirect -----------------------------------
		$cases[] = array( //18: // undo form with legal undo
			'edit', // action
			'Berlin2', // handle
			array( // params
				'undo' => 0, // current revision
			),
			false, // post
			null, // user
			'/undo-success/', // htmlPattern: should be a success
		);

		return $cases;
	}

	/**
	 * @dataProvider provideUndoForm
	 */
	public function testUndoForm(
		$action,
		$page,
		array $params,
		$post = false,
		User $user = null,
		$htmlPattern = null,
		array $expectedProps = null
	) {
		$this->tryUndoAction( $action, $page, $params, $post, $user, $htmlPattern, $expectedProps );
	}

	public function provideUndoSubmit() {
		// based upon well known test items defined in ActionTestCase::makeTestItemData
		return array(
			array( //0: submit with legal undo, but don't post
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'undo' => 0, // current revision
				),
				false, // post
				null, // user
				null, // htmlPattern
				array(
					'redirect' => '/[&?]action=edit&undo=\d+/', // redirect to undo form
				)
			),

			array( //1: submit with legal undo, but omit wpSave
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpEditToken' => true, // automatic token
					'undo' => 0, // current revision
				),
				true, // post
				null, // user
				null, // htmlPattern
				array(
					'redirect' => '/[&?]action=edit&undo=\d+/', // redirect to undo form
				)
			),

			// -- show undo form -----------------------------------
			array( //2: // undo form with legal undo
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'undo' => 0, // current revision
				),
				true, // post
				null, // user
				null, // htmlPattern
				array(
					'redirect' => '![:/=]Q\d+$!' // expect success and redirect to page
				),
			),

			array( //3: // undo form with legal undo and undoafter
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'undo' => 0, // current revision
					'undoafter' => -1, // previous revision
				),
				true, // post
				null, // user
				null, // htmlPattern
				array(
					'redirect' => '![:/=]Q\d+$!' // expect success and redirect to page
				),
			),

			array( //4: // undo form with illegal undo == undoafter
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'undo' => -1, // previous revision
					'undoafter' => -1, // previous revision
				),
				true, // post
				null, // user
				'/wikibase-undo-samerev/', // htmlPattern: should contain error
			),

			array( //5: // undo form with legal undoafter
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'undoafter' => -1, // previous revision
				),
				true, // post
				null, // user
				null, // htmlPattern
				array(
					'redirect' => '![:/=]Q\d+$!' // expect success and redirect to page
				),
			),

			array( //6: // undo form with illegal undo
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'undo' => -2, // first revision
				),
				true, // post
				null, // user
				'/wikibase-undo-firstrev/', // htmlPattern: should contain error
			),

			array( //7: // undo form with illegal undoafter
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'undoafter' => 0, // current revision
				),
				true, // post
				null, // user
				'/wikibase-undo-samerev/', // htmlPattern: should contain error
			),

			// -- show restore form -----------------------------------
			array( //8: // restore form with legal restore
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'restore' => -1, // previous revision
				),
				true, // post
				null, // user
				null, // htmlPattern
				array(
					'redirect' => '![:/=]Q\d+$!' // expect success and redirect to page
				),
			),

			array( //9: // restore form with illegal restore
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'restore' => 0, // current revision
				),
				true, // post
				null, // user
				'/wikibase-undo-samerev/', // htmlPattern: should contain error
			),

			// -- bad revision -----------------------------------
			array( //10: // undo bad revision
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'undo' => 12345678, // bad revision
				),
				true, // post
				null, // user
				'/undo-norev/', // htmlPattern: should contain error
			),

			array( //11: // undoafter bad revision with good undo
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'undo' => 0, // current revision
					'undoafter' => 12345678, // bad revision
				),
				true, // post
				null, // user
				'/undo-norev/', // htmlPattern: should contain error
			),

			array( //12: // undoafter bad revision
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'undoafter' => 12345678, // bad revision
				),
				true, // post
				null, // user
				'/undo-norev/', // htmlPattern: should contain error
			),

			array( //13: // restore bad revision
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'restore' => 12345678, // bad revision
				),
				true, // post
				null, // user
				'/undo-norev/', // htmlPattern: should contain error
			),

			// -- bad page -----------------------------------
			array( //14: // non-existing page
				'submit', // action
				Title::newFromText( 'XXX', $this->getItemNamespace() ),
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'restore' => array( 'London', 0 ), // ok revision
				),
				true, // post
				null, // user
				'/missing-article/', // htmlPattern: should contain error
			),

			array( //15: // undo revision from different pages
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'undo' => array( 'London', 0 ), // wrong page
				),
				true, // post
				null, // user
				'/wikibase-undo-badpage/', // htmlPattern: should contain error
			),

			array( //16: // undoafter revision from different pages
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'undoafter' => array( 'London', -1 ), // wrong page
				),
				true, // post
				null, // user
				'/wikibase-undo-badpage/', // htmlPattern: should contain error
			),

			array( //17: // restore revision from different pages
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // automatic token
					'restore' => array( 'London', -1 ), // wrong page
				),
				true, // post
				null, // user
				'/wikibase-undo-badpage/', // htmlPattern: should contain error
			),

			// -- bad token -----------------------------------
			array( //18: submit with legal undo, but wrong token
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => 'xyz', // bad token
					'undo' => 0, // current revision
				),
				true, // post
				null, // user
				'/token_suffix_mismatch/', // htmlPattern: should contain error
			),

			// -- incomplete form -----------------------------------
			array( //19: submit without undo/undoafter/restore
				'submit', // action
				'Berlin', // handle
				array( // params
					'wpSave' => 1,
					'wpEditToken' => true, // bad token
				),
				true, // post
				null, // user
				'/id="[^"]*\bwb-item\b[^"]*"/', // htmlPattern: should show item
			),

		);
	}

	/**
	 * @dataProvider provideUndoSubmit
	 */
	public function testUndoSubmit(
		$action,
		$page,
		array $params,
		$post = false,
		User $user = null,
		$htmlPattern = null,
		array $expectedProps = null
	) {
		if ( is_string( $page ) ) {
			self::resetTestItem( $page );
		}

		$this->tryUndoAction( $action, $page, $params, $post, $user, $htmlPattern, $expectedProps );

		if ( is_string( $page ) ) {
			self::resetTestItem( $page );
		}
	}

	/**
	 * @param string $action
	 * @param WikiPage|Title|string $page
	 * @param array $params
	 * @param bool $post
	 * @param User|null $user
	 * @param string|bool|null $htmlPattern
	 * @param string[]|null $expectedProps
	 */
	protected function tryUndoAction(
		$action,
		$page,
		array $params,
		$post = false,
		User $user = null,
		$htmlPattern = null,
		array $expectedProps = null
	) {
		if ( $user ) {
			$this->setUser( $user );
		}

		if ( is_string( $page ) ) {
			$page = $this->getTestItemPage( $page );
		} elseif ( $page instanceof Title ) {
			$page = WikiPage::factory( $page );
		}

		$this->adjustRevisionParam( 'undo', $params, $page );
		$this->adjustRevisionParam( 'undoafter', $params, $page );
		$this->adjustRevisionParam( 'restore', $params, $page );

		if ( isset( $params['wpEditToken'] ) && $params['wpEditToken'] === true ) {
			$params['wpEditToken'] = $this->getEditToken(); //TODO: $user
		}

		$out = $this->callAction( $action, $page, $params, $post );

		if ( $htmlPattern !== null && $htmlPattern !== false ) {
			$this->assertRegExp( $htmlPattern, $out->getHTML() );
		}

		if ( $expectedProps ) {
			foreach ( $expectedProps as $p => $pattern ) {
				$func = 'get' . ucfirst( $p );
				$act = call_user_func( array( $out, $func ) );

				if ( $pattern === true ) {
					$this->assertNotEmpty( $act, $p );
				} elseif ( $pattern === false ) {
					$this->assertEmpty( $act, $p );
				} else {
					$this->assertRegExp( $pattern, $act, $p );
				}
			}
		}
	}

	public function provideUndoRevisions() {

		// based upon well known test items defined in ActionTestCase::makeTestItemData

		return array(
			array( //0: undo last revision
				'Berlin', //handle
				array(
					'undo' => 0, // last revision
				),
				array( //expected
					'descriptions' => array(
						'de' => 'Stadt in Brandenburg',
						'en' => 'City in Germany',
					),
				),
			),

			array( //1: undo previous revision
				'Berlin', //handle
				array(
					'undo' => -1, // previous revision
				),
				array( //expected
					'descriptions' => array(
						'de' => 'Hauptstadt von Deutschland',
					),
				)
			),

			array( //2: undo last and previous revision
				'Berlin', //handle
				array(
					'undo' => 0, // current revision
					'undoafter' => -2, // first revision
				),
				array( //expected
					'descriptions' => array(
						'de' => 'Stadt in Deutschland',
					),
				)
			),

			array( //3: undoafter first revision (conflict, no change)
				'Berlin', //handle
				array(
					'undoafter' => -2, // first revision
				),
				array( //expected
					'descriptions' => array(
						'de' => 'Stadt in Deutschland',
					),
				)
			),

			array( //4: restore previous revision
				'Berlin', //handle
				array(
					'restore' => -1, // previous revision
				),
				array( //expected
					'descriptions' => array(
						'de' => 'Stadt in Brandenburg',
						'en' => 'City in Germany',
					),
				)
			),

			array( //5: restore first revision
				'Berlin', //handle
				array(
					'restore' => -2, // first revision
				),
				array( //expected
					'descriptions' => array(
						'de' => 'Stadt in Deutschland',
					),
				)
			),
		);
	}

	/**
	 * @dataProvider provideUndoRevisions
	 */
	public function testUndoRevisions( $handle, array $params, array $expected ) {
		self::resetTestItem( $handle );

		$page = $this->getTestItemPage( $handle );

		$this->adjustRevisionParam( 'undo', $params, $page );
		$this->adjustRevisionParam( 'undoafter', $params, $page );
		$this->adjustRevisionParam( 'restore', $params, $page );

		if ( !isset( $params['wpEditToken'] ) ) {
			$params['wpEditToken'] = $this->getEditToken();
		}

		if ( !isset( $params['wpSave'] ) ) {
			$params['wpSave'] = 1;
		}

		$out = $this->callAction( 'submit', $page, $params, true );

		$this->assertRegExp( '![:/=]Q\d+$!', $out->getRedirect(), 'successful operation should return a redirect' );

		$item = $this->loadTestItem( $handle );

		if ( isset( $expected['labels'] ) ) {
			$this->assertArrayEquals( $expected['labels'], $item->getFingerprint()->getLabels()->toTextArray(), false, true );
		}

		if ( isset( $expected['descriptions'] ) ) {
			$this->assertArrayEquals( $expected['descriptions'], $item->getFingerprint()->getDescriptions()->toTextArray(), false, true );
		}

		if ( isset( $expected['aliases'] ) ) {
			$this->assertArrayEquals( $expected['aliases'], $item->getFingerprint()->getAliasGroups()->toTextArray(), false, true );
		}

		if ( isset( $expected['sitelinks'] ) ) {
			$actual = array();

			foreach ( $item->getSiteLinkList()->toArray() as $siteLink ) {
				$actual[$siteLink->getSiteId()] = $siteLink->getPageName();
			}

			$this->assertArrayEquals( $expected['sitelinks'], $actual, false, true );
		}

		self::resetTestItem( $handle );
	}

	public function provideUndoPermissions() {
		return array(
			array( //0
				'edit',
				array(
					'*' => array( 'edit' => false ),
					'user' => array( 'edit' => false ),
				),
				'/permissions-errors/'
			),

			array( //1
				'submit',
				array(
					'*' => array( 'edit' => false ),
					'user' => array( 'edit' => false ),
				),
				'/permissions-errors/'
			),
		);
	}

	/**
	 * @dataProvider provideUndoPermissions
	 */
	public function testUndoPermissions( $action, $permissions, $error ) {
		$handle = 'London';

		self::resetTestItem( $handle );

		$this->applyPermissions( $permissions );

		$page = $this->getTestItemPage( $handle );

		$params = array(
			'wpEditToken' => $this->getEditToken(),
			'wpSave' => 1,
			'undo' => $page->getLatest(),
		);

		$out = $this->callAction( $action, $page, $params, true );

		if ( $error ) {
			$this->assertRegExp( $error, $out->getHTML() );

			$this->assertEmpty( $out->getRedirect(), 'operation should not trigger a redirect' );
		} else {
			$this->assertRegExp( '![:/=]Q\d+$!', $out->getRedirect(), 'successful operation should return a redirect' );
		}

		self::resetTestItem( $handle );
	}

	private function getItemNamespace() {
		 $entityNamespaceLookup = WikibaseRepo::getDefaultInstance()->getEntityNamespaceLookup();
		 return $entityNamespaceLookup->getEntityNamespace( CONTENT_MODEL_WIKIBASE_ITEM );
	}

	private function getEditToken() {
		global $wgUser;
		return $wgUser->getEditToken();
	}

}