MediaWiki r34169 - Code Review

Jump to: navigation, search
Repository:MediaWiki
Revision:r34168‎ | r34169 (on ViewVC)‎ | r34170 >
Date:13:09, 3 May 2008
Author:vasilievvv
Status:old
Tags:
Comment:
* (bug 709) Cannot rename/move images and other media files.
Currently in experimental mode, use $wgAllowImageMoving to enable it.
Known issues:
* Doesn't work with rev_deleted
* May also have some security and caching issues.
Modified paths:

Diff [purge]

Index: trunk/phase3/includes/filerepo/LocalFile.php
===================================================================
--- trunk/phase3/includes/filerepo/LocalFile.php	(revision 34168)
+++ trunk/phase3/includes/filerepo/LocalFile.php	(revision 34169)
@@ -910,6 +910,39 @@
 	/** wasDeleted inherited */
 
 	/**
+	 * Move file to the new title
+	 *
+	 * Move current, old version and all thumbnails
+	 * to the new filename. Old file is deleted.
+	 *
+	 * Cache purging is done; checks for validity
+	 * and logging are caller's responsibility
+	 *
+	 * @param $target Title New file name
+	 * @return FileRepoStatus object.
+	 */
+	function move( $target ) {
+		$this->lock();
+		$dbw = $this->repo->getMasterDB();
+		$batch = new LocalFileMoveBatch( $this, $target, $dbw );
+		$batch->addCurrent();
+		$batch->addOlds();
+		if( !$this->repo->canTransformVia404() ) {
+			$batch->addThumbs();
+		}
+
+		$status = $batch->execute();
+		$this->purgeEverything();
+		$this->unlock();
+
+		// Now switch the object and repurge
+		$this->title = $target;
+		unset( $this->name );
+		$this->purgeEverything();
+		return $status;
+	}
+
+	/**
 	 * Delete all versions of the file.
 	 *
 	 * Moves the files into an archive directory (or deletes them)
@@ -1606,3 +1639,160 @@
 		return $status;
 	}
 }
+
+#------------------------------------------------------------------------------
+
+/**
+ * Helper class for file movement
+ */
+class LocalFileMoveBatch {
+	var $file, $cur, $olds, $archive, $thumbs, $target, $db;
+
+	function __construct( File $file, Title $target, Database $db ) {
+		$this->file = $file;
+		$this->target = $target;
+		$this->oldHash = $this->file->repo->getHashPath( $this->file->getName() );
+		$this->newHash = $this->file->repo->getHashPath( $this->target->getDbKey() );
+		$this->oldName = $this->file->getName();
+		$this->newName = $this->file->repo->getNameFromTitle( $this->target );
+		$this->oldRel = $this->oldHash . $this->oldName;
+		$this->newRel = $this->newHash . $this->newName;
+		$this->db = $db;
+	}
+
+	function addCurrent() {
+		$this->cur = array( $this->oldRel, $this->newRel );
+	}
+
+	function addThumbs() {
+		$this->thumbs = array();
+		$repo = $this->file->repo;
+		$thumbDirRel = 'thumb/' . $this->oldRel;
+		$thumbDir = $repo->getZonePath( 'public' ) . '/' . $thumbDirRel;
+		$newThumbDirRel = 'thumb/' . $this->newRel;
+		if( !is_dir( $thumbDir ) || !is_readable( $thumbDir ) ) {
+			$this->thumbs = array();
+			return;
+		} else {
+			$files = scandir( $thumbDir );
+			foreach( $files as $file ) {
+				if( $file == '.' || $file == '..' ) continue;
+				if( preg_match( '/^(\d+)px-/', $file, $matches ) ) {
+					list( $unused, $width ) = $matches;
+					$this->thumbs[] = array(
+						$thumbDirRel . '/' . $file,
+						$newThumbDirRel . '/' . $width . 'px-' . $this->newName
+					);
+				} else {
+					wfDebug( 'Strange file in thumbnail directory: ' . $thumbDirRel . '/' . $file );
+				}
+			}
+		}
+	}
+
+	function addOlds() {
+		$archiveBase = 'archive';
+		$this->olds = array();
+
+		$result = $this->db->select( 'oldimage',
+			array( 'oi_archive_name' ),
+			array( 'oi_name' => $this->oldName ),
+			__METHOD__
+		);
+		while( $row = $this->db->fetchObject( $result ) ) {
+			$oldname = $row->oi_archive_name;
+			$bits = explode( '!', $oldname, 2 );
+			if( count( $bits ) != 2 ) {
+				wfDebug( 'Invalid old file name: ' . $oldname );
+				continue;
+			}
+			list( $timestamp, $filename ) = $bits;
+			if( $this->oldName != $filename ) {
+				wfDebug( 'Invalid old file name:' . $oldName );
+				continue;
+			}
+			$this->olds[] = array(
+				"{$archiveBase}/{$this->oldHash}{$oldname}",
+				"{$archiveBase}/{$this->oldHash}{$timestamp}!{$this->newName}"
+			);
+		}
+		$this->db->freeResult( $result );
+	}
+
+	function execute() {
+		$repo = $this->file->repo;
+		$status = $repo->newGood();
+		$triplets = $this->getMoveTriplets();
+
+		$statusDb = $this->doDBUpdates();
+		$statusMove = $repo->storeBatch( $triplets, FSRepo::DELETE_SOURCE );
+		if( !$statusMove->isOk() ) {
+			$this->db->rollback();
+		}
+		$status->merge( $statusDb );
+		$status->merge( $statusMove );
+		return $status;
+	}
+
+	function doDBUpdates() {
+		$repo = $this->file->repo;
+		$status = $repo->newGood();
+		$dbw = $this->db;
+
+		// Update current image
+		$dbw->update( 
+			'image',
+			array( 'img_name' => $this->newName ),
+			array( 'img_name' => $this->oldName ),
+			__METHOD__
+		);
+		if( $dbw->affectedRows() ) {
+			$status->successCount++;
+		} else {
+			$status->failCount++;
+		}
+
+		// Update old images
+		$dbw->update(
+			'oldimage',
+			array(
+				'oi_name' => $this->newName,
+				'oi_archive_name = ' . $dbw->strreplace( 'oi_archive_name', $dbw->addQuotes($this->oldName), $dbw->addQuotes($this->newName) ),
+			),
+			array( 'oi_name' => $this->oldName ),
+			__METHOD__
+		);
+		$affected = $dbw->affectedRows();
+		$total = count( $this->olds );
+		$status->successCount += $affected;
+		$status->failCount += $total - $affected;
+
+		// Update deleted images
+		$dbw->update(
+			'filearchive',
+			array(
+				'fa_name' => $this->newName,
+				'fa_archive_name = ' . $dbw->strreplace( 'fa_archive_name', $dbw->addQuotes($this->oldName), $dbw->addQuotes($this->newName) ),
+			),
+			array( 'fa_name' => $this->oldName ),
+			__METHOD__
+		);
+		$affected = $dbw->affectedRows();
+		$total = count( $this->olds );
+		$status->successCount += $affected;
+		$status->failCount += $total - $affected;
+
+		return $status;
+	}
+
+	// Generates triplets for FSRepo::storeBatch()
+	function getMoveTriplets() {
+		$moves = array_merge( array( $this->cur ), $this->olds, $this->thumbs );
+		$triplets = array();	// The format is: (srcUrl,destZone,desrUrl)
+		foreach( $moves as $move ) {
+			$srcUrl = $this->file->repo->getVirtualUrl() . '/public/' . rawurlencode( $move[0] );
+			$triplets[] = array( $srcUrl, 'public', $move[1] );
+		}
+		return $triplets;
+	}
+}
Index: trunk/phase3/includes/filerepo/File.php
===================================================================
--- trunk/phase3/includes/filerepo/File.php	(revision 34168)
+++ trunk/phase3/includes/filerepo/File.php	(revision 34169)
@@ -90,6 +90,21 @@
 	}
 
 	/**
+	 * Checks if file extensions are compatible
+	 *
+	 * @param $old File Old file
+	 * @param $new string New name
+	 */
+	static function checkExtesnionCompatibility( File $old, $new ) {
+		$oldMime = $old->getMimeType();
+		$n = strrpos( $new, '.' );
+		$newExt = self::normalizeExtension(
+			$n ? substr( $new, $n + 1 ) : '' );
+		$mimeMagic = MimeMagic::singleton();
+		return $mimeMagic->isMatchingExtension( $newExt, $oldMime );
+	}
+
+	/**
 	 * Upgrade the database row if there is one
 	 * Called by ImagePage
 	 * STUB
@@ -917,6 +932,22 @@
 	}
 
 	/**
+	 * Move file to the new title
+	 *
+	 * Move current, old version and all thumbnails
+	 * to the new filename. Old file is deleted.
+	 *
+	 * Cache purging is done; checks for validity
+	 * and logging are caller's responsibility
+	 *
+	 * @param $target Title New file name
+	 * @return FileRepoStatus object.
+	 */
+	 function move( $target ) {
+		$this->readOnlyError();
+	 }
+
+	/**
 	 * Delete all versions of the file.
 	 *
 	 * Moves the files into an archive directory (or deletes them)
Index: trunk/phase3/includes/Title.php
===================================================================
--- trunk/phase3/includes/Title.php	(revision 34168)
+++ trunk/phase3/includes/Title.php	(revision 34169)
@@ -2388,6 +2388,19 @@
 			return 'badarticleerror';
 		}
 
+		// Image-specific checks
+		if( $this->getNamespace() == NS_IMAGE ) {
+			$file = wfLocalFile( $this );
+			if( $file->exists() ) {
+				if( $nt->getNamespace() != NS_IMAGE ) {
+					return 'imagenocrossnamespace';
+				}
+				if( !File::checkExtesnionCompatibility( $file, $nt->getDbKey() ) ) {
+					return 'imagetypemismatch';
+				}
+			}
+		}
+
 		if ( $auth ) {
 			global $wgUser;
 			$errors = array_merge($this->getUserPermissionsErrors('move', $wgUser),
@@ -2439,12 +2452,15 @@
 
 		$pageid = $this->getArticleID();
 		if( $nt->exists() ) {
-			$this->moveOverExistingRedirect( $nt, $reason, $createRedirect );
+			$err = $this->moveOverExistingRedirect( $nt, $reason, $createRedirect );
 			$pageCountChange = ($createRedirect ? 0 : -1);
 		} else { # Target didn't exist, do normal move.
-			$this->moveToNewTitle( $nt, $reason, $createRedirect );
+			$err = $this->moveToNewTitle( $nt, $reason, $createRedirect );
 			$pageCountChange = ($createRedirect ? 1 : 0);
 		}
+		if( is_string( $err ) ) {
+			return $err;
+		}
 		$redirid = $this->getArticleID();
 
 		// Category memberships include a sort key which may be customized.
@@ -2541,6 +2557,17 @@
 		$oldid = $this->getArticleID();
 		$dbw = wfGetDB( DB_MASTER );
 
+		# Move an image if it is
+		if( $this->getNamespace() == NS_IMAGE ) {
+			$file = wfLocalFile( $this );
+			if( $file->exists() ) {
+				$status = $file->move( $nt );
+				if( !$status->isOk() ) {
+					return $status->getWikiText();
+				}
+			}
+		}
+
 		# Delete the old redirect. We don't save it to history since
 		# by definition if we've got here it's rather uninteresting.
 		# We have to remove it so that the next step doesn't trigger
@@ -2636,6 +2663,17 @@
 		$dbw = wfGetDB( DB_MASTER );
 		$now = $dbw->timestamp();
 
+		# Move an image if it is
+		if( $this->getNamespace() == NS_IMAGE ) {
+			$file = wfLocalFile( $this );
+			if( $file->exists() ) {
+				$status = $file->move( $nt );
+				if( !$status->isOk() ) {
+					return $status->getWikiText();
+				}
+			}
+		}
+
 		# Save a null revision in the page's history notifying of the move
 		$nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true );
 		$nullRevId = $nullRevision->insertOn( $dbw );
@@ -2701,6 +2739,15 @@
 		$fname = 'Title::isValidMoveTarget';
 		$dbw = wfGetDB( DB_MASTER );
 
+		# Is it an existsing file?
+		if( $nt->getNamespace() == NS_IMAGE ) {
+			$file = wfLocalFile( $nt );
+			if( $file->exists() ) {
+				wfDebug( __METHOD__ . ": file exists\n" );
+				return false;
+			}
+		}
+
 		# Is it a redirect?
 		$id  = $nt->getArticleID();
 		$obj = $dbw->selectRow( array( 'page', 'revision', 'text'),
Index: trunk/phase3/includes/Namespace.php
===================================================================
--- trunk/phase3/includes/Namespace.php	(revision 34168)
+++ trunk/phase3/includes/Namespace.php	(revision 34169)
@@ -51,7 +51,8 @@
 	 * @return bool
 	 */
 	public static function isMovable( $index ) {
-		return !( $index < NS_MAIN || $index == NS_IMAGE  || $index == NS_CATEGORY );
+		global $wgAllowImageMoving;
+		return !( $index < NS_MAIN || ($index == NS_IMAGE && !$wgAllowImageMoving)  || $index == NS_CATEGORY );
 	}
 
 	/**
Index: trunk/phase3/includes/DefaultSettings.php
===================================================================
--- trunk/phase3/includes/DefaultSettings.php	(revision 34168)
+++ trunk/phase3/includes/DefaultSettings.php	(revision 34169)
@@ -1526,6 +1526,9 @@
   */
 $wgAllowExternalImagesFrom = '';
 
+/** Allows to move images and other media files. Experemintal, not sure if it always works */
+$wgAllowImageMoving = false;
+
 /** Disable database-intensive features */
 $wgMiserMode = false;
 /** Disable all query pages if miser mode is on, not just some */
Index: trunk/phase3/includes/Database.php
===================================================================
--- trunk/phase3/includes/Database.php	(revision 34168)
+++ trunk/phase3/includes/Database.php	(revision 34169)
@@ -1656,6 +1656,18 @@
 	}
 
 	/**
+	 * Returns a comand for str_replace function in SQL query.
+	 * Uses REPLACE() in MySQL
+	 *
+	 * @param string $orig String or column to modify
+	 * @param string $old String or column to seek
+	 * @param string $new String or column to replace with
+	 */
+	function strreplace( $orig, $old, $new ) {
+		return "REPLACE({$orig}, {$old}, {$new})";
+	}
+
+	/**
 	 * Determines if the last failure was due to a deadlock
 	 */
 	function wasDeadlock() {
Index: trunk/phase3/RELEASE-NOTES
===================================================================
--- trunk/phase3/RELEASE-NOTES	(revision 34168)
+++ trunk/phase3/RELEASE-NOTES	(revision 34169)
@@ -96,6 +96,7 @@
   and local one
 * Update documentation links in auto-generated LocalSettings.php
 * (bug 13584) The new hook SkinTemplateToolboxEnd was added.
+* (bug 709) Cannot rename/move images and other media files [EXPERIMENTAL]
 
 === Bug fixes in 1.13 ===
 

Status & tagging log

  • 15:26, 12 September 2011 Meno25 (Talk | contribs) changed the status of r34169 [removed: ok added: old]
Personal tools
Namespaces
Variants
Views
Actions
Site
Support
Download
Development
Communication
Toolbox