Community Central
Register
Community Central
<?php

/**
 * The MovepagesEx Extension's task's are:
 * - Adding a Move Ex tab to the tab bar for users who are allowed to use the tool.
 * - Mass movement of subpages, talkpages, and talksubpages of an article. 
 *
 * @package GENetwork Extensions
 * @subpackage MovepageEx
 * @author Daniel Friesen (aka: Dantman)(dan_the_man@telus.net)
 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public Licence 2.0 or later
 * @derived_from includes/SpecialMovepage.php
 */

if( !defined( 'MEDIAWIKI' ) ) die( "This is an extension to the MediaWiki package and cannot be run standalone." );

$wgExtensionFunctions[] = 'wfBuildMovepageEx';

$wgGroupPermissions['*'    ]['moveex'] = false;
$wgGroupPermissions['sysop']['moveex'] = true;
$wgGroupPermissions['staff']['moveex'] = true;

require_once( "$IP/includes/SpecialPage.php" );

$wgExtensionCredits['specialpage'][] = array(
			'name' => 'MovepageEx',
			'author' => 'Daniel Friesen(aka: Dantman)',
			'url' => 'http://www.wikia.com/wiki/User:Dantman/GE_Extended/Custom_Extensions/MovepageEx'
	);

function wfBuildMovepageEx() {
	global $wgMessageCache;
	
	$wgMessageCache->addMessages(
		array(
			'uncheck-all'                => "Uncheck all",
			'check-all'                  => "Check all",
			'moveex'                     => "Move Ex",
			'movearticleex'              => "Move name",
			'newtitleex'                 => "To name",
			'movereasonex'               => "Reason",
			'movepagesex'                => "Move pages",
			'movepageex'                 => "Move page Ex",
			'movepageexbtn'              => "Move pages",
			'moveexnoallowed'            => "Not a moveex",
			'moveexnoallowedtext'        => "You must have the moveex permission to mass move pages.",
			'movepageextext'             => "Using the form below will rename a number of pages, moving all of their histories to the new name. The old titles will become redirects to the new titles. Links to the old page titles will not be changed; be sure to check for double or broken redirects. You are responsible for making sure that links continue to point where they are supposed to go.\n\n"
				. "Note that the pages will '''not''' be moved if there are already pages at the new title, unless it is empty or a redirect and has no past edit history. This means that you can rename pages back to where they were just renamed from if you make a mistake, and you cannot overwrite an existing page.\n\n"
				. "<b>WARNING!</b> This can be a drastic and unexpected change for popular pages; please be sure you understand the consequences of this before proceeding.\n\n"
				. "Please note:\n"
				. "* The name listed below is always the article name even if you came here from the talk.\n"
				. "* The name below isn't the actual page moved, it is only the name which all of the listed pages have in common.\n"
				. "* When you change the name, all the checked off pages will not be moved to that name, the part of their name that matches that name will be changed.\n"
				. "* Using this page it is not possible to move article pages to talkpages, all articles will be moved to the matching article namespace, and all talkpages will be moved to the matching talk namespace.\n"
				. "Examples:\n"
				. "{| class='box table colored bordered innerbordered type-basic fill-horiz'\n"
				. "|-\n"
				. "!| {{int:movearticleex}}\n"
				. "!| {{int:newtitleex}}\n"
				. "!| Selected Titles\n"
				. "!| New Titles\n"
				. "|-\n"
				. "|| Main Page\n"
				. "|| Main Page/1\n"
				. "|| Main Page, Talk:Main Page\n"
				. "|| Main Page/1, Talk:Main Page/1\n"
				. "|-\n"
				. "|| Main Page\n"
				. "|| Help:Main Page\n"
				. "|| Main Page, Talk:Main Page\n"
				. "|| Help:Main Page, Help talk:Main Page\n"
				. "|-\n"
				. "|| Main Page\n"
				. "|| Project talk:Main Page\n"
				. "|| Main Page, Talk:Main Page\n"
				. "|| Project:Main Page, Project talk:Main Page\n"
				. "|}\n",
			'delete_and_move_ex'         => 'Delete and move',
			'delete_and_move_confirm_ex' => 'Yes, delete the pages',
			'delete_and_move_head_ex'    => '==Deletion required==',
			'delete_and_move_item_ex'    => 'The destination article "[[$1]]" already exists.',
			'delete_and_move_foot_ex'    => 'Do you want to delete these to make way for their moves?'
		)
	);
	SpecialPage::addPage( new SpecialMovepageEx() );
}

$wgHooks['SkinTemplateTabs'][] = 'wfAddMovepageEx';
function wfAddMovepageEx( $skin, &$tabs ) {
	global $wgScript, $wgTitle, $wgUser;
	if( $wgUser->isAllowed( 'moveex' ) ) {
		$keys = array_keys( $tabs );
		$off  = array_search( 'move', $keys );
		if( $off !== false ) {
			$moveExTitle = SpecialPage::getTitleFor( 'MovepageEx', $wgTitle->getPrefixedURL() );
			array_splice( $tabs, $off+1, 0, array(
				'moveex' => array(
					'class' => $wgTitle->isSpecial( 'MovepageEx' ) ? 'selected' : false,
					'text'  => wfMsg('moveex'),
					'href'  => $moveExTitle->getLocalUrl() ) ) );
		}
	}
}

/**
 * Constructor
 */
class SpecialMovepageEx extends SpecialPage {
	function SpecialMovepageEx() {
		SpecialPage::SpecialPage( 'MovepageEx', 'moveex', false, false, 'default', false );
	}
	
	function execute( $par = null ) {
		global $wgUser, $wgOut, $wgRequest, $action;
	
		# Check rights
		if ( !$wgUser->isAllowed( 'moveex' ) ) {
			$wgOut->showErrorPage( 'moveexnoallowed', 'moveexnoallowedtext' );
			return;
		}
	
		# Don't allow blocked users to move pages
		if ( $wgUser->isBlocked() ) {
			$wgOut->blockedPage();
			return;
		}
	
		# Check for database lock
		if ( wfReadOnly() ) {
			$wgOut->readOnlyPage();
			return;
		}
	
		$f = new MovePageExForm( $par );
	
		if ( 'success' == $action ) {
			$f->showSuccess();
		} elseif ( 'submit' == $action && $wgRequest->wasPosted()
			&& $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
			$f->doSubmit();
		} else {
			$f->showForm( '' );
		}
	}
}

/**
 *
 * @package MediaWiki
 * @subpackage SpecialPage
 */
class MovePageExForm {
	var $oldTitle, $newTitle, $reason; # Text input
	var $deleteAndMove, $selectedPages;
	
	function MovePageExForm( $par ) {
		global $wgRequest;
		$target = isset($par) ? $par : $wgRequest->getVal( 'target' );
		$this->oldTitle = $wgRequest->getText( 'wpOldTitle', $target );
		$this->newTitle = $wgRequest->getText( 'wpNewTitle' );
		$this->reason = $wgRequest->getText( 'wpReason' );
		$this->deleteAndMove = $wgRequest->getBool( 'wpDeleteAndMove' ) && $wgRequest->getBool( 'wpConfirm' );
		$this->selectedPages = $wgRequest->getArray( 'selectedPages', array() );
	}

	function showForm( $err = NULL ) {
		global $wgOut, $wgUser, $wgRequest;
		$err = is_array( $err ) ? $err : array();
		
		$wgOut->setPagetitle( wfMsg( 'movepageex' ) );

		$oa = Title::newFromURL( $this->oldTitle )->getSubjectPage();
		$ot = $oa->getTalkPage();
		if( is_null( $oa ) ) {
			$wgOut->showErrorPage( 'notargettitle', 'notargettext' );
			return;
		}
		$oldTitle = $oa->getPrefixedText();

		# Find the list of subpages and talkpages to move.
		$pageList = array();
		
		$dbr =& wfGetDB( DB_SLAVE );
		$res = $dbr->select( 'page',
			array( 'page_namespace', 'page_title' ),
			array(
				"`page_namespace` = {$oa->mNamespace} OR `page_namespace` = {$ot->mNamespace}",
				"`page_title` REGEXP '^".preg_quote($oa->mDbkeyform,"'")."(/|$)'"
			),
			NULL,
			array( 'ORDER BY' => '`page_namespace` ASC, `page_title` ASC' ) );
		while( $row = $dbr->fetchObject( $res ) )
			$pageList[] = Title::makeTitle( $row->page_namespace, $row->page_title );
		$dbr->freeResult( $res );
		
		$errorQue = array();
		$articleExistsError = false;
		if( count( $err ) > 0 ) $wgOut->setSubtitle( wfMsg( 'formerror' ) );
		foreach( $err as $error ) {
			if( $error->isType('articleexists') ) {
				if( $wgUser->isAllowed( 'delete' ) ) {
					if( !$articleExistsError )
						$wgOut->addWikiText( wfMsg( 'delete_and_move_head_ex' ) );
					$wgOut->addWikiText( wfMsg( 'delete_and_move_item_ex', $error->getNewTitle()->getFullText() ) );
				}
				$articleExistsError = true;
			}
			$e = wfMsg( $error->getErrorType() );
			if( !in_array( $e, $errorQue ) ) $errorQue[] = $e;
		}
		$del = $articleExistsError && $wgUser->isAllowed( 'delete' ); 
		$wgOut->addWikiText( $del ?
			wfMsg( 'delete_and_move_foot_ex' ) : wfMsg( 'movepageextext' ) );
		foreach( $errorQue as $errorText ) {
			$wgOut->addWikiText( '<p class="error">' . $errorText . "</p>\n" );
		}
		
		$wgOut->addHTML(
			Xml::openElement( 'form', array(
				'action' => SpecialPage::getTitleFor( 'MovepageEx' )->getLocalURL( 'action=submit' ),
				'method' => 'post',
				'id' => 'movepageex'
			) ).
			Xml::openElement( 'table', array( 'border' => '0' ) ).
			Xml::openElement( 'tr' ).
			Xml::element( 'td', array( 'align' => 'right' ), wfMsg( 'movearticleex' ) ).
			Xml::closeElement( 'td' ).
			Xml::openElement( 'td', array( 'align' => 'left' ) ).
			Xml::element( 'strong', NULL, $oldTitle ).
			Xml::closeElement( 'td' ).
			Xml::closeElement( 'tr' ).
			Xml::openElement( 'tr' ).
			Xml::openElement( 'td' ).
			Xml::label( wfMsg( 'newtitleex' ), 'wpNewTitle').
			Xml::closeElement( 'td' ).
			Xml::openElement( 'td' ).
			Xml::input( 'wpNewTitle', '40',
				( $this->newTitle ? $this->newTitle : $oldTitle ),
				array( 'id' => 'wpNewTitle' )
			).
			Xml::hidden( 'wpOldTitle', $oldTitle ).
			Xml::closeElement( 'td' ).
			Xml::closeElement( 'tr' ).
			Xml::openElement( 'tr' ).
			Xml::element( 'td', array(
				'align' => 'right', 'valign' => 'top'
			), wfMsg( 'movepagesex' ) ).
			Xml::openElement( 'td', array( 'id' => 'movepages' ) ).
			Xml::openElement( 'ul', array( 'style' => 'list-style: none; margin-left: 0px;' ) )
		);
		foreach( $pageList as $title ) {
			$trimmed = preg_replace( '/^'.preg_quote($oa->mDbkeyform,'/').'/', '', $title->mDbkeyform );
			$id      = 'wp-move-'.$title->mNamespace.'-'.$trimmed;
			$wgOut->addHTML(
				Xml::openElement( 'li' ).
				Xml::check(
					"selectedPages[{$title->mNamespace}][]",
					is_array( $this->selectedPages[$title->mNamespace] ) && in_array( $trimmed, $this->selectedPages[$title->mNamespace] ),
					array(
						'id'    => $id,
						'value' => $trimmed
					)
				).
				Xml::label( $title->getPrefixedText(), $id ).
				Xml::closeElement( 'li' )
			 );
		}
		$wgOut->addHTML(
			Xml::closeElement( 'ul' ).
			Xml::span( NULL, 'checkItCheckButtons' ).
			Xml::element( 'a', array(
				'href' => Xml::escapeJsString( "javascript: checkIt( 'movepages', false );" ) 
			), wfMsg( 'uncheck-all' ) ).
			" | ".
			Xml::element( 'a', array(
				'href' => Xml::escapeJsString( "javascript: checkIt( 'movepages', true );" ) 
			), wfMsg( 'check-all' ) ).
			Xml::closeElement( 'span' ).
			Xml::closeElement( 'td' ).
			Xml::closeElement( 'tr' ).
			Xml::openElement( 'td', array( 'align' => 'right', 'valign' => 'top' ) ).
			Xml::element( 'br', NULL, '' ).
			Xml::label( wfMsg( 'movereasonex' ), 'wpReason' ).
			Xml::closeElement( 'td' ).
			Xml::openElement( 'td', array( 'align' => 'left', 'valign' => 'top' ) ).
			Xml::element( 'br', NULL, '' ).
			Xml::openElement( 'textarea', 
				array( 'cols' => '60', 'rows' => '2', 'name' => 'wpReason', 'id' => 'wpReason' )
			).
			htmlspecialchars($this->reason).
			Xml::closeElement( 'textarea' ).
			Xml::closeElement( 'td' ).
			Xml::closeElement( 'tr' )
		);

		$wgOut->addHTML(
			( $del ?
				Xml::openElement( 'tr' ).
				Xml::openElement( 'td', array( 'align' => 'right' ) ).
				Xml::check(
					'wpConfirm',
					false,
					array( 'id' => 'wpConfirm', 'value' => 'true' )
				).
				Xml::closeElement( 'td' ).
				Xml::openElement( 'td', array( 'align' => 'left' ) ).
				Xml::label( wfMsg( 'delete_and_move_confirm_ex' ), 'wpConfirm' ).
				Xml::closeElement( 'td' ).
				Xml::closeElement( 'tr' )
			: '' ).
			Xml::openElement( 'tr' ).
			Xml::element( 'td' ).
			Xml::openElement( 'td', array( 'align' => 'left' ) ).
			Xml::submitButton(
				$del ? wfMsg( 'delete_and_move_ex' ) : wfMsg( 'movepageexbtn' ),
				array(
					'name' => $del ? 'wpDeleteAndMove' : 'wpMove',
				)
			).
			Xml::closeElement( 'td' ).
			Xml::closeElement( 'tr' ).
			Xml::closeElement( 'table' ).
			Xml::hidden( 'wpEditToken', $wgUser->editToken() ).
			Xml::closeElement( 'form' )
		);
		
		$this->showLogFragment( $oa, $wgOut );
	}

	function doSubmit() {
		global $wgOut, $wgUser, $wgRequest;
		$errors = array();
		
		if ( $wgUser->pingLimiter( 'move' ) ) {
			$wgOut->rateLimited();
			return;
		}

		# Variables beginning with 'o' for old article 'n' for new article

		$oa = Title::newFromText( $this->oldTitle )->getSubjectPage();
		$ot = $oa->getTalkPage();
		$na = Title::newFromText( $this->newTitle )->getSubjectPage();
		$nt = $na->getTalkPage();
		
		# Find the list of subpages and talkpages to move.
		$pageList = array();
		
		$dbr =& wfGetDB( DB_SLAVE );
		$res = $dbr->select( 'page',
			array( 'page_namespace', 'page_title' ),
			array(
				"`page_namespace` = {$oa->mNamespace} OR `page_namespace` = {$ot->mNamespace}",
				"`page_title` REGEXP '^".preg_quote($oa->mDbkeyform,"'")."(/|$)'"
			),
			NULL,
			array( 'ORDER BY' => '`page_namespace` ASC, `page_title` ASC' ) );
		while( $row = $dbr->fetchObject( $res ) )
			$pageList[] = Title::makeTitle( $row->page_namespace, $row->page_title );
		$dbr->freeResult( $res );
		
		$moved = array();
		foreach( $pageList as $title ) {#Turn page list into a Move Que.
			$o       = $title;
			$trimmed = preg_replace( '/^'.preg_quote($oa->mDbkeyform,'/').'/', '', $title->mDbkeyform );
			$n       = Title::makeTitle(
				$o->isTalkpage() ? Namespace::getTalk( $na->getNamespace() ) : Namespace::getSubject( $na->getNamespace() ), 
				$na->mDbkeyform.$trimmed );
			$doMove  = is_array( $this->selectedPages[$title->mNamespace] ) && in_array( $trimmed, $this->selectedPages[$title->mNamespace] );
			$key     = $title->mNamespace.'-'.$trimmed;
			if( $doMove ) $moveQue[] = (object)array( 'o' => $o, 'n' => $n, 'key' => $key );
		}
		foreach( $moveQue as $que ) {#Validate each move for errors before moving anything
			# don't allow moving to pages with # in
			if ( !$que->n || $que->n->getFragment() != '' ) {
				$errors[] = new MovepageExError( 'badtitletext', $que->o, $que->n );
			}
			
			#Check for valid movement
			$error = $que->o->isValidMoveOperation( $que->n, true );
			if( $error !== true ) {
				#Ignore a articleexists error if we are told to delete pages in the way
				if( $error != 'articleexists' ||
					$error == 'articleexists' && ( !$wgUser->isAllowed( 'delete' ) || !$this->deleteAndMove ) )
						$errors[] = new MovepageExError( $error, $que->o, $que->n );
			}
		}
		#Return if there are any errors
		if( count( $errors ) > 0 ) {
			$this->showForm( $errors );
			return;
		}
		foreach( $moveQue as $que ) {#Since there are no issues, run through the que
			# Delete to make way if requested
			if ( $que->n->exists() && $wgUser->isAllowed( 'delete' ) && $this->deleteAndMove ) {
				$article = new Article( $que->n );
				// This may output an error message and exit
				$article->doDelete( wfMsgForContent( 'delete_and_move_reason' ) );
			}
			$que->o->moveTo( $que->n, true, $this->reason );
			$moved[] = $que->key;
		}
		
		# Give back result to user.
		$q = '';
		foreach( $this->selectedPages as $ns => $keys )
			foreach( $keys as $key )
				$q .= "&selectedPages[{$ns}][]=".wfUrlencode($key);
		$success = SpecialPage::getTitleFor( 'MovepageEx' )->getFullURL(
			'action=success' .
			'&newns=' . $na->mNamespace .
			'&oldname=' . wfUrlencode( $oa->mDbkeyform ) .
			'&newname=' . wfUrlencode( $na->mDbkeyform ) .
			$q );
		
		$wgOut->redirect( $success );
	}

	function showSuccess() {
		global $wgOut, $wgRequest, $wgRawHtml;
		
		$wgOut->setPagetitle( wfMsg( 'movepageex' ) );
		$wgOut->setSubtitle( wfMsg( 'pagemovedsub' ) );
		
		$text = '';
		
		$oldName = $wgRequest->getVal('oldname');
		$newName = $wgRequest->getVal('newname');
		$oldNS   = $wgRequest->getVal('oldns');
		$newNS   = $wgRequest->getVal('newns');
		foreach( $this->selectedPages as $ns => $keys ) {
			foreach( $keys as $key ) {
				$o = Title::makeTitle( $ns, $oldName.$key );
				$n = Title::makeTitle(
					Namespace::isTalk( $ns ) ? Namespace::getTalk( $newNS ) : Namespace::getSubject( $newNS ),
					$newName.$key );
				$text .= wfMsg( 'pagemovedtext', $o->getFullText(), $n->getFullText() )."<br />";
			}
		}

		$allowHTML = $wgRawHtml;
		$wgRawHtml = false;
		$wgOut->addWikiText( $text );
		$wgRawHtml = $allowHTML;
	}
	
	function showLogFragment( $title, &$out ) {
		$out->addHtml( wfElement( 'h2', NULL, LogPage::logName( 'move' ) ) );
		$request = new FauxRequest( array( 'page' => $title->getPrefixedText(), 'type' => 'move' ) );
		$viewer = new LogViewer( new LogReader( $request ) );
		$viewer->showList( $out );
	}
	
}
class MovepageExError {
	private $mType, $mOldTitle, $mNewTitle;
	
	function MovepageExError( $type, $oldTitle, $newTitle ) {
		$this->mType     = $type;
		$this->mOldTitle = $oldTitle;
		$this->mNewTitle = $newTitle;
	}
	function getErrorType() { return $this->mType; }
	function getOldTitle() { return $this->mOldTitle; }
	function getNewTitle() { return $this->mNewTitle; }
	function isType( $err ) { return $err == $this->mType; }
}
?>