/** * @dataProvider provider_testTimestampedUID */ public function testTimestampedUID($method, $digitlen, $bits, $tbits, $hostbits) { $id = call_user_func(array('UIDGenerator', $method)); $this->assertEquals(true, ctype_digit($id), "UID made of digit characters"); $this->assertLessThanOrEqual($digitlen, strlen($id), "UID has the right number of digits"); $this->assertLessThanOrEqual($bits, strlen(wfBaseConvert($id, 10, 2)), "UID has the right number of bits"); $ids = array(); for ($i = 0; $i < 300; $i++) { $ids[] = call_user_func(array('UIDGenerator', $method)); } $lastId = array_shift($ids); if ($hostbits) { $lastHost = substr(wfBaseConvert($lastId, 10, 2, $bits), -$hostbits); } $this->assertArrayEquals(array_unique($ids), $ids, "All generated IDs are unique."); foreach ($ids as $id) { $id_bin = wfBaseConvert($id, 10, 2); $lastId_bin = wfBaseConvert($lastId, 10, 2); $this->assertGreaterThanOrEqual(substr($id_bin, 0, $tbits), substr($lastId_bin, 0, $tbits), "New ID timestamp ({$id_bin}) >= prior one ({$lastId_bin})."); if ($hostbits) { $this->assertEquals(substr($id_bin, 0, -$hostbits), substr($lastId_bin, 0, -$hostbits), "Host ID of ({$id_bin}) is same as prior one ({$lastId_bin})."); } $lastId = $id; } }
/** * Get a statistically unique ID string * * @return string <9 char TS_MW timestamp in base 36><22 random base 36 chars> */ public final function getTimestampedUUID() { $s = ''; for ($i = 0; $i < 5; $i++) { $s .= mt_rand(0, 2147483647); } $s = wfBaseConvert(sha1($s), 16, 36, 31); return substr(wfBaseConvert(wfTimestamp(TS_MW), 10, 36, 9) . $s, 0, 31); }
public static function uuidConversionProvider() { $dbr = wfGetDB(DB_SLAVE); // sample uuid from UIDGenerator::newTimestampedUID128() $numeric_128 = '6709199728898751234959525538795913762'; $hex_128 = wfBaseConvert($numeric_128, 10, 16, 32); $bin_128 = new UUIDBlob(pack('H*', $hex_128)); $pretty_128 = wfBaseConvert($numeric_128, 10, 36); // Conversion from 128 bit to 88 bit takes the left // most 88 bits. $bits_88 = substr(wfBaseConvert($numeric_128, 10, 2, 128), 0, 88); $numeric_88 = wfBaseConvert($bits_88, 2, 10); $hex_88 = wfBaseConvert($numeric_88, 10, 16, 22); $bin_88 = new UUIDBlob(pack('H*', $hex_88)); $pretty_88 = wfBaseConvert($numeric_88, 10, 36); return array(array('128 bit hex input must be truncated to 88bit output', $hex_128, $bin_88, $hex_88, $pretty_88), array('88 bit binary input', $bin_88, $bin_88, $hex_88, $pretty_88), array('88 bit numeric input', $numeric_88, $bin_88, $hex_88, $pretty_88), array('88 bit hex input', $hex_88, $bin_88, $hex_88, $pretty_88), array('88 bit pretty input', $pretty_88, $bin_88, $hex_88, $pretty_88)); }
/** * @see ExternalStoreMedium::store() */ public function store($backend, $data) { $be = FileBackendGroup::singleton()->get($backend); if ($be instanceof FileBackend) { // Get three random base 36 characters to act as shard directories $rand = wfBaseConvert(mt_rand(0, 46655), 10, 36, 3); // Make sure ID is roughly lexicographically increasing for performance $id = str_pad(UIDGenerator::newTimestampedUID128(32), 26, '0', STR_PAD_LEFT); // Segregate items by wiki ID for the sake of bookkeeping $wiki = isset($this->params['wiki']) ? $this->params['wiki'] : wfWikiID(); $url = $be->getContainerStoragePath('data') . '/' . rawurlencode($wiki) . "/{$rand[0]}/{$rand[1]}/{$rand[2]}/{$id}"; $be->prepare(array('dir' => dirname($url), 'noAccess' => 1, 'noListing' => 1)); if ($be->create(array('dst' => $url, 'content' => $data))->isOK()) { return $url; } } return false; }
/** * Construct a new instance from configuration. * * $config paramaters include: * 'lockServers' : Associative array of server names to configuration. * Configuration is an associative array that includes: * 'host' - IP address/hostname * 'port' - TCP port * 'authKey' - Secret string the lock server uses * 'srvsByBucket' : Array of 1-16 consecutive integer keys, starting from 0, * each having an odd-numbered list of server names (peers) as values. * 'connTimeout' : Lock server connection attempt timeout. [optional] * * @param Array $config */ public function __construct(array $config) { $this->lockServers = $config['lockServers']; // Sanitize srvsByBucket config to prevent PHP errors $this->srvsByBucket = array_filter($config['srvsByBucket'], 'is_array'); $this->srvsByBucket = array_values($this->srvsByBucket); // consecutive if (isset($config['connTimeout'])) { $this->connTimeout = $config['connTimeout']; } else { $this->connTimeout = 3; // use some sane amount } $this->session = ''; for ($i = 0; $i < 5; $i++) { $this->session .= mt_rand(0, 2147483647); } $this->session = wfBaseConvert(sha1($this->session), 16, 36, 31); }
/** * Hash data as a session ID * * Generally this will only be used when self::persistsSessionId() is false and * the provider has to base the session ID on the verified user's identity * or other static data. * * @param string $data * @param string|null $key Defaults to $this->config->get( 'SecretKey' ) * @return string */ protected final function hashToSessionId($data, $key = null) { if (!is_string($data)) { throw new \InvalidArgumentException('$data must be a string, ' . gettype($data) . ' was passed'); } if ($key !== null && !is_string($key)) { throw new \InvalidArgumentException('$key must be a string or null, ' . gettype($key) . ' was passed'); } $hash = \MWCryptHash::hmac("{$this}\n{$data}", $key ?: $this->config->get('SecretKey'), false); if (strlen($hash) < 32) { // Should never happen, even md5 is 128 bits // @codeCoverageIgnoreStart throw new \UnexpectedValueException('Hash fuction returned less than 128 bits'); // @codeCoverageIgnoreEnd } if (strlen($hash) >= 40) { $hash = wfBaseConvert($hash, 16, 32, 32); } return substr($hash, -32); }
/** * Stash a file in a temp directory and record that we did this in the database, along with other metadata. * * @param $path String: path to file you want stashed * @param $sourceType String: the type of upload that generated this file (currently, I believe, 'file' or null) * @param $key String: optional, unique key for this file. Used for directory hashing when storing, otherwise not important * @throws UploadStashBadPathException * @throws UploadStashFileException * @throws UploadStashNotLoggedInException * @return UploadStashFile: file, or null on failure */ public function stashFile($path, $sourceType = null, $key = null) { if (!file_exists($path)) { wfDebug(__METHOD__ . " tried to stash file at '{$path}', but it doesn't exist\n"); throw new UploadStashBadPathException("path doesn't exist"); } $fileProps = File::getPropsFromPath($path); wfDebug(__METHOD__ . " stashing file at '{$path}'\n"); // we will be initializing from some tmpnam files that don't have extensions. // most of MediaWiki assumes all uploaded files have good extensions. So, we fix this. $extension = self::getExtensionForPath($path); if (!preg_match("/\\.\\Q{$extension}\\E\$/", $path)) { $pathWithGoodExtension = "{$path}.{$extension}"; if (!rename($path, $pathWithGoodExtension)) { throw new UploadStashFileException("couldn't rename {$path} to have a better extension at {$pathWithGoodExtension}"); } $path = $pathWithGoodExtension; } // If no key was supplied, make one. a mysql insertid would be totally reasonable here, except // that some users of this function might expect to supply the key instead of using the generated one. if (is_null($key)) { // some things that when combined will make a suitably unique key. // see: http://www.jwz.org/doc/mid.html list($usec, $sec) = explode(' ', microtime()); $usec = substr($usec, 2); $key = wfBaseConvert($sec . $usec, 10, 36) . '.' . wfBaseConvert(mt_rand(), 10, 36) . '.' . $this->userId . '.' . $extension; } $this->fileProps[$key] = $fileProps; if (!preg_match(self::KEY_FORMAT_REGEX, $key)) { throw new UploadStashBadPathException("key '{$key}' is not in a proper format"); } wfDebug(__METHOD__ . " key for '{$path}': {$key}\n"); // if not already in a temporary area, put it there $storeStatus = $this->repo->storeTemp(basename($path), $path); if (!$storeStatus->isOK()) { // It is a convention in MediaWiki to only return one error per API exception, even if multiple errors // are available. We use reset() to pick the "first" thing that was wrong, preferring errors to warnings. // This is a bit lame, as we may have more info in the $storeStatus and we're throwing it away, but to fix it means // redesigning API errors significantly. // $storeStatus->value just contains the virtual URL (if anything) which is probably useless to the caller $error = $storeStatus->getErrorsArray(); $error = reset($error); if (!count($error)) { $error = $storeStatus->getWarningsArray(); $error = reset($error); if (!count($error)) { $error = array('unknown', 'no error recorded'); } } throw new UploadStashFileException("error storing file in '{$path}': " . implode('; ', $error)); } $stashPath = $storeStatus->value; // fetch the current user ID if (!$this->isLoggedIn) { throw new UploadStashNotLoggedInException(__METHOD__ . ' No user is logged in, files must belong to users'); } // insert the file metadata into the db. wfDebug(__METHOD__ . " inserting {$stashPath} under {$key}\n"); $dbw = $this->repo->getMasterDb(); // select happens on the master so this can all be in a transaction, which // avoids a race condition that's likely with multiple people uploading from the same // set of files $dbw->begin(); // first, check to see if it's already there. $row = $dbw->selectRow('uploadstash', 'us_user, us_timestamp', array('us_key' => $key), __METHOD__); // The current user can't have this key if: // - the key is owned by someone else and // - the age of the key is less than REPO_AGE if (is_object($row)) { if ($row->us_user != $this->userId && $row->wfTimestamp(TS_UNIX, $row->us_timestamp) > time() - UploadStash::REPO_AGE * 3600) { $dbw->rollback(); throw new UploadStashWrongOwnerException("Attempting to upload a duplicate of a file that someone else has stashed"); } } $this->fileMetadata[$key] = array('us_user' => $this->userId, 'us_key' => $key, 'us_orig_path' => $path, 'us_path' => $stashPath, 'us_size' => $fileProps['size'], 'us_sha1' => $fileProps['sha1'], 'us_mime' => $fileProps['mime'], 'us_media_type' => $fileProps['media_type'], 'us_image_width' => $fileProps['width'], 'us_image_height' => $fileProps['height'], 'us_image_bits' => $fileProps['bits'], 'us_source_type' => $sourceType, 'us_timestamp' => $dbw->timestamp(), 'us_status' => 'finished'); // if a row exists but previous checks on it passed, let the current user take over this key. $dbw->replace('uploadstash', 'us_key', $this->fileMetadata[$key], __METHOD__); $dbw->commit(); // store the insertid in the class variable so immediate retrieval (possibly laggy) isn't necesary. $this->fileMetadata[$key]['us_id'] = $dbw->insertId(); # create the UploadStashFile object for this file. $this->initFile($key); return $this->getFile($key); }
/** * @return bool|string */ function getSha1() { if ($this->sha1base36) { return wfBaseConvert($this->sha1base36, 36, 16); } return false; }
/** * Get result information for an image revision * * @param File $file * @param array $prop Array of properties to get (in the keys) * @param ApiResult $result * @param array $thumbParams Containing 'width' and 'height' items, or null * @param array|bool|string $opts Options for data fetching. * This is an array consisting of the keys: * 'version': The metadata version for the metadata option * 'language': The language for extmetadata property * 'multilang': Return all translations in extmetadata property * 'revdelUser': User to use when checking whether to show revision-deleted fields. * @return array Result array */ static function getInfo($file, $prop, $result, $thumbParams = null, $opts = false) { global $wgContLang; $anyHidden = false; if (!$opts || is_string($opts)) { $opts = array('version' => $opts ?: 'latest', 'language' => $wgContLang, 'multilang' => false, 'extmetadatafilter' => array(), 'revdelUser' => null); } $version = $opts['version']; $vals = array(ApiResult::META_TYPE => 'assoc'); // Timestamp is shown even if the file is revdelete'd in interface // so do same here. if (isset($prop['timestamp'])) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $file->getTimestamp()); } // Handle external callers who don't pass revdelUser if (isset($opts['revdelUser']) && $opts['revdelUser']) { $revdelUser = $opts['revdelUser']; $canShowField = function ($field) use($file, $revdelUser) { return $file->userCan($field, $revdelUser); }; } else { $canShowField = function ($field) use($file) { return !$file->isDeleted($field); }; } $user = isset($prop['user']); $userid = isset($prop['userid']); if ($user || $userid) { if ($file->isDeleted(File::DELETED_USER)) { $vals['userhidden'] = true; $anyHidden = true; } if ($canShowField(File::DELETED_USER)) { if ($user) { $vals['user'] = $file->getUser(); } if ($userid) { $vals['userid'] = $file->getUser('id'); } if (!$file->getUser('id')) { $vals['anon'] = true; } } } // This is shown even if the file is revdelete'd in interface // so do same here. if (isset($prop['size']) || isset($prop['dimensions'])) { $vals['size'] = intval($file->getSize()); $vals['width'] = intval($file->getWidth()); $vals['height'] = intval($file->getHeight()); $pageCount = $file->pageCount(); if ($pageCount !== false) { $vals['pagecount'] = $pageCount; } // length as in how many seconds long a video is. $length = $file->getLength(); if ($length) { // Call it duration, because "length" can be ambiguous. $vals['duration'] = (double) $length; } } $pcomment = isset($prop['parsedcomment']); $comment = isset($prop['comment']); if ($pcomment || $comment) { if ($file->isDeleted(File::DELETED_COMMENT)) { $vals['commenthidden'] = true; $anyHidden = true; } if ($canShowField(File::DELETED_COMMENT)) { if ($pcomment) { $vals['parsedcomment'] = Linker::formatComment($file->getDescription(File::RAW), $file->getTitle()); } if ($comment) { $vals['comment'] = $file->getDescription(File::RAW); } } } $canonicaltitle = isset($prop['canonicaltitle']); $url = isset($prop['url']); $sha1 = isset($prop['sha1']); $meta = isset($prop['metadata']); $extmetadata = isset($prop['extmetadata']); $commonmeta = isset($prop['commonmetadata']); $mime = isset($prop['mime']); $mediatype = isset($prop['mediatype']); $archive = isset($prop['archivename']); $bitdepth = isset($prop['bitdepth']); $uploadwarning = isset($prop['uploadwarning']); if ($uploadwarning) { $vals['html'] = SpecialUpload::getExistsWarning(UploadBase::getExistsWarning($file)); } if ($file->isDeleted(File::DELETED_FILE)) { $vals['filehidden'] = true; $anyHidden = true; } if ($anyHidden && $file->isDeleted(File::DELETED_RESTRICTED)) { $vals['suppressed'] = true; } if (!$canShowField(File::DELETED_FILE)) { //Early return, tidier than indenting all following things one level return $vals; } if ($canonicaltitle) { $vals['canonicaltitle'] = $file->getTitle()->getPrefixedText(); } if ($url) { if (!is_null($thumbParams)) { $mto = $file->transform($thumbParams); self::$transformCount++; if ($mto && !$mto->isError()) { $vals['thumburl'] = wfExpandUrl($mto->getUrl(), PROTO_CURRENT); // bug 23834 - If the URL's are the same, we haven't resized it, so shouldn't give the wanted // thumbnail sizes for the thumbnail actual size if ($mto->getUrl() !== $file->getUrl()) { $vals['thumbwidth'] = intval($mto->getWidth()); $vals['thumbheight'] = intval($mto->getHeight()); } else { $vals['thumbwidth'] = intval($file->getWidth()); $vals['thumbheight'] = intval($file->getHeight()); } if (isset($prop['thumbmime']) && $file->getHandler()) { list(, $mime) = $file->getHandler()->getThumbType($mto->getExtension(), $file->getMimeType(), $thumbParams); $vals['thumbmime'] = $mime; } } elseif ($mto && $mto->isError()) { $vals['thumberror'] = $mto->toText(); } } $vals['url'] = wfExpandUrl($file->getFullURL(), PROTO_CURRENT); $vals['descriptionurl'] = wfExpandUrl($file->getDescriptionUrl(), PROTO_CURRENT); } if ($sha1) { $vals['sha1'] = wfBaseConvert($file->getSha1(), 36, 16, 40); } if ($meta) { wfSuppressWarnings(); $metadata = unserialize($file->getMetadata()); wfRestoreWarnings(); if ($metadata && $version !== 'latest') { $metadata = $file->convertMetadataVersion($metadata, $version); } $vals['metadata'] = $metadata ? self::processMetaData($metadata, $result) : null; } if ($commonmeta) { $metaArray = $file->getCommonMetaArray(); $vals['commonmetadata'] = $metaArray ? self::processMetaData($metaArray, $result) : array(); } if ($extmetadata) { // Note, this should return an array where all the keys // start with a letter, and all the values are strings. // Thus there should be no issue with format=xml. $format = new FormatMetadata(); $format->setSingleLanguage(!$opts['multilang']); $format->getContext()->setLanguage($opts['language']); $extmetaArray = $format->fetchExtendedMetadata($file); if ($opts['extmetadatafilter']) { $extmetaArray = array_intersect_key($extmetaArray, array_flip($opts['extmetadatafilter'])); } $vals['extmetadata'] = $extmetaArray; } if ($mime) { $vals['mime'] = $file->getMimeType(); } if ($mediatype) { $vals['mediatype'] = $file->getMediaType(); } if ($archive && $file->isOld()) { $vals['archivename'] = $file->getArchiveName(); } if ($bitdepth) { $vals['bitdepth'] = $file->getBitDepth(); } return $vals; }
/** * Return a random password. * * @return String new random password */ public static function randomPassword() { global $wgMinimalPasswordLength; // Decide the final password length based on our min password length, stopping at a minimum of 10 chars $length = max(10, $wgMinimalPasswordLength); // Multiply by 1.25 to get the number of hex characters we need $length = $length * 1.25; // Generate random hex chars $hex = MWCryptRand::generateHex($length); // Convert from base 16 to base 32 to get a proper password like string return wfBaseConvert($hex, 16, 32); }
/** * @see FileBackendStore::doStoreInternal() */ protected function doStoreInternal(array $params) { $status = Status::newGood(); list($dstCont, $dstRel) = $this->resolveStoragePathReal($params['dst']); if ($dstRel === null) { $status->fatal('backend-fail-invalidpath', $params['dst']); return $status; } // (a) Check the destination container and object try { unset($this->objCache[$params['dst']]); $dContObj = $this->getContainer($dstCont); if (empty($params['overwrite']) && $this->fileExists(array('src' => $params['dst'], 'latest' => 1))) { $status->fatal('backend-fail-alreadyexists', $params['dst']); return $status; } } catch (NoSuchContainerException $e) { $status->fatal('backend-fail-copy', $params['src'], $params['dst']); $this->logException($e, __METHOD__, $params); return $status; } catch (InvalidResponseException $e) { $status->fatal('backend-fail-connect', $this->name); $this->logException($e, __METHOD__, $params); return $status; } catch (Exception $e) { // some other exception? $status->fatal('backend-fail-internal', $this->name); $this->logException($e, __METHOD__, $params); return $status; } // (b) Get a SHA-1 hash of the object $sha1Hash = sha1_file($params['src']); if ($sha1Hash === false) { // source doesn't exist? $status->fatal('backend-fail-copy', $params['src'], $params['dst']); return $status; } $sha1Hash = wfBaseConvert($sha1Hash, 16, 36, 31); // (c) Actually store the object try { // Create a fresh CF_Object with no fields preloaded. // We don't want to preserve headers, metadata, and such. $obj = new CF_Object($dContObj, $dstRel, false, false); // skip HEAD // Note: metadata keys stored as [Upper case char][[Lower case char]...] $obj->metadata = array('Sha1base36' => $sha1Hash); // The MD5 here will be checked within Swift against its own MD5. $obj->set_etag(md5_file($params['src'])); // Use the same content type as StreamFile for security $obj->content_type = $this->getFileProps($params)['mime']; // Wikia cnange: use the same logic as for DB row (BAC-1199) // Actually write the object in Swift $obj->load_from_filename($params['src'], True); // calls $obj->write() } catch (BadContentTypeException $e) { $status->fatal('backend-fail-contenttype', $params['dst']); $this->logException($e, __METHOD__, $params); } catch (IOException $e) { $status->fatal('backend-fail-copy', $params['src'], $params['dst']); $this->logException($e, __METHOD__, $params); } catch (InvalidResponseException $e) { $status->fatal('backend-fail-connect', $this->name); $this->logException($e, __METHOD__, $params); } catch (Exception $e) { // some other exception? $status->fatal('backend-fail-internal', $this->name); $this->logException($e, __METHOD__, $params); } wfRunHooks('SwiftFileBackend::doStoreInternal', array($params, &$status)); return $status; }
/** * @return null|string */ function getSha1() { return isset($this->mInfo['sha1']) ? wfBaseConvert(strval($this->mInfo['sha1']), 16, 36, 31) : null; }
/** * @param array $time Result of UIDGenerator::millitime() * @return string 46 MSBs of "milliseconds since epoch" in binary (rolls over in 4201) * @throws MWException */ protected function millisecondsSinceEpochBinary(array $time) { list($sec, $msec) = $time; $ts = 1000 * $sec + $msec; if ($ts > pow(2, 52)) { throw new MWException(__METHOD__ . ': sorry, this function doesn\'t work after the year 144680'); } return substr(wfBaseConvert($ts, 10, 2, 46), -46); }
protected function getSourceSha1Base36() { return wfBaseConvert(sha1($this->params['content']), 16, 36, 31); }
/** * @param string $ip * @param bool $xfor * @param string $reason * @param int $period * @param string $tag * @param string $talkTag * Lists all users in recent changes who used an IP, newest to oldest down * Outputs usernames, latest and earliest found edit date, and count * List unique IPs used for each user in time order, list corresponding user agent */ protected function doIPUsersRequest($ip, $xfor = false, $reason = '', $period = 0, $tag = '', $talkTag = '') { global $wgUser, $wgOut, $wgLang; $dbr = wfGetDB(DB_SLAVE); # Invalid IPs are passed in as a blank string $ip_conds = self::getIpConds($dbr, $ip, $xfor); if (!$ip || $ip_conds === false) { $wgOut->addWikiMsg('badipaddress'); return; } $logType = 'ipusers'; if ($xfor) { $logType .= '-xff'; } # Log the check... if (!self::addLogEntry($logType, 'ip', $ip, $reason)) { $wgOut->addHTML('<p>' . wfMsgHtml('checkuser-log-fail') . '</p>'); } $ip_conds = $dbr->makeList($ip_conds, LIST_AND); $time_conds = $this->getTimeConds($period); $index = $xfor ? 'cuc_xff_hex_time' : 'cuc_ip_hex_time'; # Ordered in descent by timestamp. Can cause large filesorts on range scans. # Check how many rows will need sorting ahead of time to see if this is too big. if (strpos($ip, '/') !== false) { # Quick index check only OK if no time constraint if ($period) { $rangecount = $dbr->selectField('cu_changes', 'COUNT(*)', array($ip_conds, $time_conds), __METHOD__, array('USE INDEX' => $index)); } else { $rangecount = $dbr->estimateRowCount('cu_changes', '*', array($ip_conds), __METHOD__, array('USE INDEX' => $index)); } // Sorting might take some time...make sure it is there wfSuppressWarnings(); set_time_limit(120); wfRestoreWarnings(); } // Are there too many edits? if (isset($rangecount) && $rangecount > 10000) { $ret = $dbr->select('cu_changes', array('cuc_ip_hex', 'COUNT(*) AS count', 'MIN(cuc_timestamp) AS first', 'MAX(cuc_timestamp) AS last'), array($ip_conds, $time_conds), __METHOD__, array('GROUP BY' => 'cuc_ip_hex', 'ORDER BY' => 'cuc_ip_hex', 'LIMIT' => 5001, 'USE INDEX' => $index)); # List out each IP that has edits $s = '<h5>' . wfMsg('checkuser-too-many') . '</h5>'; $s .= '<ol>'; $counter = 0; foreach ($ret as $row) { if ($counter >= 5000) { $wgOut->addHTML(wfMsgExt('checkuser-limited', array('parse'))); break; } # Convert the IP hexes into normal form if (strpos($row->cuc_ip_hex, 'v6-') !== false) { $ip = substr($row->cuc_ip_hex, 3); $ip = IP::HextoOctet($ip); } else { $ip = long2ip(wfBaseConvert($row->cuc_ip_hex, 16, 10, 8)); } $s .= '<li><a href="' . $this->getTitle()->escapeLocalURL('user='******'&reason=' . urlencode($reason) . '&checktype=subipusers') . '">' . $ip . '</a>'; if ($row->first == $row->last) { $s .= ' (' . $wgLang->timeanddate(wfTimestamp(TS_MW, $row->first), true) . ') '; } else { $s .= ' (' . $wgLang->timeanddate(wfTimestamp(TS_MW, $row->first), true) . ' -- ' . $wgLang->timeanddate(wfTimestamp(TS_MW, $row->last), true) . ') '; } $s .= ' [<strong>' . $row->count . "</strong>]</li>\n"; ++$counter; } $s .= '</ol>'; $wgOut->addHTML($s); return; } elseif (isset($rangecount) && !$rangecount) { $s = $this->noMatchesMessage($ip, !$xfor) . "\n"; $wgOut->addHTML($s); return; } global $wgMemc; # OK, do the real query... $ret = $dbr->select('cu_changes', array('cuc_user_text', 'cuc_timestamp', 'cuc_user', 'cuc_ip', 'cuc_agent', 'cuc_xff'), array($ip_conds, $time_conds), __METHOD__, array('ORDER BY' => 'cuc_timestamp DESC', 'LIMIT' => 10000, 'USE INDEX' => $index)); $users_first = $users_last = $users_edits = $users_ids = array(); if (!$dbr->numRows($ret)) { $s = $this->noMatchesMessage($ip, !$xfor) . "\n"; } else { global $wgAuth; foreach ($ret as $row) { if (!array_key_exists($row->cuc_user_text, $users_edits)) { $users_last[$row->cuc_user_text] = $row->cuc_timestamp; $users_edits[$row->cuc_user_text] = 0; $users_ids[$row->cuc_user_text] = $row->cuc_user; $users_infosets[$row->cuc_user_text] = array(); $users_agentsets[$row->cuc_user_text] = array(); } $users_edits[$row->cuc_user_text] += 1; $users_first[$row->cuc_user_text] = $row->cuc_timestamp; # Treat blank or NULL xffs as empty strings $xff = empty($row->cuc_xff) ? null : $row->cuc_xff; $xff_ip_combo = array($row->cuc_ip, $xff); # Add this IP/XFF combo for this username if it's not already there if (!in_array($xff_ip_combo, $users_infosets[$row->cuc_user_text])) { $users_infosets[$row->cuc_user_text][] = $xff_ip_combo; } # Add this agent string if it's not already there; 10 max. if (count($users_agentsets[$row->cuc_user_text]) < 10) { if (!in_array($row->cuc_agent, $users_agentsets[$row->cuc_user_text])) { $users_agentsets[$row->cuc_user_text][] = $row->cuc_agent; } } } $action = $this->getTitle()->escapeLocalURL('action=block'); $s = "<form name='checkuserblock' id='checkuserblock' action=\"{$action}\" method='post'>"; $s .= '<div id="checkuserresults"><ul>'; foreach ($users_edits as $name => $count) { $s .= '<li>'; $s .= Xml::check('users[]', false, array('value' => $name)) . ' '; # Load user object $user = User::newFromName($name, false); # Add user tool links $s .= $this->sk->userLink(-1, $name) . $this->sk->userToolLinks(-1, $name); # Add CheckUser link $s .= ' (<a href="' . $this->getTitle()->escapeLocalURL('user='******'&reason=' . urlencode($reason)) . '">' . wfMsgHtml('checkuser-check') . '</a>)'; # Show edit time range if ($users_first[$name] == $users_last[$name]) { $s .= ' (' . $wgLang->timeanddate(wfTimestamp(TS_MW, $users_first[$name]), true) . ') '; } else { $s .= ' (' . $wgLang->timeanddate(wfTimestamp(TS_MW, $users_first[$name]), true) . ' -- ' . $wgLang->timeanddate(wfTimestamp(TS_MW, $users_last[$name]), true) . ') '; } # Total edit count $s .= ' [<strong>' . $count . '</strong>]<br />'; # Check if this user or IP is blocked. If so, give a link to the block log... $ip = IP::isIPAddress($name) ? $name : ''; $flags = $this->userBlockFlags($ip, $users_ids[$name], $user); # Show if account is local only $authUser = $wgAuth->getUserInstance($user); if ($user->getId() && $authUser->getId() === 0) { $flags[] = '<strong>(' . wfMsgHtml('checkuser-localonly') . ')</strong>'; } # Check for extra user rights... if ($users_ids[$name]) { if ($user->isLocked()) { $flags[] = '<b>(' . wfMsgHtml('checkuser-locked') . ')</b>'; } $list = array(); foreach ($user->getGroups() as $group) { $list[] = self::buildGroupLink($group, $user->getName()); } $groups = $wgLang->commaList($list); if ($groups) { $flags[] = '<i>(' . $groups . ')</i>'; } } # Check how many accounts the user made recently? if ($ip) { $key = wfMemcKey('acctcreate', 'ip', $ip); $count = intval($wgMemc->get($key)); if ($count) { $flags[] = '<strong>[' . wfMsgExt('checkuser-accounts', 'parsemag', $wgLang->formatNum($count)) . ']</strong>'; } } $s .= implode(' ', $flags); $s .= '<ol>'; # List out each IP/XFF combo for this username for ($i = count($users_infosets[$name]) - 1; $i >= 0; $i--) { $set = $users_infosets[$name][$i]; # IP link $s .= '<li>'; $s .= '<a href="' . $this->getTitle()->escapeLocalURL('user='******'">' . htmlspecialchars($set[0]) . '</a>'; # XFF string, link to /xff search if ($set[1]) { # Flag our trusted proxies list($client, $trusted) = CheckUserHooks::getClientIPfromXFF($set[1], $set[0]); $c = $trusted ? '#F0FFF0' : '#FFFFCC'; $s .= '   <span style="background-color: ' . $c . '"><strong>XFF</strong>: '; $s .= $this->sk->makeKnownLinkObj($this->getTitle(), htmlspecialchars($set[1]), 'user='******'/xff') . '</span>'; } $s .= "</li>\n"; } $s .= '</ol><br /><ol>'; # List out each agent for this username for ($i = count($users_agentsets[$name]) - 1; $i >= 0; $i--) { $agent = $users_agentsets[$name][$i]; $s .= '<li><i>' . htmlspecialchars($agent) . "</i></li>\n"; } $s .= '</ol>'; $s .= '</li>'; } $s .= "</ul></div>\n"; if ($wgUser->isAllowed('block') && !$wgUser->isBlocked()) { $s .= "<fieldset>\n"; $s .= '<legend>' . wfMsgHtml('checkuser-massblock') . "</legend>\n"; $s .= '<p>' . wfMsgExt('checkuser-massblock-text', array('parseinline')) . "</p>\n"; $s .= '<table><tr>' . '<td>' . Xml::check('usetag', false, array('id' => 'usetag')) . '</td>' . '<td>' . Xml::label(wfMsgHtml('checkuser-blocktag'), 'usetag') . '</td>' . '<td>' . Xml::input('tag', 46, $tag, array('id' => 'blocktag')) . '</td>' . '</tr><tr>' . '<td>' . Xml::check('usettag', false, array('id' => 'usettag')) . '</td>' . '<td>' . Xml::label(wfMsgHtml('checkuser-blocktag-talk'), 'usettag') . '</td>' . '<td>' . Xml::input('talktag', 46, $talkTag, array('id' => 'talktag')) . '</td>' . '</tr></table>'; $s .= '<p>' . wfMsgHtml('checkuser-reason') . ' '; $s .= Xml::input('blockreason', 46, '', array('maxlength' => '150', 'id' => 'blockreason')); $s .= ' ' . Xml::submitButton(wfMsgHtml('checkuser-massblock-commit'), array('id' => 'checkuserblocksubmit', 'name' => 'checkuserblock')) . "</p>\n"; $s .= "</fieldset>\n"; } $s .= '</form>'; } $wgOut->addHTML($s); }
/** * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case * encoding, zero padded to 31 digits. * * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36 * fairly neatly. * * Returns false on failure */ static function sha1Base36($path) { wfSuppressWarnings(); $hash = sha1_file($path); wfRestoreWarnings(); if ($hash === false) { return false; } else { return wfBaseConvert($hash, 16, 36, 31); } }
/** * @return bool|string */ protected function getSourceSha1Base36() { wfSuppressWarnings(); $hash = sha1_file($this->params['src']); wfRestoreWarnings(); if ($hash !== false) { $hash = wfBaseConvert($hash, 16, 36, 31); } return $hash; }
private function extractRowInfo($row) { $revision = new Revision($row); $title = $revision->getTitle(); $vals = array(); if ($this->fld_ids) { $vals['revid'] = intval($revision->getId()); // $vals['oldid'] = intval( $row->rev_text_id ); // todo: should this be exposed? if (!is_null($revision->getParentId())) { $vals['parentid'] = intval($revision->getParentId()); } } if ($this->fld_flags && $revision->isMinor()) { $vals['minor'] = ''; } if ($this->fld_user || $this->fld_userid) { if ($revision->isDeleted(Revision::DELETED_USER)) { $vals['userhidden'] = ''; } else { if ($this->fld_user) { $vals['user'] = $revision->getUserText(); } $userid = $revision->getUser(); if (!$userid) { $vals['anon'] = ''; } if ($this->fld_userid) { $vals['userid'] = $userid; } } } if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $revision->getTimestamp()); } if ($this->fld_size) { if (!is_null($revision->getSize())) { $vals['size'] = intval($revision->getSize()); } else { $vals['size'] = 0; } } if ($this->fld_sha1) { if ($revision->getSha1() != '') { $vals['sha1'] = wfBaseConvert($revision->getSha1(), 36, 16, 40); } else { $vals['sha1'] = ''; } } if ($this->fld_comment || $this->fld_parsedcomment) { if ($revision->isDeleted(Revision::DELETED_COMMENT)) { $vals['commenthidden'] = ''; } else { $comment = $revision->getComment(); if ($this->fld_comment) { $vals['comment'] = $comment; } if ($this->fld_parsedcomment) { $vals['parsedcomment'] = Linker::formatComment($comment, $title); } } } if ($this->fld_tags) { if ($row->ts_tags) { $tags = explode(',', $row->ts_tags); $this->getResult()->setIndexedTagName($tags, 'tag'); $vals['tags'] = $tags; } else { $vals['tags'] = array(); } } if (!is_null($this->token)) { $tokenFunctions = $this->getTokenFunctions(); foreach ($this->token as $t) { $val = call_user_func($tokenFunctions[$t], $title->getArticleID(), $title, $revision); if ($val === false) { $this->setWarning("Action '{$t}' is not allowed for the current user"); } else { $vals[$t . 'token'] = $val; } } } $text = null; global $wgParser; if ($this->fld_content || !is_null($this->difftotext)) { $text = $revision->getText(); // Expand templates after getting section content because // template-added sections don't count and Parser::preprocess() // will have less input if ($this->section !== false) { $text = $wgParser->getSection($text, $this->section, false); if ($text === false) { $this->dieUsage("There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection'); } } } if ($this->fld_content && !$revision->isDeleted(Revision::DELETED_TEXT)) { if ($this->generateXML) { $wgParser->startExternalParse($title, ParserOptions::newFromContext($this->getContext()), OT_PREPROCESS); $dom = $wgParser->preprocessToDom($text); if (is_callable(array($dom, 'saveXML'))) { $xml = $dom->saveXML(); } else { $xml = $dom->__toString(); } $vals['parsetree'] = $xml; } if ($this->expandTemplates && !$this->parseContent) { $text = $wgParser->preprocess($text, $title, ParserOptions::newFromContext($this->getContext())); } if ($this->parseContent) { $text = $wgParser->parse($text, $title, ParserOptions::newFromContext($this->getContext()))->getText(); } ApiResult::setContent($vals, $text); } elseif ($this->fld_content) { $vals['texthidden'] = ''; } if (!is_null($this->diffto) || !is_null($this->difftotext)) { global $wgAPIMaxUncachedDiffs; static $n = 0; // Number of uncached diffs we've had if ($n < $wgAPIMaxUncachedDiffs) { $vals['diff'] = array(); $context = new DerivativeContext($this->getContext()); $context->setTitle($title); if (!is_null($this->difftotext)) { $engine = new DifferenceEngine($context); $engine->setText($text, $this->difftotext); } else { $engine = new DifferenceEngine($context, $revision->getID(), $this->diffto); $vals['diff']['from'] = $engine->getOldid(); $vals['diff']['to'] = $engine->getNewid(); } $difftext = $engine->getDiffBody(); ApiResult::setContent($vals['diff'], $difftext); if (!$engine->wasCacheHit()) { $n++; } } else { $vals['diff']['notcached'] = ''; } } return $vals; }
/** * @see FileBackendStore::doStoreInternal() * @return Status */ protected function doStoreInternal(array $params) { $status = Status::newGood(); list($dstCont, $dstRel) = $this->resolveStoragePathReal($params['dst']); if ($dstRel === null) { $status->fatal('backend-fail-invalidpath', $params['dst']); return $status; } // (a) Check the destination container and object try { $dContObj = $this->getContainer($dstCont); } catch (NoSuchContainerException $e) { $status->fatal('backend-fail-copy', $params['src'], $params['dst']); return $status; } catch (CloudFilesException $e) { // some other exception? $this->handleException($e, $status, __METHOD__, $params); return $status; } // (b) Get a SHA-1 hash of the object wfSuppressWarnings(); $sha1Hash = sha1_file($params['src']); wfRestoreWarnings(); if ($sha1Hash === false) { // source doesn't exist? $status->fatal('backend-fail-copy', $params['src'], $params['dst']); return $status; } $sha1Hash = wfBaseConvert($sha1Hash, 16, 36, 31); // (c) Actually store the object try { // Create a fresh CF_Object with no fields preloaded. // We don't want to preserve headers, metadata, and such. $obj = new CF_Object($dContObj, $dstRel, false, false); // skip HEAD $obj->setMetadataValues(array('Sha1base36' => $sha1Hash)); // The MD5 here will be checked within Swift against its own MD5. $obj->set_etag(md5_file($params['src'])); // Use the same content type as StreamFile for security $obj->content_type = StreamFile::contentTypeFromPath($params['dst']); if (!strlen($obj->content_type)) { // special case $obj->content_type = 'unknown/unknown'; } // Set any other custom headers if requested if (isset($params['headers'])) { $obj->headers += $this->sanitizeHdrs($params['headers']); } if (!empty($params['async'])) { // deferred wfSuppressWarnings(); $fp = fopen($params['src'], 'rb'); wfRestoreWarnings(); if (!$fp) { $status->fatal('backend-fail-copy', $params['src'], $params['dst']); } else { $op = $obj->write_async($fp, filesize($params['src']), true); $status->value = new SwiftFileOpHandle($this, $params, 'Store', $op); $status->value->resourcesToClose[] = $fp; $status->value->affectedObjects[] = $obj; } } else { // actually write the object in Swift $obj->load_from_filename($params['src'], true); // calls $obj->write() $this->purgeCDNCache(array($obj)); } } catch (CDNNotEnabledException $e) { // CDN not enabled; nothing to see here } catch (BadContentTypeException $e) { $status->fatal('backend-fail-contenttype', $params['dst']); } catch (IOException $e) { $status->fatal('backend-fail-copy', $params['src'], $params['dst']); } catch (CloudFilesException $e) { // some other exception? $this->handleException($e, $status, __METHOD__, $params); } return $status; }
/** * Given a string range in a number of formats, return the * start and end of the range in hexadecimal. For IPv6. * * Formats are: * 2001:0db8:85a3::7344/96 CIDR * 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range * 2001:0db8:85a3::7344/96 Single IP * * @param string $range * * @return array(string, string) */ private static function parseRange6($range) { # Expand any IPv6 IP $range = IP::sanitizeIP($range); // CIDR notation... if (strpos($range, '/') !== false) { list($network, $bits) = self::parseCIDR6($range); if ($network === false) { $start = $end = false; } else { $start = wfBaseConvert($network, 10, 16, 32, false); # Turn network to binary (again) $end = wfBaseConvert($network, 10, 2, 128); # Truncate the last (128-$bits) bits and replace them with ones $end = str_pad(substr($end, 0, $bits), 128, 1, STR_PAD_RIGHT); # Convert to hex $end = wfBaseConvert($end, 2, 16, 32, false); # see toHex() comment $start = "v6-{$start}"; $end = "v6-{$end}"; } // Explicit range notation... } elseif (strpos($range, '-') !== false) { list($start, $end) = array_map('trim', explode('-', $range, 2)); $start = self::toHex($start); $end = self::toHex($end); if ($start > $end) { $start = $end = false; } } else { # Single IP $start = $end = self::toHex($range); } if ($start === false || $end === false) { return array(false, false); } else { return array($start, $end); } }
public function execute() { global $wgContLang; $params = $this->extractRequestParams(); $this->requireMaxOneParameter($params, 'users', 'ip'); $prop = array_flip($params['prop']); $fld_id = isset($prop['id']); $fld_user = isset($prop['user']); $fld_userid = isset($prop['userid']); $fld_by = isset($prop['by']); $fld_byid = isset($prop['byid']); $fld_timestamp = isset($prop['timestamp']); $fld_expiry = isset($prop['expiry']); $fld_reason = isset($prop['reason']); $fld_range = isset($prop['range']); $fld_flags = isset($prop['flags']); $result = $this->getResult(); $this->addTables('ipblocks'); $this->addFields('ipb_auto'); $this->addFieldsIf('ipb_id', $fld_id); $this->addFieldsIf(array('ipb_address', 'ipb_user'), $fld_user || $fld_userid); $this->addFieldsIf('ipb_by_text', $fld_by); $this->addFieldsIf('ipb_by', $fld_byid); $this->addFieldsIf('ipb_timestamp', $fld_timestamp); $this->addFieldsIf('ipb_expiry', $fld_expiry); $this->addFieldsIf('ipb_reason', $fld_reason); $this->addFieldsIf(array('ipb_range_start', 'ipb_range_end'), $fld_range); $this->addFieldsIf(array('ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock', 'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk'), $fld_flags); $this->addOption('LIMIT', $params['limit'] + 1); $this->addTimestampWhereRange('ipb_timestamp', $params['dir'], $params['start'], $params['end']); $db = $this->getDB(); if (isset($params['ids'])) { $this->addWhereFld('ipb_id', $params['ids']); } if (isset($params['users'])) { foreach ((array) $params['users'] as $u) { $this->prepareUsername($u); } $this->addWhereFld('ipb_address', $this->usernames); $this->addWhereFld('ipb_auto', 0); } if (isset($params['ip'])) { list($ip, $range) = IP::parseCIDR($params['ip']); if ($ip && $range) { // We got a CIDR range if ($range < 16) { $this->dieUsage('CIDR ranges broader than /16 are not accepted', 'cidrtoobroad'); } $lower = wfBaseConvert($ip, 10, 16, 8, false); $upper = wfBaseConvert($ip + pow(2, 32 - $range) - 1, 10, 16, 8, false); } else { $lower = $upper = IP::toHex($params['ip']); } $prefix = substr($lower, 0, 4); # Fairly hard to make a malicious SQL statement out of hex characters, # but it is good practice to add quotes $lower = $db->addQuotes($lower); $upper = $db->addQuotes($upper); $this->addWhere(array('ipb_range_start' . $db->buildLike($prefix, $db->anyString()), 'ipb_range_start <= ' . $lower, 'ipb_range_end >= ' . $upper, 'ipb_auto' => 0)); } if (!is_null($params['show'])) { $show = array_flip($params['show']); /* Check for conflicting parameters. */ if (isset($show['account']) && isset($show['!account']) || isset($show['ip']) && isset($show['!ip']) || isset($show['range']) && isset($show['!range']) || isset($show['temp']) && isset($show['!temp'])) { $this->dieUsageMsg('show'); } $this->addWhereIf('ipb_user = 0', isset($show['!account'])); $this->addWhereIf('ipb_user != 0', isset($show['account'])); $this->addWhereIf('ipb_user != 0 OR ipb_range_end > ipb_range_start', isset($show['!ip'])); $this->addWhereIf('ipb_user = 0 AND ipb_range_end = ipb_range_start', isset($show['ip'])); $this->addWhereIf('ipb_expiry = ' . $db->addQuotes($db->getInfinity()), isset($show['!temp'])); $this->addWhereIf('ipb_expiry != ' . $db->addQuotes($db->getInfinity()), isset($show['temp'])); $this->addWhereIf("ipb_range_end = ipb_range_start", isset($show['!range'])); $this->addWhereIf("ipb_range_end > ipb_range_start", isset($show['range'])); } if (!$this->getUser()->isAllowed('hideuser')) { $this->addWhereFld('ipb_deleted', 0); } // Purge expired entries on one in every 10 queries if (!mt_rand(0, 10)) { Block::purgeExpired(); } $res = $this->select(__METHOD__); $count = 0; foreach ($res as $row) { if (++$count > $params['limit']) { // We've had enough $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ipb_timestamp)); break; } $block = array(); if ($fld_id) { $block['id'] = $row->ipb_id; } if ($fld_user && !$row->ipb_auto) { $block['user'] = $row->ipb_address; } if ($fld_userid && !$row->ipb_auto) { $block['userid'] = $row->ipb_user; } if ($fld_by) { $block['by'] = $row->ipb_by_text; } if ($fld_byid) { $block['byid'] = $row->ipb_by; } if ($fld_timestamp) { $block['timestamp'] = wfTimestamp(TS_ISO_8601, $row->ipb_timestamp); } if ($fld_expiry) { $block['expiry'] = $wgContLang->formatExpiry($row->ipb_expiry, TS_ISO_8601); } if ($fld_reason) { $block['reason'] = $row->ipb_reason; } if ($fld_range && !$row->ipb_auto) { $block['rangestart'] = IP::hexToQuad($row->ipb_range_start); $block['rangeend'] = IP::hexToQuad($row->ipb_range_end); } if ($fld_flags) { // For clarity, these flags use the same names as their action=block counterparts if ($row->ipb_auto) { $block['automatic'] = ''; } if ($row->ipb_anon_only) { $block['anononly'] = ''; } if ($row->ipb_create_account) { $block['nocreate'] = ''; } if ($row->ipb_enable_autoblock) { $block['autoblock'] = ''; } if ($row->ipb_block_email) { $block['noemail'] = ''; } if ($row->ipb_deleted) { $block['hidden'] = ''; } if ($row->ipb_allow_usertalk) { $block['allowusertalk'] = ''; } } $fit = $result->addValue(array('query', $this->getModuleName()), null, $block); if (!$fit) { $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ipb_timestamp)); break; } } $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'block'); }
/** * Get result information for an image revision * @param File f The image * @return array Result array */ protected function getInfo($f) { $vals = array(); if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $f->getTimestamp()); } if ($this->fld_user) { $vals['user'] = $f->getUser(); if (!$f->getUser('id')) { $vals['anon'] = ''; } } if ($this->fld_size) { $vals['size'] = intval($f->getSize()); $vals['width'] = intval($f->getWidth()); $vals['height'] = intval($f->getHeight()); } if ($this->fld_url) { if ($this->scale && !$f->isOld()) { $thumb = $f->getThumbnail($this->urlwidth, $this->urlheight); if ($thumb) { $vals['thumburl'] = $thumb->getURL(); $vals['thumbwidth'] = $thumb->getWidth(); $vals['thumbheight'] = $thumb->getHeight(); } } $vals['url'] = $f->getURL(); } if ($this->fld_comment) { $vals['comment'] = $f->getDescription(); } if ($this->fld_sha1) { $vals['sha1'] = wfBaseConvert($f->getSha1(), 36, 16, 40); } if ($this->fld_metadata) { $metadata = unserialize($f->getMetadata()); $vals['metadata'] = $metadata ? $metadata : null; $this->getResult()->setIndexedTagName_recursive($vals['metadata'], 'meta'); } return $vals; }
function getSha1() { return wfBaseConvert(strval(@$this->mInfo['sha1']), 16, 36, 31); }
private function doTestDoOperationsFailing() { $base = self::baseStorePath(); $fileA = "{$base}/unittest-cont2/a/b/fileA.txt"; $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq'; $fileB = "{$base}/unittest-cont2/a/b/fileB.txt"; $fileBContents = 'g-jmq3gpqgt3qtg q3GT '; $fileC = "{$base}/unittest-cont2/a/b/fileC.txt"; $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag'; $fileD = "{$base}/unittest-cont2/a/b/fileD.txt"; $this->prepare(array('dir' => dirname($fileA))); $this->create(array('dst' => $fileA, 'content' => $fileAContents)); $this->prepare(array('dir' => dirname($fileB))); $this->create(array('dst' => $fileB, 'content' => $fileBContents)); $this->prepare(array('dir' => dirname($fileC))); $this->create(array('dst' => $fileC, 'content' => $fileCContents)); $status = $this->backend->doOperations(array(array('op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1), array('op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1), array('op' => 'copy', 'src' => $fileB, 'dst' => $fileD, 'overwrite' => 1), array('op' => 'move', 'src' => $fileC, 'dst' => $fileD), array('op' => 'move', 'src' => $fileB, 'dst' => $fileC, 'overwriteSame' => 1), array('op' => 'move', 'src' => $fileB, 'dst' => $fileA, 'overwrite' => 1), array('op' => 'delete', 'src' => $fileD), array('op' => 'null')), array('force' => 1)); $this->assertNotEquals(array(), $status->errors, "Operation had warnings"); $this->assertEquals(true, $status->isOK(), "Operation batch succeeded"); $this->assertEquals(8, count($status->success), "Operation batch has correct success array"); $this->assertEquals(false, $this->backend->fileExists(array('src' => $fileB)), "File does not exist at {$fileB}"); $this->assertEquals(false, $this->backend->fileExists(array('src' => $fileD)), "File does not exist at {$fileD}"); $this->assertEquals(true, $this->backend->fileExists(array('src' => $fileA)), "File does not exist at {$fileA}"); $this->assertEquals(true, $this->backend->fileExists(array('src' => $fileC)), "File exists at {$fileC}"); $this->assertEquals($fileBContents, $this->backend->getFileContents(array('src' => $fileA)), "Correct file contents of {$fileA}"); $this->assertEquals(strlen($fileBContents), $this->backend->getFileSize(array('src' => $fileA)), "Correct file size of {$fileA}"); $this->assertEquals(wfBaseConvert(sha1($fileBContents), 16, 36, 31), $this->backend->getFileSha1Base36(array('src' => $fileA)), "Correct file SHA-1 of {$fileA}"); }
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'); }
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'); }
/** * Generate a new random session ID * @return string */ public function generateSessionId() { do { $id = wfBaseConvert(\MWCryptRand::generateHex(40), 16, 32, 32); $key = wfMemcKey('MWSession', $id); } while (isset($this->allSessionIds[$id]) || is_array($this->store->get($key))); return $id; }
/** * Extract information from the Revision * * @param Revision $revision * @param object $row Should have a field 'ts_tags' if $this->fld_tags is set * @return array */ protected function extractRevisionInfo(Revision $revision, $row) { $title = $revision->getTitle(); $user = $this->getUser(); $vals = array(); $anyHidden = false; if ($this->fld_ids) { $vals['revid'] = intval($revision->getId()); if (!is_null($revision->getParentId())) { $vals['parentid'] = intval($revision->getParentId()); } } if ($this->fld_flags) { $vals['minor'] = $revision->isMinor(); } if ($this->fld_user || $this->fld_userid) { if ($revision->isDeleted(Revision::DELETED_USER)) { $vals['userhidden'] = true; $anyHidden = true; } if ($revision->userCan(Revision::DELETED_USER, $user)) { if ($this->fld_user) { $vals['user'] = $revision->getUserText(Revision::RAW); } $userid = $revision->getUser(Revision::RAW); if (!$userid) { $vals['anon'] = true; } if ($this->fld_userid) { $vals['userid'] = $userid; } } } if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $revision->getTimestamp()); } if ($this->fld_size) { if (!is_null($revision->getSize())) { $vals['size'] = intval($revision->getSize()); } else { $vals['size'] = 0; } } if ($this->fld_sha1) { if ($revision->isDeleted(Revision::DELETED_TEXT)) { $vals['sha1hidden'] = true; $anyHidden = true; } if ($revision->userCan(Revision::DELETED_TEXT, $user)) { if ($revision->getSha1() != '') { $vals['sha1'] = wfBaseConvert($revision->getSha1(), 36, 16, 40); } else { $vals['sha1'] = ''; } } } if ($this->fld_contentmodel) { $vals['contentmodel'] = $revision->getContentModel(); } if ($this->fld_comment || $this->fld_parsedcomment) { if ($revision->isDeleted(Revision::DELETED_COMMENT)) { $vals['commenthidden'] = true; $anyHidden = true; } if ($revision->userCan(Revision::DELETED_COMMENT, $user)) { $comment = $revision->getComment(Revision::RAW); if ($this->fld_comment) { $vals['comment'] = $comment; } if ($this->fld_parsedcomment) { $vals['parsedcomment'] = Linker::formatComment($comment, $title); } } } if ($this->fld_tags) { if ($row->ts_tags) { $tags = explode(',', $row->ts_tags); ApiResult::setIndexedTagName($tags, 'tag'); $vals['tags'] = $tags; } else { $vals['tags'] = array(); } } $content = null; global $wgParser; if ($this->fetchContent) { $content = $revision->getContent(Revision::FOR_THIS_USER, $this->getUser()); // Expand templates after getting section content because // template-added sections don't count and Parser::preprocess() // will have less input if ($content && $this->section !== false) { $content = $content->getSection($this->section, false); if (!$content) { $this->dieUsage("There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection'); } } if ($revision->isDeleted(Revision::DELETED_TEXT)) { $vals['texthidden'] = true; $anyHidden = true; } elseif (!$content) { $vals['textmissing'] = true; } } if ($this->fld_content && $content) { $text = null; if ($this->generateXML) { if ($content->getModel() === CONTENT_MODEL_WIKITEXT) { $t = $content->getNativeData(); # note: don't set $text $wgParser->startExternalParse($title, ParserOptions::newFromContext($this->getContext()), Parser::OT_PREPROCESS); $dom = $wgParser->preprocessToDom($t); if (is_callable(array($dom, 'saveXML'))) { $xml = $dom->saveXML(); } else { $xml = $dom->__toString(); } $vals['parsetree'] = $xml; } else { $vals['badcontentformatforparsetree'] = true; $this->setWarning("Conversion to XML is supported for wikitext only, " . $title->getPrefixedDBkey() . " uses content model " . $content->getModel()); } } if ($this->expandTemplates && !$this->parseContent) { #XXX: implement template expansion for all content types in ContentHandler? if ($content->getModel() === CONTENT_MODEL_WIKITEXT) { $text = $content->getNativeData(); $text = $wgParser->preprocess($text, $title, ParserOptions::newFromContext($this->getContext())); } else { $this->setWarning("Template expansion is supported for wikitext only, " . $title->getPrefixedDBkey() . " uses content model " . $content->getModel()); $vals['badcontentformat'] = true; $text = false; } } if ($this->parseContent) { $po = $content->getParserOutput($title, $revision->getId(), ParserOptions::newFromContext($this->getContext())); $text = $po->getText(); } if ($text === null) { $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat(); $model = $content->getModel(); if (!$content->isSupportedFormat($format)) { $name = $title->getPrefixedDBkey(); $this->setWarning("The requested format {$this->contentFormat} is not " . "supported for content model {$model} used by {$name}"); $vals['badcontentformat'] = true; $text = false; } else { $text = $content->serialize($format); // always include format and model. // Format is needed to deserialize, model is needed to interpret. $vals['contentformat'] = $format; $vals['contentmodel'] = $model; } } if ($text !== false) { ApiResult::setContentValue($vals, 'content', $text); } } if ($content && (!is_null($this->diffto) || !is_null($this->difftotext))) { static $n = 0; // Number of uncached diffs we've had if ($n < $this->getConfig()->get('APIMaxUncachedDiffs')) { $vals['diff'] = array(); $context = new DerivativeContext($this->getContext()); $context->setTitle($title); $handler = $revision->getContentHandler(); if (!is_null($this->difftotext)) { $model = $title->getContentModel(); if ($this->contentFormat && !ContentHandler::getForModelID($model)->isSupportedFormat($this->contentFormat)) { $name = $title->getPrefixedDBkey(); $this->setWarning("The requested format {$this->contentFormat} is not " . "supported for content model {$model} used by {$name}"); $vals['diff']['badcontentformat'] = true; $engine = null; } else { $difftocontent = ContentHandler::makeContent($this->difftotext, $title, $model, $this->contentFormat); $engine = $handler->createDifferenceEngine($context); $engine->setContent($content, $difftocontent); } } else { $engine = $handler->createDifferenceEngine($context, $revision->getID(), $this->diffto); $vals['diff']['from'] = $engine->getOldid(); $vals['diff']['to'] = $engine->getNewid(); } if ($engine) { $difftext = $engine->getDiffBody(); ApiResult::setContentValue($vals['diff'], 'body', $difftext); if (!$engine->wasCacheHit()) { $n++; } } } else { $vals['diff']['notcached'] = true; } } if ($anyHidden && $revision->isDeleted(Revision::DELETED_RESTRICTED)) { $vals['suppressed'] = true; } return $vals; }
/** * Extracts from a single sql row the data needed to describe one recent change. * * @param stdClass $row The row from which to extract the data. * @return array An array mapping strings (descriptors) to their respective string values. * @access public */ public function extractRowInfo($row) { /* Determine the title of the page that has been changed. */ $title = Title::makeTitle($row->rc_namespace, $row->rc_title); $user = $this->getUser(); /* Our output data. */ $vals = array(); $type = intval($row->rc_type); $vals['type'] = RecentChange::parseFromRCType($type); $anyHidden = false; /* Create a new entry in the result for the title. */ if ($this->fld_title || $this->fld_ids) { if ($type === RC_LOG && $row->rc_deleted & LogPage::DELETED_ACTION) { $vals['actionhidden'] = true; $anyHidden = true; } if ($type !== RC_LOG || LogEventsList::userCanBitfield($row->rc_deleted, LogPage::DELETED_ACTION, $user)) { if ($this->fld_title) { ApiQueryBase::addTitleInfo($vals, $title); } if ($this->fld_ids) { $vals['pageid'] = intval($row->rc_cur_id); $vals['revid'] = intval($row->rc_this_oldid); $vals['old_revid'] = intval($row->rc_last_oldid); } } } if ($this->fld_ids) { $vals['rcid'] = intval($row->rc_id); } /* Add user data and 'anon' flag, if user is anonymous. */ if ($this->fld_user || $this->fld_userid) { if ($row->rc_deleted & Revision::DELETED_USER) { $vals['userhidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->rc_deleted, Revision::DELETED_USER, $user)) { if ($this->fld_user) { $vals['user'] = $row->rc_user_text; } if ($this->fld_userid) { $vals['userid'] = $row->rc_user; } if (!$row->rc_user) { $vals['anon'] = true; } } } /* Add flags, such as new, minor, bot. */ if ($this->fld_flags) { $vals['bot'] = (bool) $row->rc_bot; $vals['new'] = $row->rc_type == RC_NEW; $vals['minor'] = (bool) $row->rc_minor; } /* Add sizes of each revision. (Only available on 1.10+) */ if ($this->fld_sizes) { $vals['oldlen'] = intval($row->rc_old_len); $vals['newlen'] = intval($row->rc_new_len); } /* Add the timestamp. */ if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rc_timestamp); } /* Add edit summary / log summary. */ if ($this->fld_comment || $this->fld_parsedcomment) { if ($row->rc_deleted & Revision::DELETED_COMMENT) { $vals['commenthidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->rc_deleted, Revision::DELETED_COMMENT, $user)) { if ($this->fld_comment && isset($row->rc_comment)) { $vals['comment'] = $row->rc_comment; } if ($this->fld_parsedcomment && isset($row->rc_comment)) { $vals['parsedcomment'] = Linker::formatComment($row->rc_comment, $title); } } } if ($this->fld_redirect) { $vals['redirect'] = (bool) $row->page_is_redirect; } /* Add the patrolled flag */ if ($this->fld_patrolled) { $vals['patrolled'] = $row->rc_patrolled == 1; $vals['unpatrolled'] = ChangesList::isUnpatrolled($row, $user); } if ($this->fld_loginfo && $row->rc_type == RC_LOG) { if ($row->rc_deleted & LogPage::DELETED_ACTION) { $vals['actionhidden'] = true; $anyHidden = true; } if (LogEventsList::userCanBitfield($row->rc_deleted, LogPage::DELETED_ACTION, $user)) { $vals['logid'] = intval($row->rc_logid); $vals['logtype'] = $row->rc_log_type; $vals['logaction'] = $row->rc_log_action; $vals['logparams'] = LogFormatter::newFromRow($row)->formatParametersForApi(); } } if ($this->fld_tags) { if ($row->ts_tags) { $tags = explode(',', $row->ts_tags); ApiResult::setIndexedTagName($tags, 'tag'); $vals['tags'] = $tags; } else { $vals['tags'] = array(); } } if ($this->fld_sha1 && $row->rev_sha1 !== null) { if ($row->rev_deleted & Revision::DELETED_TEXT) { $vals['sha1hidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->rev_deleted, Revision::DELETED_TEXT, $user)) { if ($row->rev_sha1 !== '') { $vals['sha1'] = wfBaseConvert($row->rev_sha1, 36, 16, 40); } else { $vals['sha1'] = ''; } } } if (!is_null($this->token)) { $tokenFunctions = $this->getTokenFunctions(); foreach ($this->token as $t) { $val = call_user_func($tokenFunctions[$t], $row->rc_cur_id, $title, RecentChange::newFromRow($row)); if ($val === false) { $this->setWarning("Action '{$t}' is not allowed for the current user"); } else { $vals[$t . 'token'] = $val; } } } if ($anyHidden && $row->rc_deleted & Revision::DELETED_RESTRICTED) { $vals['suppressed'] = true; } return $vals; }
/** * @param $resultPageSet ApiPageSet * @return void */ private function run($resultPageSet = null) { $repo = $this->mRepo; if (!$repo instanceof LocalRepo) { $this->dieUsage('Local file repository does not support querying all images', 'unsupportedrepo'); } $prefix = $this->getModulePrefix(); $db = $this->getDB(); $params = $this->extractRequestParams(); // Table and return fields $this->addTables('image'); $prop = array_flip($params['prop']); $this->addFields(LocalFile::selectFields()); $ascendingOrder = true; if ($params['dir'] == 'descending' || $params['dir'] == 'older') { $ascendingOrder = false; } if ($params['sort'] == 'name') { // Check mutually exclusive params $disallowed = array('start', 'end', 'user'); foreach ($disallowed as $pname) { if (isset($params[$pname])) { $this->dieUsage("Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=timestamp", 'badparams'); } } if ($params['filterbots'] != 'all') { $this->dieUsage("Parameter '{$prefix}filterbots' can only be used with {$prefix}sort=timestamp", 'badparams'); } // Pagination if (!is_null($params['continue'])) { $cont = explode('|', $params['continue']); $this->dieContinueUsageIf(count($cont) != 1); $op = $ascendingOrder ? '>' : '<'; $continueFrom = $db->addQuotes($cont[0]); $this->addWhere("img_name {$op}= {$continueFrom}"); } // Image filters $from = $params['from'] === null ? null : $this->titlePartToKey($params['from'], NS_FILE); $to = $params['to'] === null ? null : $this->titlePartToKey($params['to'], NS_FILE); $this->addWhereRange('img_name', $ascendingOrder ? 'newer' : 'older', $from, $to); if (isset($params['prefix'])) { $this->addWhere('img_name' . $db->buildLike($this->titlePartToKey($params['prefix'], NS_FILE), $db->anyString())); } } else { // Check mutually exclusive params $disallowed = array('from', 'to', 'prefix'); foreach ($disallowed as $pname) { if (isset($params[$pname])) { $this->dieUsage("Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=name", 'badparams'); } } if (!is_null($params['user']) && $params['filterbots'] != 'all') { // Since filterbots checks if each user has the bot right, it // doesn't make sense to use it with user $this->dieUsage("Parameters '{$prefix}user' and '{$prefix}filterbots' cannot be used together", 'badparams'); } // Pagination $this->addTimestampWhereRange('img_timestamp', $ascendingOrder ? 'newer' : 'older', $params['start'], $params['end']); // Include in ORDER BY for uniqueness $this->addWhereRange('img_name', $ascendingOrder ? 'newer' : 'older', null, null); if (!is_null($params['continue'])) { $cont = explode('|', $params['continue']); $this->dieContinueUsageIf(count($cont) != 2); $op = $ascendingOrder ? '>' : '<'; $continueTimestamp = $db->addQuotes($db->timestamp($cont[0])); $continueName = $db->addQuotes($cont[1]); $this->addWhere("img_timestamp {$op} {$continueTimestamp} OR " . "(img_timestamp = {$continueTimestamp} AND " . "img_name {$op}= {$continueName})"); } // Image filters if (!is_null($params['user'])) { $this->addWhereFld('img_user_text', $params['user']); } if ($params['filterbots'] != 'all') { $this->addTables('user_groups'); $this->addJoinConds(array('user_groups' => array('LEFT JOIN', array('ug_group' => User::getGroupsWithPermission('bot'), 'ug_user = img_user')))); $groupCond = $params['filterbots'] == 'nobots' ? 'NULL' : 'NOT NULL'; $this->addWhere("ug_group IS {$groupCond}"); } } // Filters not depending on sort if (isset($params['minsize'])) { $this->addWhere('img_size>=' . intval($params['minsize'])); } if (isset($params['maxsize'])) { $this->addWhere('img_size<=' . intval($params['maxsize'])); } $sha1 = false; if (isset($params['sha1'])) { $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 (isset($params['sha1base36'])) { $sha1 = strtolower($params['sha1base36']); if (!$this->validateSha1Base36Hash($sha1)) { $this->dieUsage('The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash'); } } if ($sha1) { $this->addWhereFld('img_sha1', $sha1); } if (!is_null($params['mime'])) { global $wgMiserMode; if ($wgMiserMode) { $this->dieUsage('MIME search disabled in Miser Mode', 'mimesearchdisabled'); } list($major, $minor) = File::splitMime($params['mime']); $this->addWhereFld('img_major_mime', $major); $this->addWhereFld('img_minor_mime', $minor); } $limit = $params['limit']; $this->addOption('LIMIT', $limit + 1); $sortFlag = ''; if (!$ascendingOrder) { $sortFlag = ' DESC'; } if ($params['sort'] == 'timestamp') { $this->addOption('ORDER BY', 'img_timestamp' . $sortFlag); if (!is_null($params['user'])) { $this->addOption('USE INDEX', array('image' => 'img_usertext_timestamp')); } else { $this->addOption('USE INDEX', array('image' => 'img_timestamp')); } } else { $this->addOption('ORDER BY', 'img_name' . $sortFlag); } $res = $this->select(__METHOD__); $titles = array(); $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... if ($params['sort'] == 'name') { $this->setContinueEnumParameter('continue', $row->img_name); } else { $this->setContinueEnumParameter('continue', "{$row->img_timestamp}|{$row->img_name}"); } break; } if (is_null($resultPageSet)) { $file = $repo->newFileFromRow($row); $info = array_merge(array('name' => $row->img_name), ApiQueryImageInfo::getInfo($file, $prop, $result)); self::addTitleInfo($info, $file->getTitle()); $fit = $result->addValue(array('query', $this->getModuleName()), null, $info); if (!$fit) { if ($params['sort'] == 'name') { $this->setContinueEnumParameter('continue', $row->img_name); } else { $this->setContinueEnumParameter('continue', "{$row->img_timestamp}|{$row->img_name}"); } break; } } else { $titles[] = Title::makeTitle(NS_FILE, $row->img_name); } } if (is_null($resultPageSet)) { $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'img'); } else { $resultPageSet->populateFromTitles($titles); } }