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),
$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.
$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
$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 );
$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'),
}
}
+ /**
+ * 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
return $title && $title->isDeleted() > 0;
}
+ /**
+ * 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.
*
/** isLocal inherited */
/** 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.
*
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;
+ }
+}