/** * @throws MWException * @param Title $title * @param int $id * @param string $key */ function __construct($title, $id = 0, $key = '') { $this->id = -1; $this->title = false; $this->name = false; $this->group = 'deleted'; // needed for direct use of constructor $this->key = ''; $this->size = 0; $this->bits = 0; $this->width = 0; $this->height = 0; $this->metadata = ''; $this->mime = "unknown/unknown"; $this->media_type = ''; $this->description = ''; $this->user = 0; $this->user_text = ''; $this->timestamp = null; $this->deleted = 0; $this->dataLoaded = false; $this->exists = false; $this->sha1 = ''; if ($title instanceof Title) { $this->title = File::normalizeTitle($title, 'exception'); $this->name = $title->getDBkey(); } if ($id) { $this->id = $id; } if ($key) { $this->key = $key; } if (!$id && !$key && !$title instanceof Title) { throw new MWException("No specifications provided to ArchivedFile constructor."); } }
/** * Create an UnregisteredLocalFile based on a path or a (title,repo) pair. * A FileRepo object is not required here, unlike most other File classes. * * @throws MWException * @param $title Title|false * @param $repo FileRepo * @param $path string * @param $mime string */ function __construct($title = false, $repo = false, $path = false, $mime = false) { if (!($title && $repo) && !$path) { throw new MWException(__METHOD__ . ': not enough parameters, must specify title and repo, or a full path'); } if ($title instanceof Title) { $this->title = File::normalizeTitle($title, 'exception'); $this->name = $repo->getNameFromTitle($title); } else { $this->name = basename($path); $this->title = File::normalizeTitle($this->name, 'exception'); } $this->repo = $repo; if ($path) { $this->path = $path; } else { $this->assertRepoDefined(); $this->path = $repo->getRootDirectory() . '/' . $repo->getHashPath($this->name) . $this->name; } if ($mime) { $this->mime = $mime; } $this->dims = array(); }
public function findFiles(array $items, $flags = 0) { $finalFiles = array(); // map of (DB key => corresponding File) for matches $searchSet = array(); // map of (normalized DB key => search params) foreach ($items as $item) { if (is_array($item)) { $title = File::normalizeTitle($item['title']); if ($title) { $searchSet[$title->getDBkey()] = $item; } } else { $title = File::normalizeTitle($item); if ($title) { $searchSet[$title->getDBkey()] = array(); } } } $fileMatchesSearch = function (File $file, array $search) { // Note: file name comparison done elsewhere (to handle redirects) $user = !empty($search['private']) && $search['private'] instanceof User ? $search['private'] : null; return $file->exists() && (empty($search['time']) && !$file->isOld() || !empty($search['time']) && $search['time'] === $file->getTimestamp()) && (!empty($search['private']) || !$file->isDeleted(File::DELETED_FILE)) && $file->userCan(File::DELETED_FILE, $user); }; $that = $this; $applyMatchingFiles = function (ResultWrapper $res, &$searchSet, &$finalFiles) use($that, $fileMatchesSearch, $flags) { global $wgContLang; $info = $that->getInfo(); foreach ($res as $row) { $file = $that->newFileFromRow($row); // There must have been a search for this DB key, but this has to handle the // cases were title capitalization is different on the client and repo wikis. $dbKeysLook = array(strtr($file->getName(), ' ', '_')); if (!empty($info['initialCapital'])) { // Search keys for "hi.png" and "Hi.png" should use the "Hi.png file" $dbKeysLook[] = $wgContLang->lcfirst($file->getName()); } foreach ($dbKeysLook as $dbKey) { if (isset($searchSet[$dbKey]) && $fileMatchesSearch($file, $searchSet[$dbKey])) { $finalFiles[$dbKey] = $flags & FileRepo::NAME_AND_TIME_ONLY ? array('title' => $dbKey, 'timestamp' => $file->getTimestamp()) : $file; unset($searchSet[$dbKey]); } } } }; $dbr = $this->getSlaveDB(); // Query image table $imgNames = array(); foreach (array_keys($searchSet) as $dbKey) { $imgNames[] = $this->getNameFromTitle(File::normalizeTitle($dbKey)); } if (count($imgNames)) { $res = $dbr->select('image', LocalFile::selectFields(), array('img_name' => $imgNames), __METHOD__); $applyMatchingFiles($res, $searchSet, $finalFiles); } // Query old image table $oiConds = array(); // WHERE clause array for each file foreach ($searchSet as $dbKey => $search) { if (isset($search['time'])) { $oiConds[] = $dbr->makeList(array('oi_name' => $this->getNameFromTitle(File::normalizeTitle($dbKey)), 'oi_timestamp' => $dbr->timestamp($search['time'])), LIST_AND); } } if (count($oiConds)) { $res = $dbr->select('oldimage', OldLocalFile::selectFields(), $dbr->makeList($oiConds, LIST_OR), __METHOD__); $applyMatchingFiles($res, $searchSet, $finalFiles); } // Check for redirects... foreach ($searchSet as $dbKey => $search) { if (!empty($search['ignoreRedirect'])) { continue; } $title = File::normalizeTitle($dbKey); $redir = $this->checkRedirect($title); // hopefully hits memcached if ($redir && $redir->getNamespace() == NS_FILE) { $file = $this->newFile($redir); if ($file && $fileMatchesSearch($file, $search)) { $file->redirectedFrom($title->getDBkey()); if ($flags & FileRepo::NAME_AND_TIME_ONLY) { $finalFiles[$dbKey] = array('title' => $file->getTitle()->getDBkey(), 'timestamp' => $file->getTimestamp()); } else { $finalFiles[$dbKey] = $file; } } } } return $finalFiles; }
/** * Find an instance of the named file created at the specified time * Returns false if the file does not exist. Repositories not supporting * version control should return false if the time is specified. * * @param $title Mixed: Title object or string * @param $options array Associative array of options: * time: requested time for an archived image, or false for the * current version. An image object will be returned which was * created at the specified time. * * ignoreRedirect: If true, do not follow file redirects * * private: If true, return restricted (deleted) files if the current * user is allowed to view them. Otherwise, such files will not * be found. * @return File|false */ public function findFile($title, $options = array()) { $title = File::normalizeTitle($title); if (!$title) { return false; } $time = isset($options['time']) ? $options['time'] : false; # First try the current version of the file to see if it precedes the timestamp $img = $this->newFile($title); if (!$img) { return false; } if ($img->exists() && (!$time || $img->getTimestamp() == $time)) { return $img; } # Now try an old version of the file if ($time !== false) { $img = $this->newFile($title, $time); if ($img && $img->exists()) { if (!$img->isDeleted(File::DELETED_FILE)) { return $img; // always OK } elseif (!empty($options['private']) && $img->userCan(File::DELETED_FILE)) { return $img; } } } # Now try redirects if (!empty($options['ignoreRedirect'])) { return false; } $redir = $this->checkRedirect($title); if ($redir && $title->getNamespace() == NS_FILE) { $img = $this->newFile($redir); if (!$img) { return false; } if ($img->exists()) { $img->redirectedFrom($title->getDBkey()); return $img; } } return false; }
/** * Checks if there is a redirect named as $title * * @param $title Title of file * @return bool */ function checkRedirect(Title $title) { global $wgMemc; $title = File::normalizeTitle($title, 'exception'); $memcKey = $this->getSharedCacheKey('image_redirect', md5($title->getDBkey())); if ($memcKey === false) { $memcKey = $this->getLocalCacheKey('image_redirect', md5($title->getDBkey())); $expiry = 300; // no invalidation, 5 minutes } else { $expiry = 86400; // has invalidation, 1 day } $cachedValue = $wgMemc->get($memcKey); if ($cachedValue === ' ' || $cachedValue === '') { // Does not exist return false; } elseif (strval($cachedValue) !== '') { return Title::newFromText($cachedValue, NS_FILE); } // else $cachedValue is false or null: cache miss $id = $this->getArticleID($title); if (!$id) { $wgMemc->set($memcKey, " ", $expiry); return false; } $dbr = $this->getSlaveDB(); $row = $dbr->selectRow('redirect', array('rd_title', 'rd_namespace'), array('rd_from' => $id), __METHOD__); if ($row && $row->rd_namespace == NS_FILE) { $targetTitle = Title::makeTitle($row->rd_namespace, $row->rd_title); $wgMemc->set($memcKey, $targetTitle->getDBkey(), $expiry); return $targetTitle; } else { $wgMemc->set($memcKey, '', $expiry); return false; } }
/** * Find many files at once. * * @param array $items An array of titles, or an array of findFile() options with * the "title" option giving the title. Example: * * $findItem = array( 'title' => $title, 'private' => true ); * $findBatch = array( $findItem ); * $repo->findFiles( $findBatch ); * * No title should appear in $items twice, as the result use titles as keys * @param int $flags Supports: * - FileRepo::NAME_AND_TIME_ONLY : return a (search title => (title,timestamp)) map. * The search title uses the input titles; the other is the final post-redirect title. * All titles are returned as string DB keys and the inner array is associative. * @return array Map of (file name => File objects) for matches */ public function findFiles(array $items, $flags = 0) { $result = array(); foreach ($items as $item) { if (is_array($item)) { $title = $item['title']; $options = $item; unset($options['title']); } else { $title = $item; $options = array(); } $file = $this->findFile($title, $options); if ($file) { $searchName = File::normalizeTitle($title)->getDBkey(); // must be valid if ($flags & self::NAME_AND_TIME_ONLY) { $result[$searchName] = array('title' => $file->getTitle()->getDBkey(), 'timestamp' => $file->getTimestamp()); } else { $result[$searchName] = $file; } } } return $result; }
/** * Search repositories for many files at once. * * @param array $items An array of titles, or an array of findFile() options with * the "title" option giving the title. Example: * * $findItem = array( 'title' => $title, 'private' => true ); * $findBatch = array( $findItem ); * $repo->findFiles( $findBatch ); * * No title should appear in $items twice, as the result use titles as keys * @param int $flags Supports: * - FileRepo::NAME_AND_TIME_ONLY : return a (search title => (title,timestamp)) map. * The search title uses the input titles; the other is the final post-redirect title. * All titles are returned as string DB keys and the inner array is associative. * @return array Map of (file name => File objects) for matches * * @param array $inputItems * @param integer $flags * @return array */ function findFiles(array $inputItems, $flags = 0) { if (!$this->reposInitialised) { $this->initialiseRepos(); } $items = array(); foreach ($inputItems as $item) { if (!is_array($item)) { $item = array('title' => $item); } $item['title'] = File::normalizeTitle($item['title']); if ($item['title']) { $items[$item['title']->getDBkey()] = $item; } } $images = $this->localRepo->findFiles($items, $flags); foreach ($this->foreignRepos as $repo) { // Remove found files from $items foreach ($images as $name => $image) { unset($items[$name]); } $images = array_merge($images, $repo->findFiles($items, $flags)); } return $images; }