/** * Determine if the current user is allowed to view a particular * field of this revision, if it's marked as deleted. * @param $rc RCCacheEntry * @param $field Integer * @param $user User object to check, or null to use $wgUser * @return Boolean */ public static function userCan($rc, $field, User $user = null) { if ($rc->mAttribs['rc_type'] == RC_LOG) { return LogEventsList::userCanBitfield($rc->mAttribs['rc_deleted'], $field, $user); } else { return Revision::userCanBitfield($rc->mAttribs['rc_deleted'], $field, $user); } }
/** * 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; }
/** * Determine if the current user is allowed to view a particular * field of this image file, if it's marked as deleted. * * @param $field Integer * @return bool */ function userCan($field) { $this->load(); return Revision::userCanBitfield($this->deleted, $field); }
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'); }
/** * Extract fields from the database row and append them to a result array * * @param $row * @return array */ private function extractRowInfo($row) { $vals = array(); $anyHidden = false; if ($row->rev_deleted & Revision::DELETED_TEXT) { $vals['texthidden'] = ''; $anyHidden = true; } // Any rows where we can't view the user were filtered out in the query. $vals['userid'] = $row->rev_user; $vals['user'] = $row->rev_user_text; if ($row->rev_deleted & Revision::DELETED_USER) { $vals['userhidden'] = ''; $anyHidden = true; } if ($this->fld_ids) { $vals['pageid'] = intval($row->rev_page); $vals['revid'] = intval($row->rev_id); // $vals['textid'] = intval( $row->rev_text_id ); // todo: Should this field be exposed? if (!is_null($row->rev_parent_id)) { $vals['parentid'] = intval($row->rev_parent_id); } } $title = Title::makeTitle($row->page_namespace, $row->page_title); if ($this->fld_title) { ApiQueryBase::addTitleInfo($vals, $title); } if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rev_timestamp); } if ($this->fld_flags) { if ($row->rev_parent_id == 0 && !is_null($row->rev_parent_id)) { $vals['new'] = ''; } if ($row->rev_minor_edit) { $vals['minor'] = ''; } if ($row->page_latest == $row->rev_id) { $vals['top'] = ''; } } if (($this->fld_comment || $this->fld_parsedcomment) && isset($row->rev_comment)) { if ($row->rev_deleted & Revision::DELETED_COMMENT) { $vals['commenthidden'] = ''; $anyHidden = true; } $userCanView = Revision::userCanBitfield($row->rev_deleted, Revision::DELETED_COMMENT, $this->getUser()); if ($userCanView) { if ($this->fld_comment) { $vals['comment'] = $row->rev_comment; } if ($this->fld_parsedcomment) { $vals['parsedcomment'] = Linker::formatComment($row->rev_comment, $title); } } } if ($this->fld_patrolled && $row->rc_patrolled) { $vals['patrolled'] = ''; } if ($this->fld_size && !is_null($row->rev_len)) { $vals['size'] = intval($row->rev_len); } if ($this->fld_sizediff && !is_null($row->rev_len) && !is_null($row->rev_parent_id)) { $parentLen = isset($this->parentLens[$row->rev_parent_id]) ? $this->parentLens[$row->rev_parent_id] : 0; $vals['sizediff'] = intval($row->rev_len - $parentLen); } 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 ($anyHidden && $row->rev_deleted & Revision::DELETED_RESTRICTED) { $vals['suppressed'] = ''; } return $vals; }
/** * Determine if the current user is allowed to view a particular * field of this FileStore image file, if it's marked as deleted. * @param int $field * @param null|User $user User object to check, or null to use $wgUser * @return bool */ public function userCan($field, User $user = null) { $this->load(); return Revision::userCanBitfield($this->deleted, $field, $user); }
private 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); /* Determine what kind of change this was. */ switch ($type) { case RC_EDIT: $vals['type'] = 'edit'; break; case RC_NEW: $vals['type'] = 'new'; break; case RC_MOVE: $vals['type'] = 'move'; break; case RC_LOG: $vals['type'] = 'log'; break; case RC_EXTERNAL: $vals['type'] = 'external'; break; case RC_MOVE_OVER_REDIRECT: $vals['type'] = 'move over redirect'; break; default: $vals['type'] = $type; } $anyHidden = false; /* Create a new entry in the result for the title. */ if ($this->fld_title || $this->fld_ids) { // These should already have been filtered out of the query, but just in case. if ($type === RC_LOG && $row->rc_deleted & LogPage::DELETED_ACTION) { $vals['actionhidden'] = ''; $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); } } } /* 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'] = ''; $anyHidden = true; } if (Revision::userCanBitfield($row->rc_deleted, Revision::DELETED_USER, $user)) { if ($this->fld_userid) { $vals['userid'] = $row->rc_user; // for backwards compatibility $vals['user'] = $row->rc_user; } if ($this->fld_user) { $vals['user'] = $row->rc_user_text; } if (!$row->rc_user) { $vals['anon'] = ''; } } } /* Add flags, such as new, minor, bot. */ if ($this->fld_flags) { if ($row->rc_bot) { $vals['bot'] = ''; } if ($row->rc_type == RC_NEW) { $vals['new'] = ''; } if ($row->rc_minor) { $vals['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); } if ($this->fld_notificationtimestamp) { $vals['notificationtimestamp'] = $row->wl_notificationtimestamp == null ? '' : wfTimestamp(TS_ISO_8601, $row->wl_notificationtimestamp); } /* Add edit summary / log summary. */ if ($this->fld_comment || $this->fld_parsedcomment) { if ($row->rc_deleted & Revision::DELETED_COMMENT) { $vals['commenthidden'] = ''; $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); } } } /* Add the patrolled flag */ if ($this->fld_patrol && $row->rc_patrolled == 1) { $vals['patrolled'] = ''; } if ($this->fld_patrol && ChangesList::isUnpatrolled($row, $user)) { $vals['unpatrolled'] = ''; } if ($this->fld_loginfo && $row->rc_type == RC_LOG) { if ($row->rc_deleted & LogPage::DELETED_ACTION) { $vals['actionhidden'] = ''; $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; $logEntry = DatabaseLogEntry::newFromRow((array) $row); ApiQueryLogEvents::addLogParams($this->getResult(), $vals, $logEntry->getParameters(), $logEntry->getType(), $logEntry->getSubtype(), $logEntry->getTimestamp()); } } if ($anyHidden && $row->rc_deleted & Revision::DELETED_RESTRICTED) { $vals['suppressed'] = ''; } return $vals; }
private 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 = []; $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) { // These should already have been filtered out of the query, but just in case. 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); } } } /* 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_userid) { $vals['userid'] = (int) $row->rc_user; // for backwards compatibility $vals['user'] = (int) $row->rc_user; } if ($this->fld_user) { $vals['user'] = $row->rc_user_text; } 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); } if ($this->fld_notificationtimestamp) { $vals['notificationtimestamp'] = $row->wl_notificationtimestamp == null ? '' : wfTimestamp(TS_ISO_8601, $row->wl_notificationtimestamp); } /* 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); } } } /* Add the patrolled flag */ if ($this->fld_patrol) { $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 ($anyHidden && $row->rc_deleted & Revision::DELETED_RESTRICTED) { $vals['suppressed'] = true; } return $vals; }
/** * Determine if the current user is allowed to view a particular * field of this FileStore image file, if it's marked as deleted. * @param int $field * @param null|User $user User object to check, or null to use $wgUser * @return bool */ public function userCan($field, User $user = null) { $this->load(); $title = $this->getTitle(); return Revision::userCanBitfield($this->deleted, $field, $user, $title ?: null); }
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 revision information', 'permissiondenied'); } $this->setWarning('list=deletedrevs has been deprecated. Please use prop=deletedrevisions or ' . 'list=alldeletedrevisions instead.'); $this->logFeatureUsage('action=query&list=deletedrevs'); $db = $this->getDB(); $params = $this->extractRequestParams(false); $prop = array_flip($params['prop']); $fld_parentid = isset($prop['parentid']); $fld_revid = isset($prop['revid']); $fld_user = isset($prop['user']); $fld_userid = isset($prop['userid']); $fld_comment = isset($prop['comment']); $fld_parsedcomment = isset($prop['parsedcomment']); $fld_minor = isset($prop['minor']); $fld_len = isset($prop['len']); $fld_sha1 = isset($prop['sha1']); $fld_content = isset($prop['content']); $fld_token = isset($prop['token']); $fld_tags = isset($prop['tags']); if (isset($prop['token'])) { $p = $this->getModulePrefix(); $this->setWarning("{$p}prop=token has been deprecated. Please use action=query&meta=tokens instead."); } // If we're in a mode that breaks the same-origin policy, no tokens can // be obtained if ($this->lacksSameOriginSecurity()) { $fld_token = false; } // If user can't undelete, no tokens if (!$user->isAllowed('undelete')) { $fld_token = false; } $result = $this->getResult(); $pageSet = $this->getPageSet(); $titles = $pageSet->getTitles(); // This module operates in three modes: // 'revs': List deleted revs for certain titles (1) // 'user': List deleted revs by a certain user (2) // 'all': List all deleted revs in NS (3) $mode = 'all'; if (count($titles) > 0) { $mode = 'revs'; } elseif (!is_null($params['user'])) { $mode = 'user'; } if ($mode == 'revs' || $mode == 'user') { // Ignore namespace and unique due to inability to know whether they were purposely set foreach (array('from', 'to', 'prefix') as $p) { if (!is_null($params[$p])) { $this->dieUsage("The '{$p}' parameter cannot be used in modes 1 or 2", 'badparams'); } } } else { foreach (array('start', 'end') as $p) { if (!is_null($params[$p])) { $this->dieUsage("The {$p} parameter cannot be used in mode 3", 'badparams'); } } } if (!is_null($params['user']) && !is_null($params['excludeuser'])) { $this->dieUsage('user and excludeuser cannot be used together', 'badparams'); } $this->addTables('archive'); $this->addFields(array('ar_title', 'ar_namespace', 'ar_timestamp', 'ar_deleted', 'ar_id')); $this->addFieldsIf('ar_parent_id', $fld_parentid); $this->addFieldsIf('ar_rev_id', $fld_revid); $this->addFieldsIf('ar_user_text', $fld_user); $this->addFieldsIf('ar_user', $fld_userid); $this->addFieldsIf('ar_comment', $fld_comment || $fld_parsedcomment); $this->addFieldsIf('ar_minor_edit', $fld_minor); $this->addFieldsIf('ar_len', $fld_len); $this->addFieldsIf('ar_sha1', $fld_sha1); if ($fld_tags) { $this->addTables('tag_summary'); $this->addJoinConds(array('tag_summary' => array('LEFT JOIN', array('ar_rev_id=ts_rev_id')))); $this->addFields('ts_tags'); } if (!is_null($params['tag'])) { $this->addTables('change_tag'); $this->addJoinConds(array('change_tag' => array('INNER JOIN', array('ar_rev_id=ct_rev_id')))); $this->addWhereFld('ct_tag', $params['tag']); } if ($fld_content) { // Modern MediaWiki has the content for deleted revs in the 'text' // table using fields old_text and old_flags. But revisions deleted // pre-1.5 store the content in the 'archive' table directly using // fields ar_text and ar_flags, and no corresponding 'text' row. So // we have to LEFT JOIN and fetch all four fields, plus ar_text_id // to be able to tell the difference. $this->addTables('text'); $this->addJoinConds(array('text' => array('LEFT JOIN', array('ar_text_id=old_id')))); $this->addFields(array('ar_text', 'ar_flags', 'ar_text_id', 'old_text', 'old_flags')); // This also means stricter restrictions if (!$user->isAllowedAny('undelete', 'deletedtext')) { $this->dieUsage('You don\'t have permission to view deleted revision content', 'permissiondenied'); } } // Check limits $userMax = $fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1; $botMax = $fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2; $limit = $params['limit']; if ($limit == 'max') { $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; $this->getResult()->addParsedLimit($this->getModuleName(), $limit); } $this->validateLimit('limit', $limit, 1, $userMax, $botMax); if ($fld_token) { // Undelete tokens are identical for all pages, so we cache one here $token = $user->getEditToken('', $this->getMain()->getRequest()); } $dir = $params['dir']; // We need a custom WHERE clause that matches all titles. if ($mode == 'revs') { $lb = new LinkBatch($titles); $where = $lb->constructSet('ar', $db); $this->addWhere($where); } elseif ($mode == 'all') { $this->addWhereFld('ar_namespace', $params['namespace']); $from = $params['from'] === null ? null : $this->titlePartToKey($params['from'], $params['namespace']); $to = $params['to'] === null ? null : $this->titlePartToKey($params['to'], $params['namespace']); $this->addWhereRange('ar_title', $dir, $from, $to); if (isset($params['prefix'])) { $this->addWhere('ar_title' . $db->buildLike($this->titlePartToKey($params['prefix'], $params['namespace']), $db->anyString())); } } if (!is_null($params['user'])) { $this->addWhereFld('ar_user_text', $params['user']); } elseif (!is_null($params['excludeuser'])) { $this->addWhere('ar_user_text != ' . $db->addQuotes($params['excludeuser'])); } if (!is_null($params['user']) || !is_null($params['excludeuser'])) { // Paranoia: avoid brute force searches (bug 17342) // (shouldn't be able to get here without 'deletedhistory', but // check it again just in case) if (!$user->isAllowed('deletedhistory')) { $bitmask = Revision::DELETED_USER; } elseif (!$user->isAllowedAny('suppressrevision', 'viewsuppressed')) { $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED; } else { $bitmask = 0; } if ($bitmask) { $this->addWhere($db->bitAnd('ar_deleted', $bitmask) . " != {$bitmask}"); } } if (!is_null($params['continue'])) { $cont = explode('|', $params['continue']); $op = $dir == 'newer' ? '>' : '<'; if ($mode == 'all' || $mode == 'revs') { $this->dieContinueUsageIf(count($cont) != 4); $ns = intval($cont[0]); $this->dieContinueUsageIf(strval($ns) !== $cont[0]); $title = $db->addQuotes($cont[1]); $ts = $db->addQuotes($db->timestamp($cont[2])); $ar_id = (int) $cont[3]; $this->dieContinueUsageIf(strval($ar_id) !== $cont[3]); $this->addWhere("ar_namespace {$op} {$ns} OR " . "(ar_namespace = {$ns} AND " . "(ar_title {$op} {$title} OR " . "(ar_title = {$title} AND " . "(ar_timestamp {$op} {$ts} OR " . "(ar_timestamp = {$ts} AND " . "ar_id {$op}= {$ar_id})))))"); } else { $this->dieContinueUsageIf(count($cont) != 2); $ts = $db->addQuotes($db->timestamp($cont[0])); $ar_id = (int) $cont[1]; $this->dieContinueUsageIf(strval($ar_id) !== $cont[1]); $this->addWhere("ar_timestamp {$op} {$ts} OR " . "(ar_timestamp = {$ts} AND " . "ar_id {$op}= {$ar_id})"); } } $this->addOption('LIMIT', $limit + 1); $this->addOption('USE INDEX', array('archive' => $mode == 'user' ? 'usertext_timestamp' : 'name_title_timestamp')); if ($mode == 'all') { if ($params['unique']) { // @todo Does this work on non-MySQL? $this->addOption('GROUP BY', 'ar_title'); } else { $sort = $dir == 'newer' ? '' : ' DESC'; $this->addOption('ORDER BY', array('ar_title' . $sort, 'ar_timestamp' . $sort, 'ar_id' . $sort)); } } else { if ($mode == 'revs') { // Sort by ns and title in the same order as timestamp for efficiency $this->addWhereRange('ar_namespace', $dir, null, null); $this->addWhereRange('ar_title', $dir, null, null); } $this->addTimestampWhereRange('ar_timestamp', $dir, $params['start'], $params['end']); // Include in ORDER BY for uniqueness $this->addWhereRange('ar_id', $dir, null, null); } $res = $this->select(__METHOD__); $pageMap = array(); // Maps ns&title to (fake) pageid $count = 0; $newPageID = 0; foreach ($res as $row) { if (++$count > $limit) { // We've had enough if ($mode == 'all' || $mode == 'revs') { $this->setContinueEnumParameter('continue', "{$row->ar_namespace}|{$row->ar_title}|{$row->ar_timestamp}|{$row->ar_id}"); } else { $this->setContinueEnumParameter('continue', "{$row->ar_timestamp}|{$row->ar_id}"); } break; } $rev = array(); $anyHidden = false; $rev['timestamp'] = wfTimestamp(TS_ISO_8601, $row->ar_timestamp); if ($fld_revid) { $rev['revid'] = intval($row->ar_rev_id); } if ($fld_parentid && !is_null($row->ar_parent_id)) { $rev['parentid'] = intval($row->ar_parent_id); } if ($fld_user || $fld_userid) { if ($row->ar_deleted & Revision::DELETED_USER) { $rev['userhidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->ar_deleted, Revision::DELETED_USER, $user)) { if ($fld_user) { $rev['user'] = $row->ar_user_text; } if ($fld_userid) { $rev['userid'] = $row->ar_user; } } } if ($fld_comment || $fld_parsedcomment) { if ($row->ar_deleted & Revision::DELETED_COMMENT) { $rev['commenthidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->ar_deleted, Revision::DELETED_COMMENT, $user)) { if ($fld_comment) { $rev['comment'] = $row->ar_comment; } if ($fld_parsedcomment) { $title = Title::makeTitle($row->ar_namespace, $row->ar_title); $rev['parsedcomment'] = Linker::formatComment($row->ar_comment, $title); } } } if ($fld_minor) { $rev['minor'] = $row->ar_minor_edit == 1; } if ($fld_len) { $rev['len'] = $row->ar_len; } if ($fld_sha1) { if ($row->ar_deleted & Revision::DELETED_TEXT) { $rev['sha1hidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->ar_deleted, Revision::DELETED_TEXT, $user)) { if ($row->ar_sha1 != '') { $rev['sha1'] = wfBaseConvert($row->ar_sha1, 36, 16, 40); } else { $rev['sha1'] = ''; } } } if ($fld_content) { if ($row->ar_deleted & Revision::DELETED_TEXT) { $rev['texthidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->ar_deleted, Revision::DELETED_TEXT, $user)) { if (isset($row->ar_text) && !$row->ar_text_id) { // Pre-1.5 ar_text row (if condition from Revision::newFromArchiveRow) ApiResult::setContentValue($rev, 'text', Revision::getRevisionText($row, 'ar_')); } else { ApiResult::setContentValue($rev, 'text', Revision::getRevisionText($row)); } } } if ($fld_tags) { if ($row->ts_tags) { $tags = explode(',', $row->ts_tags); ApiResult::setIndexedTagName($tags, 'tag'); $rev['tags'] = $tags; } else { $rev['tags'] = array(); } } if ($anyHidden && $row->ar_deleted & Revision::DELETED_RESTRICTED) { $rev['suppressed'] = true; } if (!isset($pageMap[$row->ar_namespace][$row->ar_title])) { $pageID = $newPageID++; $pageMap[$row->ar_namespace][$row->ar_title] = $pageID; $a['revisions'] = array($rev); ApiResult::setIndexedTagName($a['revisions'], 'rev'); $title = Title::makeTitle($row->ar_namespace, $row->ar_title); ApiQueryBase::addTitleInfo($a, $title); if ($fld_token) { $a['token'] = $token; } $fit = $result->addValue(array('query', $this->getModuleName()), $pageID, $a); } else { $pageID = $pageMap[$row->ar_namespace][$row->ar_title]; $fit = $result->addValue(array('query', $this->getModuleName(), $pageID, 'revisions'), null, $rev); } if (!$fit) { if ($mode == 'all' || $mode == 'revs') { $this->setContinueEnumParameter('continue', "{$row->ar_namespace}|{$row->ar_title}|{$row->ar_timestamp}|{$row->ar_id}"); } else { $this->setContinueEnumParameter('continue', "{$row->ar_timestamp}|{$row->ar_id}"); } break; } } $result->addIndexedTagName(array('query', $this->getModuleName()), 'page'); }
/** * Determine if the current user is allowed to view a particular * field of this revision, if it's marked as deleted. * @param $rc InterwikiIntegrationRCCacheEntry * @param $field Integer * @return Boolean */ public static function userCan($rc, $field) { if ($rc->mAttribs['integration_rc_type'] == RC_LOG) { return LogEventsList::userCanBitfield($rc->mAttribs['integration_rc_deleted'], $field); } else { return Revision::userCanBitfield($rc->mAttribs['integration_rc_deleted'], $field); } }