/** * Update the images's fa_deleted field * @param ArchivedFile $file * @param int $bitfield new rev_deleted bitfield value */ function updateArchFiles($file, $bitfield) { $this->dbw->update('filearchive', array('fa_deleted' => $bitfield), array('fa_id' => $file->getId()), __METHOD__); }
/** * Run the transaction, except the cleanup batch. * The cleanup batch should be run in a separate transaction, because it locks different * rows and there's no need to keep the image row locked while it's acquiring those locks * The caller may have its own transaction open. * So we save the batch and let the caller call cleanup() * @return FileRepoStatus */ function execute() { global $wgLang; if (!$this->all && !$this->ids) { // Do nothing return $this->file->repo->newGood(); } $exists = $this->file->lock(); $dbw = $this->file->repo->getMasterDB(); $status = $this->file->repo->newGood(); // Fetch all or selected archived revisions for the file, // sorted from the most recent to the oldest. $conditions = array('fa_name' => $this->file->getName()); if (!$this->all) { $conditions['fa_id'] = $this->ids; } $result = $dbw->select('filearchive', ArchivedFile::selectFields(), $conditions, __METHOD__, array('ORDER BY' => 'fa_timestamp DESC')); $idsPresent = array(); $storeBatch = array(); $insertBatch = array(); $insertCurrent = false; $deleteIds = array(); $first = true; $archiveNames = array(); foreach ($result as $row) { $idsPresent[] = $row->fa_id; if ($row->fa_name != $this->file->getName()) { $status->error('undelete-filename-mismatch', $wgLang->timeanddate($row->fa_timestamp)); $status->failCount++; continue; } if ($row->fa_storage_key == '') { // Revision was missing pre-deletion $status->error('undelete-bad-store-key', $wgLang->timeanddate($row->fa_timestamp)); $status->failCount++; continue; } $deletedRel = $this->file->repo->getDeletedHashPath($row->fa_storage_key) . $row->fa_storage_key; $deletedUrl = $this->file->repo->getVirtualUrl() . '/deleted/' . $deletedRel; if (isset($row->fa_sha1)) { $sha1 = $row->fa_sha1; } else { // old row, populate from key $sha1 = LocalRepo::getHashFromKey($row->fa_storage_key); } # Fix leading zero if (strlen($sha1) == 32 && $sha1[0] == '0') { $sha1 = substr($sha1, 1); } if (is_null($row->fa_major_mime) || $row->fa_major_mime == 'unknown' || is_null($row->fa_minor_mime) || $row->fa_minor_mime == 'unknown' || is_null($row->fa_media_type) || $row->fa_media_type == 'UNKNOWN' || is_null($row->fa_metadata)) { // Refresh our metadata // Required for a new current revision; nice for older ones too. :) $props = RepoGroup::singleton()->getFileProps($deletedUrl); } else { $props = array('minor_mime' => $row->fa_minor_mime, 'major_mime' => $row->fa_major_mime, 'media_type' => $row->fa_media_type, 'metadata' => $row->fa_metadata); } if ($first && !$exists) { // This revision will be published as the new current version $destRel = $this->file->getRel(); $insertCurrent = array('img_name' => $row->fa_name, 'img_size' => $row->fa_size, 'img_width' => $row->fa_width, 'img_height' => $row->fa_height, 'img_metadata' => $props['metadata'], 'img_bits' => $row->fa_bits, 'img_media_type' => $props['media_type'], 'img_major_mime' => $props['major_mime'], 'img_minor_mime' => $props['minor_mime'], 'img_description' => $row->fa_description, 'img_user' => $row->fa_user, 'img_user_text' => $row->fa_user_text, 'img_timestamp' => $row->fa_timestamp, 'img_sha1' => $sha1); // The live (current) version cannot be hidden! if (!$this->unsuppress && $row->fa_deleted) { $status->fatal('undeleterevdel'); $this->file->unlock(); return $status; } } else { $archiveName = $row->fa_archive_name; if ($archiveName == '') { // This was originally a current version; we // have to devise a new archive name for it. // Format is <timestamp of archiving>!<name> $timestamp = wfTimestamp(TS_UNIX, $row->fa_deleted_timestamp); do { $archiveName = wfTimestamp(TS_MW, $timestamp) . '!' . $row->fa_name; $timestamp++; } while (isset($archiveNames[$archiveName])); } $archiveNames[$archiveName] = true; $destRel = $this->file->getArchiveRel($archiveName); $insertBatch[] = array('oi_name' => $row->fa_name, 'oi_archive_name' => $archiveName, 'oi_size' => $row->fa_size, 'oi_width' => $row->fa_width, 'oi_height' => $row->fa_height, 'oi_bits' => $row->fa_bits, 'oi_description' => $row->fa_description, 'oi_user' => $row->fa_user, 'oi_user_text' => $row->fa_user_text, 'oi_timestamp' => $row->fa_timestamp, 'oi_metadata' => $props['metadata'], 'oi_media_type' => $props['media_type'], 'oi_major_mime' => $props['major_mime'], 'oi_minor_mime' => $props['minor_mime'], 'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted, 'oi_sha1' => $sha1); } $deleteIds[] = $row->fa_id; if (!$this->unsuppress && $row->fa_deleted & File::DELETED_FILE) { // private files can stay where they are $status->successCount++; } else { $storeBatch[] = array($deletedUrl, 'public', $destRel); $this->cleanupBatch[] = $row->fa_storage_key; } $first = false; } unset($result); // Add a warning to the status object for missing IDs $missingIds = array_diff($this->ids, $idsPresent); foreach ($missingIds as $id) { $status->error('undelete-missing-filearchive', $id); } // Remove missing files from batch, so we don't get errors when undeleting them $storeBatch = $this->removeNonexistentFiles($storeBatch); // Run the store batch // Use the OVERWRITE_SAME flag to smooth over a common error $storeStatus = $this->file->repo->storeBatch($storeBatch, FileRepo::OVERWRITE_SAME); $status->merge($storeStatus); if (!$status->isGood()) { // Even if some files could be copied, fail entirely as that is the // easiest thing to do without data loss $this->cleanupFailedBatch($storeStatus, $storeBatch); $status->ok = false; $this->file->unlock(); return $status; } // Run the DB updates // Because we have locked the image row, key conflicts should be rare. // If they do occur, we can roll back the transaction at this time with // no data loss, but leaving unregistered files scattered throughout the // public zone. // This is not ideal, which is why it's important to lock the image row. if ($insertCurrent) { $dbw->insert('image', $insertCurrent, __METHOD__); } if ($insertBatch) { $dbw->insert('oldimage', $insertBatch, __METHOD__); } if ($deleteIds) { $dbw->delete('filearchive', array('fa_id' => $deleteIds), __METHOD__); } // If store batch is empty (all files are missing), deletion is to be considered successful if ($status->successCount > 0 || !$storeBatch) { if (!$exists) { wfDebug(__METHOD__ . " restored {$status->successCount} items, creating a new current\n"); DeferredUpdates::addUpdate(SiteStatsUpdate::factory(array('images' => 1))); $this->file->purgeEverything(); } else { wfDebug(__METHOD__ . " restored {$status->successCount} as archived versions\n"); $this->file->purgeDescription(); $this->file->purgeHistory(); } } $this->file->unlock(); return $status; }
/** * @param IDatabase $db * @return mixed */ public function doQuery($db) { $ids = array_map('intval', $this->ids); return $db->select('filearchive', ArchivedFile::selectFields(), array('fa_name' => $this->title->getDBkey(), 'fa_id' => $ids), __METHOD__, array('ORDER BY' => 'fa_id DESC')); }
/** * Check for non fatal problems with the file. * * This should not assume that mTempPath is set. * * @return Array of warnings */ public function checkWarnings() { global $wgLang; wfProfileIn(__METHOD__); $warnings = array(); $localFile = $this->getLocalFile(); $filename = $localFile->getName(); /** * Check whether the resulting filename is different from the desired one, * but ignore things like ucfirst() and spaces/underscore things */ $comparableName = str_replace(' ', '_', $this->mDesiredDestName); $comparableName = Title::capitalize($comparableName, NS_FILE); if ($this->mDesiredDestName != $filename && $comparableName != $filename) { $warnings['badfilename'] = $filename; // Debugging for bug 62241 wfDebugLog('upload', "Filename: '{$filename}', mDesiredDestName: '{$this->mDesiredDestName}', comparableName: '{$comparableName}'"); } // Check whether the file extension is on the unwanted list global $wgCheckFileExtensions, $wgFileExtensions; if ($wgCheckFileExtensions) { $extensions = array_unique($wgFileExtensions); if (!$this->checkFileExtension($this->mFinalExtension, $extensions)) { $warnings['filetype-unwanted-type'] = array($this->mFinalExtension, $wgLang->commaList($extensions), count($extensions)); } } global $wgUploadSizeWarning; if ($wgUploadSizeWarning && $this->mFileSize > $wgUploadSizeWarning) { $warnings['large-file'] = array($wgUploadSizeWarning, $this->mFileSize); } if ($this->mFileSize == 0) { $warnings['emptyfile'] = true; } $exists = self::getExistsWarning($localFile); if ($exists !== false) { $warnings['exists'] = $exists; } // Check dupes against existing files $hash = $this->getTempFileSha1Base36(); $dupes = RepoGroup::singleton()->findBySha1($hash); $title = $this->getTitle(); // Remove all matches against self foreach ($dupes as $key => $dupe) { if ($title->equals($dupe->getTitle())) { unset($dupes[$key]); } } if ($dupes) { $warnings['duplicate'] = $dupes; } // Check dupes against archives $archivedImage = new ArchivedFile(null, 0, "{$hash}.{$this->mFinalExtension}"); if ($archivedImage->getID() > 0) { if ($archivedImage->userCan(File::DELETED_FILE)) { $warnings['duplicate-archive'] = $archivedImage->getName(); } else { $warnings['duplicate-archive'] = ''; } } wfProfileOut(__METHOD__); return $warnings; }
private function formatFileRow($row, $sk) { global $wgUser, $wgLang; $file = ArchivedFile::newFromRow($row); $ts = wfTimestamp(TS_MW, $row->fa_timestamp); if ($this->mAllowed && $row->fa_storage_key) { $checkBox = Xml::check("fileid" . $row->fa_id); $key = urlencode($row->fa_storage_key); $target = urlencode($this->mTarget); $titleObj = SpecialPage::getTitleFor("Undelete"); $pageLink = $this->getFileLink($file, $titleObj, $ts, $key, $sk); } else { $checkBox = ''; $pageLink = $wgLang->timeanddate($ts, true); } $userLink = $this->getFileUser($file, $sk); $data = wfMsg('widthheight', $wgLang->formatNum($row->fa_width), $wgLang->formatNum($row->fa_height)) . ' (' . wfMsg('nbytes', $wgLang->formatNum($row->fa_size)) . ')'; $data = htmlspecialchars($data); $comment = $this->getFileComment($file, $sk); $revdlink = ''; if ($wgUser->isAllowed('deleterevision')) { $revdel = SpecialPage::getTitleFor('Revisiondelete'); if (!$file->userCan(File::DELETED_RESTRICTED)) { // If revision was hidden from sysops $del = wfMsgHtml('rev-delundel'); } else { $del = $sk->makeKnownLinkObj($revdel, wfMsgHtml('rev-delundel'), 'target=' . $this->mTargetObj->getPrefixedUrl() . '&fileid=' . $row->fa_id); // Bolden oversighted content if ($file->isDeleted(File::DELETED_RESTRICTED)) { $del = "<strong>{$del}</strong>"; } } $revdlink = "<tt>(<small>{$del}</small>)</tt>"; } return "<li>{$checkBox} {$revdlink} {$pageLink} . . {$userLink} {$data} {$comment}</li>\n"; }
/** * Loads a file object from the filearchive table * * @param stdClass $row * @return ArchivedFile */ public static function newFromRow($row) { $file = new ArchivedFile(Title::makeTitle(NS_FILE, $row->fa_name)); $file->loadFromRow($row); return $file; }
/** * Check for non fatal problems with the file. * * This should not assume that mTempPath is set. * * @return array Array of warnings */ public function checkWarnings() { global $wgLang; $warnings = []; $localFile = $this->getLocalFile(); $localFile->load(File::READ_LATEST); $filename = $localFile->getName(); /** * Check whether the resulting filename is different from the desired one, * but ignore things like ucfirst() and spaces/underscore things */ $comparableName = str_replace(' ', '_', $this->mDesiredDestName); $comparableName = Title::capitalize($comparableName, NS_FILE); if ($this->mDesiredDestName != $filename && $comparableName != $filename) { $warnings['badfilename'] = $filename; } // Check whether the file extension is on the unwanted list global $wgCheckFileExtensions, $wgFileExtensions; if ($wgCheckFileExtensions) { $extensions = array_unique($wgFileExtensions); if (!$this->checkFileExtension($this->mFinalExtension, $extensions)) { $warnings['filetype-unwanted-type'] = [$this->mFinalExtension, $wgLang->commaList($extensions), count($extensions)]; } } global $wgUploadSizeWarning; if ($wgUploadSizeWarning && $this->mFileSize > $wgUploadSizeWarning) { $warnings['large-file'] = [$wgUploadSizeWarning, $this->mFileSize]; } if ($this->mFileSize == 0) { $warnings['empty-file'] = true; } $hash = $this->getTempFileSha1Base36(); $exists = self::getExistsWarning($localFile); if ($exists !== false) { $warnings['exists'] = $exists; // check if file is an exact duplicate of current file version if ($hash === $localFile->getSha1()) { $warnings['no-change'] = $localFile; } // check if file is an exact duplicate of older versions of this file $history = $localFile->getHistory(); foreach ($history as $oldFile) { if ($hash === $oldFile->getSha1()) { $warnings['duplicate-version'][] = $oldFile; } } } if ($localFile->wasDeleted() && !$localFile->exists()) { $warnings['was-deleted'] = $filename; } // Check dupes against existing files $dupes = RepoGroup::singleton()->findBySha1($hash); $title = $this->getTitle(); // Remove all matches against self foreach ($dupes as $key => $dupe) { if ($title->equals($dupe->getTitle())) { unset($dupes[$key]); } } if ($dupes) { $warnings['duplicate'] = $dupes; } // Check dupes against archives $archivedFile = new ArchivedFile(null, 0, '', $hash); if ($archivedFile->getID() > 0) { if ($archivedFile->userCan(File::DELETED_FILE)) { $warnings['duplicate-archive'] = $archivedFile->getName(); } else { $warnings['duplicate-archive'] = ''; } } return $warnings; }
public function execute() { $user = $this->getUser(); // Before doing anything at all, let's check permissions if (!$user->isAllowed('deletedhistory')) { $this->dieUsage('You don\'t have permission to view deleted file information', 'permissiondenied'); } $db = $this->getDB(); $params = $this->extractRequestParams(); $prop = array_flip($params['prop']); $fld_sha1 = isset($prop['sha1']); $fld_timestamp = isset($prop['timestamp']); $fld_user = isset($prop['user']); $fld_size = isset($prop['size']); $fld_dimensions = isset($prop['dimensions']); $fld_description = isset($prop['description']) || isset($prop['parseddescription']); $fld_mime = isset($prop['mime']); $fld_mediatype = isset($prop['mediatype']); $fld_metadata = isset($prop['metadata']); $fld_bitdepth = isset($prop['bitdepth']); $fld_archivename = isset($prop['archivename']); $this->addTables('filearchive'); $this->addFields(ArchivedFile::selectFields()); $this->addFields(array('fa_id', 'fa_name', 'fa_timestamp', 'fa_deleted')); $this->addFieldsIf('fa_sha1', $fld_sha1); $this->addFieldsIf(array('fa_user', 'fa_user_text'), $fld_user); $this->addFieldsIf(array('fa_height', 'fa_width', 'fa_size'), $fld_dimensions || $fld_size); $this->addFieldsIf('fa_description', $fld_description); $this->addFieldsIf(array('fa_major_mime', 'fa_minor_mime'), $fld_mime); $this->addFieldsIf('fa_media_type', $fld_mediatype); $this->addFieldsIf('fa_metadata', $fld_metadata); $this->addFieldsIf('fa_bits', $fld_bitdepth); $this->addFieldsIf('fa_archive_name', $fld_archivename); if (!is_null($params['continue'])) { $cont = explode('|', $params['continue']); $this->dieContinueUsageIf(count($cont) != 3); $op = $params['dir'] == 'descending' ? '<' : '>'; $cont_from = $db->addQuotes($cont[0]); $cont_timestamp = $db->addQuotes($db->timestamp($cont[1])); $cont_id = (int) $cont[2]; $this->dieContinueUsageIf($cont[2] !== (string) $cont_id); $this->addWhere("fa_name {$op} {$cont_from} OR " . "(fa_name = {$cont_from} AND " . "(fa_timestamp {$op} {$cont_timestamp} OR " . "(fa_timestamp = {$cont_timestamp} AND " . "fa_id {$op}= {$cont_id} )))"); } // Image filters $dir = $params['dir'] == 'descending' ? 'older' : 'newer'; $from = $params['from'] === null ? null : $this->titlePartToKey($params['from'], NS_FILE); $to = $params['to'] === null ? null : $this->titlePartToKey($params['to'], NS_FILE); $this->addWhereRange('fa_name', $dir, $from, $to); if (isset($params['prefix'])) { $this->addWhere('fa_name' . $db->buildLike($this->titlePartToKey($params['prefix'], NS_FILE), $db->anyString())); } $sha1Set = isset($params['sha1']); $sha1base36Set = isset($params['sha1base36']); if ($sha1Set || $sha1base36Set) { $sha1 = false; if ($sha1Set) { $sha1 = strtolower($params['sha1']); if (!$this->validateSha1Hash($sha1)) { $this->dieUsage('The SHA1 hash provided is not valid', 'invalidsha1hash'); } $sha1 = wfBaseConvert($sha1, 16, 36, 31); } elseif ($sha1base36Set) { $sha1 = strtolower($params['sha1base36']); if (!$this->validateSha1Base36Hash($sha1)) { $this->dieUsage('The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash'); } } if ($sha1) { $this->addWhereFld('fa_sha1', $sha1); } } // Exclude files this user can't view. if (!$user->isAllowed('deletedtext')) { $bitmask = File::DELETED_FILE; } elseif (!$user->isAllowedAny('suppressrevision', 'viewsuppressed')) { $bitmask = File::DELETED_FILE | File::DELETED_RESTRICTED; } else { $bitmask = 0; } if ($bitmask) { $this->addWhere($this->getDB()->bitAnd('fa_deleted', $bitmask) . " != {$bitmask}"); } $limit = $params['limit']; $this->addOption('LIMIT', $limit + 1); $sort = $params['dir'] == 'descending' ? ' DESC' : ''; $this->addOption('ORDER BY', array('fa_name' . $sort, 'fa_timestamp' . $sort, 'fa_id' . $sort)); $res = $this->select(__METHOD__); $count = 0; $result = $this->getResult(); foreach ($res as $row) { if (++$count > $limit) { // We've reached the one extra which shows that there are // additional pages to be had. Stop here... $this->setContinueEnumParameter('continue', "{$row->fa_name}|{$row->fa_timestamp}|{$row->fa_id}"); break; } $file = array(); $file['id'] = (int) $row->fa_id; $file['name'] = $row->fa_name; $title = Title::makeTitle(NS_FILE, $row->fa_name); self::addTitleInfo($file, $title); if ($fld_description && Revision::userCanBitfield($row->fa_deleted, File::DELETED_COMMENT, $user)) { $file['description'] = $row->fa_description; if (isset($prop['parseddescription'])) { $file['parseddescription'] = Linker::formatComment($row->fa_description, $title); } } if ($fld_user && Revision::userCanBitfield($row->fa_deleted, File::DELETED_USER, $user)) { $file['userid'] = (int) $row->fa_user; $file['user'] = $row->fa_user_text; } if ($fld_sha1) { $file['sha1'] = wfBaseConvert($row->fa_sha1, 36, 16, 40); } if ($fld_timestamp) { $file['timestamp'] = wfTimestamp(TS_ISO_8601, $row->fa_timestamp); } if ($fld_size || $fld_dimensions) { $file['size'] = $row->fa_size; $pageCount = ArchivedFile::newFromRow($row)->pageCount(); if ($pageCount !== false) { $file['pagecount'] = $pageCount; } $file['height'] = $row->fa_height; $file['width'] = $row->fa_width; } if ($fld_mediatype) { $file['mediatype'] = $row->fa_media_type; } if ($fld_metadata) { $file['metadata'] = $row->fa_metadata ? ApiQueryImageInfo::processMetaData(unserialize($row->fa_metadata), $result) : null; } if ($fld_bitdepth) { $file['bitdepth'] = $row->fa_bits; } if ($fld_mime) { $file['mime'] = "{$row->fa_major_mime}/{$row->fa_minor_mime}"; } if ($fld_archivename && !is_null($row->fa_archive_name)) { $file['archivename'] = $row->fa_archive_name; } if ($row->fa_deleted & File::DELETED_FILE) { $file['filehidden'] = true; } if ($row->fa_deleted & File::DELETED_COMMENT) { $file['commenthidden'] = true; } if ($row->fa_deleted & File::DELETED_USER) { $file['userhidden'] = true; } if ($row->fa_deleted & File::DELETED_RESTRICTED) { // This file is deleted for normal admins $file['suppressed'] = true; } $fit = $result->addValue(array('query', $this->getModuleName()), null, $file); if (!$fit) { $this->setContinueEnumParameter('continue', "{$row->fa_name}|{$row->fa_timestamp}|{$row->fa_id}"); break; } } $result->addIndexedTagName(array('query', $this->getModuleName()), 'fa'); }
protected function scrubVersion(ArchivedFile $archivedFile) { $key = $archivedFile->getStorageKey(); $name = $archivedFile->getName(); $ts = $archivedFile->getTimestamp(); $repo = RepoGroup::singleton()->getLocalRepo(); $path = $repo->getZonePath('deleted') . '/' . $repo->getDeletedHashPath($key) . $key; if ($this->hasOption('delete')) { $status = $repo->getBackend()->delete(['src' => $path]); if ($status->isOK()) { $this->output("Deleted version '{$key}' ({$ts}) of file '{$name}'\n"); } else { $this->output("Failed to delete version '{$key}' ({$ts}) of file '{$name}'\n"); $this->output(print_r($status->getErrorsArray(), true)); } } else { $this->output("Would delete version '{$key}' ({$ts}) of file '{$name}'\n"); } }
function purgeArchivedFilesSQL($whereSQL) { # Purge files of archived images # lance.gatlin@gmail.com: TESTME $archivedImages_rows = $this->dbw->select('filearchive', array('*'), array($whereSQL)); if ($archivedImages_rows->numRows() == 0) { return; } foreach ($archivedImages_rows as $row) { # Images that have been deleted use ArchivedFile path => $IP/images/deleted $archivedFile = ArchivedFile::newFromRow($row); // No path helper in ArchivedFile class // Path code taken from DeleteArchiveFiles maintenance class $key = $archivedFile->getKey(); $path = $this->repo->getZonePath('deleted') . '/' . $this->repo->getDeletedHashPath($key) . $key; if ($path !== false && file_exists($path)) { unlink($path); } } # Purge the archived images from database # lance.gatlin@gmail.com: TESTME $this->dbw->delete('filearchive', array($whereSQL)); }
public function __construct($list, $row) { RevDelItem::__construct($list, $row); $this->file = ArchivedFile::newFromRow($row); $this->lockFile = RepoGroup::singleton()->getLocalRepo()->newFile($row->fa_name); }
private function formatFileRow($row, $sk) { global $wgUser, $wgLang; $file = ArchivedFile::newFromRow($row); $ts = wfTimestamp(TS_MW, $row->fa_timestamp); if ($this->mAllowed && $row->fa_storage_key) { $checkBox = Xml::check("fileid" . $row->fa_id); $key = urlencode($row->fa_storage_key); $target = urlencode($this->mTarget); $titleObj = SpecialPage::getTitleFor("Undelete"); $pageLink = $this->getFileLink($file, $titleObj, $ts, $key, $sk); } else { $checkBox = ''; $pageLink = $wgLang->timeanddate($ts, true); } $userLink = $this->getFileUser($file, $sk); $data = wfMsg('widthheight', $wgLang->formatNum($row->fa_width), $wgLang->formatNum($row->fa_height)) . ' (' . wfMsg('nbytes', $wgLang->formatNum($row->fa_size)) . ')'; $data = htmlspecialchars($data); $comment = $this->getFileComment($file, $sk); // Add show/hide deletion links if available $canHide = $wgUser->isAllowed('deleterevision'); if ($canHide || $file->getVisibility() && $wgUser->isAllowed('deletedhistory')) { if (!$file->userCan(File::DELETED_RESTRICTED)) { $revdlink = $sk->revDeleteLinkDisabled($canHide); // revision was hidden from sysops } else { $query = array('type' => 'filearchive', 'target' => $this->mTargetObj->getPrefixedDBkey(), 'ids' => $row->fa_id); $revdlink = $sk->revDeleteLink($query, $file->isDeleted(File::DELETED_RESTRICTED), $canHide); } } else { $revdlink = ''; } return "<li>{$checkBox} {$revdlink} {$pageLink} . . {$userLink} {$data} {$comment}</li>\n"; }
/** * Check for duplicate files and throw up a warning before the upload * completes. */ function getDupeWarning($tempfile, $extension, $destinationTitle) { $hash = File::sha1Base36($tempfile); $dupes = RepoGroup::singleton()->findBySha1($hash); $archivedImage = new ArchivedFile(null, 0, $hash . ".{$extension}"); if ($dupes) { global $wgOut; $msg = "<gallery>"; foreach ($dupes as $file) { $title = $file->getTitle(); # Don't throw the warning when the titles are the same, it's a reupload # and highly redundant. if (!$title->equals($destinationTitle) || !$this->mForReUpload) { $msg .= $title->getPrefixedText() . "|" . $title->getText() . "\n"; } } $msg .= "</gallery>"; return "<li>" . wfMsgExt("file-exists-duplicate", array("parse"), count($dupes)) . $wgOut->parse($msg) . "</li>\n"; } elseif ($archivedImage->getID() > 0) { global $wgOut; $name = Title::makeTitle(NS_FILE, $archivedImage->getName())->getPrefixedText(); return Xml::tags('li', null, wfMsgExt('file-deleted-duplicate', array('parseinline'), array($name))); } else { return ''; } }
public function execute() { $user = $this->getUser(); // Before doing anything at all, let's check permissions if (!$user->isAllowed('deletedhistory')) { $this->dieUsage('You don\'t have permission to view deleted file information', 'permissiondenied'); } $db = $this->getDB(); $params = $this->extractRequestParams(); $prop = array_flip($params['prop']); $fld_sha1 = isset($prop['sha1']); $fld_timestamp = isset($prop['timestamp']); $fld_user = isset($prop['user']); $fld_size = isset($prop['size']); $fld_dimensions = isset($prop['dimensions']); $fld_description = isset($prop['description']) || isset($prop['parseddescription']); $fld_mime = isset($prop['mime']); $fld_mediatype = isset($prop['mediatype']); $fld_metadata = isset($prop['metadata']); $fld_bitdepth = isset($prop['bitdepth']); $fld_archivename = isset($prop['archivename']); $this->addTables('filearchive'); $this->addFields(array('fa_name', 'fa_deleted')); $this->addFieldsIf('fa_storage_key', $fld_sha1); $this->addFieldsIf('fa_timestamp', $fld_timestamp); $this->addFieldsIf(array('fa_user', 'fa_user_text'), $fld_user); $this->addFieldsIf(array('fa_height', 'fa_width', 'fa_size'), $fld_dimensions || $fld_size); $this->addFieldsIf('fa_description', $fld_description); $this->addFieldsIf(array('fa_major_mime', 'fa_minor_mime'), $fld_mime); $this->addFieldsIf('fa_media_type', $fld_mediatype); $this->addFieldsIf('fa_metadata', $fld_metadata); $this->addFieldsIf('fa_bits', $fld_bitdepth); $this->addFieldsIf('fa_archive_name', $fld_archivename); if (!is_null($params['continue'])) { $cont = explode('|', $params['continue']); if (count($cont) != 1) { $this->dieUsage("Invalid continue param. You should pass the " . "original value returned by the previous query", "_badcontinue"); } $op = $params['dir'] == 'descending' ? '<' : '>'; $cont_from = $db->addQuotes($cont[0]); $this->addWhere("fa_name {$op}= {$cont_from}"); } // Image filters $dir = $params['dir'] == 'descending' ? 'older' : 'newer'; $from = is_null($params['from']) ? null : $this->titlePartToKey($params['from']); if (!is_null($params['continue'])) { $from = $params['continue']; } $to = is_null($params['to']) ? null : $this->titlePartToKey($params['to']); $this->addWhereRange('fa_name', $dir, $from, $to); if (isset($params['prefix'])) { $this->addWhere('fa_name' . $db->buildLike($this->titlePartToKey($params['prefix']), $db->anyString())); } $sha1Set = isset($params['sha1']); $sha1base36Set = isset($params['sha1base36']); if ($sha1Set || $sha1base36Set) { global $wgMiserMode; if ($wgMiserMode) { $this->dieUsage('Search by hash disabled in Miser Mode', 'hashsearchdisabled'); } $sha1 = false; if ($sha1Set) { if (!$this->validateSha1Hash($params['sha1'])) { $this->dieUsage('The SHA1 hash provided is not valid', 'invalidsha1hash'); } $sha1 = wfBaseConvert($params['sha1'], 16, 36, 31); } elseif ($sha1base36Set) { if (!$this->validateSha1Base36Hash($params['sha1base36'])) { $this->dieUsage('The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash'); } $sha1 = $params['sha1base36']; } if ($sha1) { $this->addWhere('fa_storage_key ' . $db->buildLike("{$sha1}.", $db->anyString())); } } if (!$user->isAllowed('suppressrevision')) { // Filter out revisions that the user is not allowed to see. There // is no way to indicate that we have skipped stuff because the // continuation parameter is fa_name // Note that this field is unindexed. This should however not be // a big problem as files with fa_deleted are rare $this->addWhereFld('fa_deleted', 0); } $limit = $params['limit']; $this->addOption('LIMIT', $limit + 1); $sort = $params['dir'] == 'descending' ? ' DESC' : ''; $this->addOption('ORDER BY', 'fa_name' . $sort); $res = $this->select(__METHOD__); $count = 0; $result = $this->getResult(); foreach ($res as $row) { if (++$count > $limit) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... $this->setContinueEnumParameter('continue', $row->fa_name); break; } $file = array(); $file['name'] = $row->fa_name; $title = Title::makeTitle(NS_FILE, $row->fa_name); self::addTitleInfo($file, $title); if ($fld_sha1) { $file['sha1'] = wfBaseConvert(LocalRepo::getHashFromKey($row->fa_storage_key), 36, 16, 40); } if ($fld_timestamp) { $file['timestamp'] = wfTimestamp(TS_ISO_8601, $row->fa_timestamp); } if ($fld_user) { $file['userid'] = $row->fa_user; $file['user'] = $row->fa_user_text; } if ($fld_size || $fld_dimensions) { $file['size'] = $row->fa_size; $pageCount = ArchivedFile::newFromRow($row)->pageCount(); if ($pageCount !== false) { $vals['pagecount'] = $pageCount; } $file['height'] = $row->fa_height; $file['width'] = $row->fa_width; } if ($fld_description) { $file['description'] = $row->fa_description; if (isset($prop['parseddescription'])) { $file['parseddescription'] = Linker::formatComment($row->fa_description, $title); } } if ($fld_mediatype) { $file['mediatype'] = $row->fa_media_type; } if ($fld_metadata) { $file['metadata'] = $row->fa_metadata ? ApiQueryImageInfo::processMetaData(unserialize($row->fa_metadata), $result) : null; } if ($fld_bitdepth) { $file['bitdepth'] = $row->fa_bits; } if ($fld_mime) { $file['mime'] = "{$row->fa_major_mime}/{$row->fa_minor_mime}"; } if ($fld_archivename && !is_null($row->fa_archive_name)) { $file['archivename'] = $row->fa_archive_name; } if ($row->fa_deleted & File::DELETED_FILE) { $file['filehidden'] = ''; } if ($row->fa_deleted & File::DELETED_COMMENT) { $file['commenthidden'] = ''; } if ($row->fa_deleted & File::DELETED_USER) { $file['userhidden'] = ''; } if ($row->fa_deleted & File::DELETED_RESTRICTED) { // This file is deleted for normal admins $file['suppressed'] = ''; } $fit = $result->addValue(array('query', $this->getModuleName()), null, $file); if (!$fit) { $this->setContinueEnumParameter('continue', $row->fa_name); break; } } $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'fa'); }
private function formatFileRow($row) { $file = ArchivedFile::newFromRow($row); $ts = wfTimestamp(TS_MW, $row->fa_timestamp); $user = $this->getUser(); if ($this->mAllowed && $row->fa_storage_key) { $checkBox = Xml::check('fileid' . $row->fa_id); $key = urlencode($row->fa_storage_key); $pageLink = $this->getFileLink($file, $this->getTitle(), $ts, $key); } else { $checkBox = ''; $pageLink = $this->getLanguage()->userTimeAndDate($ts, $user); } $userLink = $this->getFileUser($file); $data = $this->msg('widthheight')->numParams($row->fa_width, $row->fa_height)->text() . ' (' . $this->msg('nbytes')->numParams($row->fa_size)->text() . ')'; $data = htmlspecialchars($data); $comment = $this->getFileComment($file); // Add show/hide deletion links if available $canHide = $user->isAllowed('deleterevision'); if ($canHide || $file->getVisibility() && $user->isAllowed('deletedhistory')) { if (!$file->userCan(File::DELETED_RESTRICTED, $user)) { $revdlink = Linker::revDeleteLinkDisabled($canHide); // revision was hidden from sysops } else { $query = array('type' => 'filearchive', 'target' => $this->mTargetObj->getPrefixedDBkey(), 'ids' => $row->fa_id); $revdlink = Linker::revDeleteLink($query, $file->isDeleted(File::DELETED_RESTRICTED), $canHide); } } else { $revdlink = ''; } return "<li>{$checkBox} {$revdlink} {$pageLink} . . {$userLink} {$data} {$comment}</li>\n"; }
/** * Fetch file upload comment if it's available to this user * * @param File|ArchivedFile $file * @return string HTML fragment */ function getFileComment($file) { if (!$file->userCan(File::DELETED_COMMENT, $this->getUser())) { return '<span class="history-deleted"><span class="comment">' . $this->msg('rev-deleted-comment')->escaped() . '</span></span>'; } $link = Linker::commentBlock($file->getRawDescription()); if ($file->isDeleted(File::DELETED_COMMENT)) { $link = '<span class="history-deleted">' . $link . '</span>'; } return $link; }
/** * Check for non fatal problems with the file * * @return Array of warnings */ public function checkWarnings() { $warnings = array(); $localFile = $this->getLocalFile(); $filename = $localFile->getName(); $n = strrpos($filename, '.'); /** * Check whether the resulting filename is different from the desired one, * but ignore things like ucfirst() and spaces/underscore things */ $comparableName = str_replace(' ', '_', $this->mDesiredDestName); $comparableName = Title::capitalize($comparableName, NS_FILE); if ($this->mDesiredDestName != $filename && $comparableName != $filename) { $warnings['badfilename'] = $filename; } // Check whether the file extension is on the unwanted list global $wgCheckFileExtensions, $wgFileExtensions; if ($wgCheckFileExtensions) { if (!$this->checkFileExtension($this->mFinalExtension, $wgFileExtensions)) { $warnings['filetype-unwanted-type'] = $this->mFinalExtension; } } global $wgUploadSizeWarning; if ($wgUploadSizeWarning && $this->mFileSize > $wgUploadSizeWarning) { $warnings['large-file'] = $wgUploadSizeWarning; } if ($this->mFileSize == 0) { $warnings['emptyfile'] = true; } $exists = self::getExistsWarning($localFile); if ($exists !== false) { $warnings['exists'] = $exists; } // Check dupes against existing files $hash = File::sha1Base36($this->mTempPath); $dupes = RepoGroup::singleton()->findBySha1($hash); $title = $this->getTitle(); // Remove all matches against self foreach ($dupes as $key => $dupe) { if ($title->equals($dupe->getTitle())) { unset($dupes[$key]); } } if ($dupes) { $warnings['duplicate'] = $dupes; } // Check dupes against archives $archivedImage = new ArchivedFile(null, 0, "{$hash}.{$this->mFinalExtension}"); if ($archivedImage->getID() > 0) { $warnings['duplicate-archive'] = $archivedImage->getName(); } return $warnings; }
public function __construct($list, $row) { RevDel_Item::__construct($list, $row); $this->file = ArchivedFile::newFromRow($row); }
private function formatFileRow($row, $sk) { global $wgUser, $wgLang; $file = ArchivedFile::newFromRow($row); $ts = wfTimestamp(TS_MW, $row->fa_timestamp); if ($this->mAllowed && $row->fa_storage_key) { $checkBox = Xml::check("fileid" . $row->fa_id); $key = urlencode($row->fa_storage_key); $target = urlencode($this->mTarget); $titleObj = SpecialPage::getTitleFor("Undelete"); $pageLink = $this->getFileLink($file, $titleObj, $ts, $key, $sk); } else { $checkBox = ''; $pageLink = $wgLang->timeanddate($ts, true); } $userLink = $this->getFileUser($file, $sk); $data = wfMsg('widthheight', $wgLang->formatNum($row->fa_width), $wgLang->formatNum($row->fa_height)) . ' (' . wfMsg('nbytes', $wgLang->formatNum($row->fa_size)) . ')'; $data = htmlspecialchars($data); $comment = $this->getFileComment($file, $sk); $revdlink = ''; if ($wgUser->isAllowed('deleterevision')) { if (!$file->userCan(File::DELETED_RESTRICTED)) { // If revision was hidden from sysops $revdlink = Xml::tags('span', array('class' => 'mw-revdelundel-link'), '(' . wfMsgHtml('rev-delundel') . ')'); } else { $query = array('target' => $this->mTargetObj->getPrefixedDBkey(), 'fileid' => $row->fa_id); $revdlink = $sk->revDeleteLink($query, $file->isDeleted(File::DELETED_RESTRICTED)); } } return "<li>{$checkBox} {$revdlink} {$pageLink} . . {$userLink} {$data} {$comment}</li>\n"; }