/** * Render the contributions of user to page * @param ResultWrapper $res */ protected function showContributions(ResultWrapper $res) { $numRows = $res->numRows(); $rev = null; $out = $this->getOutput(); $revs = array(); $prevRevs = array(); foreach ($res as $row) { $rev = new Revision($row); $revs[] = $rev; if ($res->key() <= self::LIMIT + 1 && $rev->getParentId()) { $prevRevs[] = $rev->getParentId(); } } $this->prevLengths = Revision::getParentLengths(wfGetDB(DB_SLAVE), $prevRevs); if ($numRows > 0) { $count = 0; foreach ($revs as $rev) { if ($count++ < self::LIMIT) { $this->showContributionsRow($rev); } } $out->addHtml('</ul>'); // Captured 1 more than we should have done so if the number of // results is greater than the limit there are more to show. if ($numRows > self::LIMIT) { $out->addHtml($this->getMoreButton($rev->getTimestamp())); } } else { // For users who exist but have not made any edits $out->addHtml(MobileUI::warningBox($this->msg('mobile-frontend-history-no-results'))); } }
/** * @param ResultWrapper $queryResult * @return GWTUser[] */ private function materializeList($queryResult) { $list = array(); while ($obj = $queryResult->fetchObject()) { $list[] = $this->materialize($obj); } return $list; }
/** * Pre-fill the link cache * * @param DatabaseBase $db * @param ResultWrapper $res */ function preprocessResults($db, $res) { if ($res->numRows() > 0) { $linkBatch = new LinkBatch(); foreach ($res as $row) { $linkBatch->add($row->namespace, $row->title); } $res->seek(0); $linkBatch->execute(); } }
/** * Run a LinkBatch to pre-cache LinkCache information, * like page existence and information for stub color and redirect hints. * This should be done for live data and cached data. * * @param IDatabase $db * @param ResultWrapper $res */ public function preprocessResults($db, $res) { if (!$res->numRows()) { return; } $batch = new LinkBatch(); foreach ($res as $row) { $batch->add($row->namespace, $row->title); } $batch->execute(); $res->seek(0); }
/** * Fetch user page links and cache their existence * * @param IDatabase $db * @param ResultWrapper $res */ function preprocessResults($db, $res) { if (!$res->numRows()) { return; } $batch = new LinkBatch(); foreach ($res as $row) { $batch->add(NS_CATEGORY, $row->title); } $batch->execute(); // Back to start for display $res->seek(0); }
/** * @param IDatabase $db * @param ResultWrapper $res */ function preprocessResults($db, $res) { # There's no point doing a batch check if we aren't caching results; # the page must exist for it to have been pulled out of the table if (!$this->isCached() || !$res->numRows()) { return; } $batch = new LinkBatch(); foreach ($res as $row) { $batch->add($row->namespace, $row->title); } $batch->execute(); $res->seek(0); }
/** * Cache page existence for performance * * @param DatabaseBase $db * @param ResultWrapper $res */ function preprocessResults($db, $res) { $batch = new LinkBatch(); foreach ($res as $row) { $batch->add($row->namespace, $row->title); $batch->addObj($this->getRedirectTarget($row)); } $batch->execute(); // Back to start for display if ($res->numRows() > 0) { // If there are no rows we get an error seeking. $db->dataSeek($res, 0); } }
/** * Cache page existence for performance * * @param IDatabase $db * @param ResultWrapper $res */ function preprocessResults($db, $res) { if (!$res->numRows()) { return; } $batch = new LinkBatch(); foreach ($res as $row) { $batch->add($row->namespace, $row->title); $batch->addObj($this->getRedirectTarget($row)); } $batch->execute(); // Back to start for display $res->seek(0); }
/** * Get the number of items in the list. * @return int */ public function length() { if (!$this->res) { return 0; } else { return $this->res->numRows(); } }
protected function loadFromRows(ResultWrapper $res) { $ret = array(); while ($row = $res->fetchObject()) { unset($grp); unset($ver); // experiment $e_id = $row->e_id; if (!isset($ret[$e_id])) { $ret[$e_id] = array('id' => $row->e_id, 'name' => $row->e_name, 'description' => $row->e_description, 'versions' => array(), 'groups' => array()); } $exp =& $ret[$e_id]; // group $g_id = $row->g_id; if ($g_id && !isset($exp['groups'][$g_id])) { $exp['groups'][$g_id] = array('experiment_id' => $e_id, 'id' => $row->g_id, 'name' => $row->g_name, 'description' => $row->g_description); } if ($g_id) { $grp =& $exp['groups'][$g_id]; } // version $v_id = $row->v_id; if ($v_id && !isset($exp['versions'][$v_id])) { $exp['versions'][$v_id] = array('experiment_id' => $e_id, 'id' => $row->v_id, 'start_time' => $row->v_start_time, 'end_time' => $row->v_end_time, 'ga_slot' => $row->v_ga_slot, 'control_group_id' => $row->v_control_group_id, 'group_ranges' => array()); } if ($v_id) { $ver =& $exp['versions'][$v_id]; } // group ranges if ($g_id && $v_id && $row->r_group_id) { // row in group_ranges found if (!isset($ver['group_ranges'][$g_id])) { $ver['group_ranges'][$g_id] = array('version_id' => $v_id, 'group_id' => $g_id, 'ranges' => $row->r_ranges); } } } return $ret; }
/** * Render the history list * @see showRow() * @see doQuery() * @param ResultWrapper $res The result of doQuery */ protected function showHistory(ResultWrapper $res) { $numRows = $res->numRows(); $rev1 = $rev2 = null; $out = $this->getOutput(); if ($numRows > 0) { foreach ($res as $row) { $rev1 = new Revision($row); if ($rev2) { $this->showRow($rev2, $rev1); } $rev2 = $rev1; } if ($rev1 && $numRows < self::LIMIT + 1) { $this->showRow($rev1, null); } $out->addHtml('</ul>'); // Captured 1 more than we should have done so if the number of // results is greater than the limit there are more to show. if ($numRows > self::LIMIT) { $out->addHtml($this->getMoreButton($rev1->getTimestamp())); } } else { // Edge case. // I suspect this is here because revisions may exist but may have been hidden. $out->addHtml(MobileUI::warningBox($this->msg('mobile-frontend-history-no-results'))); } }
/** * Partition a DB result with backlinks in it into batches * @param ResultWrapper $res Database result * @param int $batchSize * @param bool $isComplete Whether $res includes all the backlinks * @throws MWException * @return array */ protected function partitionResult($res, $batchSize, $isComplete = true) { $batches = array(); $numRows = $res->numRows(); $numBatches = ceil($numRows / $batchSize); for ($i = 0; $i < $numBatches; $i++) { if ($i == 0 && $isComplete) { $start = false; } else { $rowNum = $i * $batchSize; $res->seek($rowNum); $row = $res->fetchObject(); $start = (int) $row->page_id; } if ($i == $numBatches - 1 && $isComplete) { $end = false; } else { $rowNum = min($numRows - 1, ($i + 1) * $batchSize - 1); $res->seek($rowNum); $row = $res->fetchObject(); $end = (int) $row->page_id; } # Sanity check order if ($start && $end && $start > $end) { throw new MWException(__METHOD__ . ': Internal error: query result out of order'); } $batches[] = array($start, $end); } return array('numRows' => $numRows, 'batches' => $batches); }
/** * @param resource|ResultWrapper $res * @param int $row * @return mixed */ protected function mysqlDataSeek($res, $row) { return $res->data_seek($row); }
/** * Combine results from 2 tables. * * Note: This will throw away some results * * @param ResultWrapper $res1 * @param ResultWrapper $res2 * @param int $limit * @param bool $ascending See note about $asc in $this->reallyDoQuery * @return FakeResultWrapper $res1 and $res2 combined */ protected function combineResult($res1, $res2, $limit, $ascending) { $res1->rewind(); $res2->rewind(); $topRes1 = $res1->next(); $topRes2 = $res2->next(); $resultArray = array(); for ($i = 0; $i < $limit && $topRes1 && $topRes2; $i++) { if (strcmp($topRes1->{$this->mIndexField}, $topRes2->{$this->mIndexField}) > 0) { if (!$ascending) { $resultArray[] = $topRes1; $topRes1 = $res1->next(); } else { $resultArray[] = $topRes2; $topRes2 = $res2->next(); } } else { if (!$ascending) { $resultArray[] = $topRes2; $topRes2 = $res2->next(); } else { $resultArray[] = $topRes1; $topRes1 = $res1->next(); } } } // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect for (; $i < $limit && $topRes1; $i++) { // @codingStandardsIgnoreEnd $resultArray[] = $topRes1; $topRes1 = $res1->next(); } // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect for (; $i < $limit && $topRes2; $i++) { // @codingStandardsIgnoreEnd $resultArray[] = $topRes2; $topRes2 = $res2->next(); } return new FakeResultWrapper($resultArray); }
/** * Invalidate a set of IDs, right now */ public function invalidateIDs(ResultWrapper $res) { global $wgUseFileCache, $wgUseSquid; if ($res->numRows() == 0) { return; } // sanity check $dbw = wfGetDB(DB_MASTER); $timestamp = $dbw->timestamp(); $done = false; while (!$done) { # Get all IDs in this query into an array $ids = array(); for ($i = 0; $i < $this->mRowsPerQuery; $i++) { $row = $res->fetchRow(); if ($row) { $ids[] = $row[0]; } else { $done = true; break; } } if (count($ids) == 0) { break; } # Update page_touched $dbw->update('page', array('page_touched' => $timestamp), array('page_id' => $ids), __METHOD__); # Update static caches if ($wgUseSquid || $wgUseFileCache) { $titles = Title::newFromIDs($ids); # Update squid cache if ($wgUseSquid) { $u = SquidUpdate::newFromTitles($titles); $u->doUpdate(); } # Update file cache if ($wgUseFileCache) { foreach ($titles as $title) { HTMLFileCache::clearFileCache($title); } } } } }
/** * Generic list of deleted pages * * @param ResultWrapper $result * @return bool */ private function showList($result) { $out = $this->getOutput(); if ($result->numRows() == 0) { $out->addWikiMsg('undelete-no-results'); return false; } $out->addWikiMsg('undeletepagetext', $this->getLanguage()->formatNum($result->numRows())); $undelete = $this->getPageTitle(); $out->addHTML("<ul>\n"); foreach ($result as $row) { $title = Title::makeTitleSafe($row->ar_namespace, $row->ar_title); if ($title !== null) { $item = Linker::linkKnown($undelete, htmlspecialchars($title->getPrefixedText()), array(), array('target' => $title->getPrefixedText())); } else { // The title is no longer valid, show as text $item = Html::element('span', array('class' => 'mw-invalidtitle'), Linker::getInvalidTitleDescription($this->getContext(), $row->ar_namespace, $row->ar_title)); } $revs = $this->msg('undeleterevisions')->numParams($row->count)->parse(); $out->addHTML("<li>{$item} ({$revs})</li>\n"); } $result->free(); $out->addHTML("</ul>\n"); return true; }
/** * Get the number of rows in the result set * * @return Integer */ function getNumRows() { if ( !$this->mQueryDone ) { $this->doQuery(); } return $this->mResult->numRows(); }
function rewind() { $this->res->rewind(); $this->key = 0; $this->setCurrent($this->res->current()); }
/** * Runs through a query result set dumping page and revision records. * The result set should be sorted/grouped by page to avoid duplicate * page records in the output. * * The result set will be freed once complete. Should be safe for * streaming (non-buffered) queries, as long as it was made on a * separate database connection not managed by LoadBalancer; some * blob storage types will make queries to pull source data. * * @param ResultWrapper $resultset * @access private */ function outputStream($resultset) { $last = null; while ($row = $resultset->fetchObject()) { if (is_null($last) || $last->page_namespace != $row->page_namespace || $last->page_title != $row->page_title) { if (isset($last)) { $this->closePage($last); } $this->openPage($row); $last = $row; } $this->dumpRev($row); } if (isset($last)) { $this->closePage($last); } $resultset->free(); }
/** * Log a SQL statement. This function does not log every statement but samples * at the rate defined in self::QUERY_SAMPLE_RATE. * * @param string $sql the query * @param ResultWrapper|resource|bool $ret database results * @param string $fname the name of the function that made this query * @param bool $isMaster is this against the master * @return void */ protected function logSql($sql, $ret, $fname, $elapsedTime, $isMaster) { global $wgDBcluster; if ($ret instanceof ResultWrapper) { // NOP for MySQL driver $num_rows = $ret->numRows(); } elseif ($ret instanceof mysqli_result) { // for SELECT queries report how many rows are sent to the client // for INSERT, UPDATE, DELETE, DROP queries report affected rows $num_rows = $ret->num_rows ?: $this->affectedRows(); } elseif (is_resource($ret)) { // for SELECT queries report how many rows are sent to the client $num_rows = mysql_num_rows($ret); } elseif ($ret === true) { // for INSERT, UPDATE, DELETE, DROP queries report affected rows $num_rows = $this->affectedRows(); } else { // failed queries $num_rows = false; } if ($this->getSampler()->shouldSample()) { $this->getWikiaLogger()->info("SQL {$sql}", ['method' => $fname, 'elapsed' => $elapsedTime, 'num_rows' => $num_rows, 'cluster' => $wgDBcluster, 'server' => $this->mServer, 'server_role' => $isMaster ? 'master' : 'slave', 'db_name' => $this->mDBname, 'exception' => new Exception()]); } }
/** * Build and output the actual changes list. * * @param ResultWrapper $rows Database rows * @param FormOptions $opts */ public function outputChangesList($rows, $opts) { $dbr = $this->getDB(); $user = $this->getUser(); $output = $this->getOutput(); # Show a message about replica DB lag, if applicable $lag = wfGetLB()->safeGetLag($dbr); if ($lag > 0) { $output->showLagWarning($lag); } # If no rows to display, show message before try to render the list if ($rows->numRows() == 0) { $output->wrapWikiMsg("<div class='mw-changeslist-empty'>\n\$1\n</div>", 'recentchanges-noresult'); return; } $dbr->dataSeek($rows, 0); $list = ChangesList::newFromContext($this->getContext()); $list->setWatchlistDivs(); $list->initChangesListRows($rows); $dbr->dataSeek($rows, 0); if ($this->getConfig()->get('RCShowWatchingUsers') && $user->getOption('shownumberswatching')) { $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore(); } $s = $list->beginRecentChangesList(); $userShowHiddenCats = $this->getUser()->getBoolOption('showhiddencats'); $counter = 1; foreach ($rows as $obj) { # Make RC entry $rc = RecentChange::newFromRow($obj); # Skip CatWatch entries for hidden cats based on user preference if ($rc->getAttribute('rc_type') == RC_CATEGORIZE && !$userShowHiddenCats && $rc->getParam('hidden-cat')) { continue; } $rc->counter = $counter++; if ($this->getConfig()->get('ShowUpdatedMarker')) { $updated = $obj->wl_notificationtimestamp; } else { $updated = false; } if (isset($watchedItemStore)) { $rcTitleValue = new TitleValue((int) $obj->rc_namespace, $obj->rc_title); $rc->numberofWatchingusers = $watchedItemStore->countWatchers($rcTitleValue); } else { $rc->numberofWatchingusers = 0; } $changeLine = $list->recentChangesLine($rc, $updated, $counter); if ($changeLine !== false) { $s .= $changeLine; } } $s .= $list->endRecentChangesList(); $output->addHTML($s); }
/** * Initialize total values so we can figure out percentages later. * * @param IDatabase $dbr * @param ResultWrapper $res */ public function preprocessResults($dbr, $res) { $this->totalCount = $this->totalBytes = 0; foreach ($res as $row) { $mediaStats = $this->splitFakeTitle($row->title); $this->totalCount += isset($mediaStats[2]) ? $mediaStats[2] : 0; $this->totalBytes += isset($mediaStats[3]) ? $mediaStats[3] : 0; } $res->seek(0); }
/** * Extract some useful data from the result object for use by * the navigation bar, put it into $this */ function extractResultInfo($offset, $limit, ResultWrapper $res) { $numRows = $res->numRows(); if ($numRows) { $row = $res->fetchRow(); $firstIndex = $row[$this->mIndexField]; # Discard the extra result row if there is one if ($numRows > $this->mLimit && $numRows > 1) { $res->seek($numRows - 1); $this->mPastTheEndRow = $res->fetchObject(); $indexField = $this->mIndexField; $this->mPastTheEndIndex = $this->mPastTheEndRow->{$indexField}; $res->seek($numRows - 2); $row = $res->fetchRow(); $lastIndex = $row[$this->mIndexField]; } else { $this->mPastTheEndRow = null; # Setting indexes to an empty string means that they will be # omitted if they would otherwise appear in URLs. It just so # happens that this is the right thing to do in the standard # UI, in all the relevant cases. $this->mPastTheEndIndex = ''; $res->seek($numRows - 1); $row = $res->fetchRow(); $lastIndex = $row[$this->mIndexField]; } } else { $firstIndex = ''; $lastIndex = ''; $this->mPastTheEndRow = null; $this->mPastTheEndIndex = ''; } if ($this->mIsBackwards) { $this->mIsFirst = $numRows < $limit; $this->mIsLast = $offset == ''; $this->mLastShown = $firstIndex; $this->mFirstShown = $lastIndex; } else { $this->mIsFirst = $offset == ''; $this->mIsLast = $numRows < $limit; $this->mLastShown = $lastIndex; $this->mFirstShown = $firstIndex; } }
/** * Fill in member variables from a result wrapper */ function loadFromResult(ResultWrapper $res, $killExpired = true) { $ret = false; if (0 != $res->numRows()) { # Get first block $row = $res->fetchObject(); $this->initFromRow($row); if ($killExpired) { # If requested, delete expired rows do { $killed = $this->deleteIfExpired(); if ($killed) { $row = $res->fetchObject(); if ($row) { $this->initFromRow($row); } } } while ($killed && $row); # If there were any left after the killing finished, return true if ($row) { $ret = true; } } else { $ret = true; } } $res->free(); return $ret; }
/** * Creates a new LinkBatch object, adds all pages from the passed ResultWrapper (MUST include * title and optional the namespace field) and executes the batch. This operation will pre-cache * LinkCache information like page existence and information for stub color and redirect hints. * * @param ResultWrapper $res The ResultWrapper object to process. Needs to include the title * field and namespace field, if the $ns parameter isn't set. * @param null $ns Use this namespace for the given titles in the ResultWrapper object, * instead of the namespace value of $res. */ protected function executeLBFromResultWrapper(ResultWrapper $res, $ns = null) { if (!$res->numRows()) { return; } $batch = new LinkBatch(); foreach ($res as $row) { $batch->add($ns !== null ? $ns : $row->namespace, $row->title); } $batch->execute(); $res->seek(0); }
/** * Runs through a query result set dumping page and revision records. * The result set should be sorted/grouped by page to avoid duplicate * page records in the output. * * The result set will be freed once complete. Should be safe for * streaming (non-buffered) queries, as long as it was made on a * separate database connection not managed by LoadBalancer; some * blob storage types will make queries to pull source data. * * @param ResultWrapper $resultset * @access private */ function outputStream($resultset) { $last = null; while ($row = $resultset->fetchObject()) { if (is_null($last) || $last->page_namespace != $row->page_namespace || $last->page_title != $row->page_title) { if (isset($last)) { $output = $this->writer->closePage(); $this->sink->writeClosePage($output); } $output = $this->writer->openPage($row); $this->sink->writeOpenPage($row, $output); $last = $row; } $output = $this->writer->writeRevision($row, $this->parse); $this->sink->writeRevision($row, $output); } if (isset($last)) { $output = $this->author_list . $this->writer->closePage(); $this->sink->writeClosePage($output); } $resultset->free(); }
/** * Send output to the OutputPage object, only called if not used feeds * * @param ResultWrapper $rows Database rows * @param FormOptions $opts */ public function webOutput($rows, $opts) { if (!$this->including()) { $this->outputFeedLinks(); $this->doHeader($opts, $rows->numRows()); } $this->outputChangesList($rows, $opts); }
/** * Format and output report results using the given information plus * OutputPage * * @param OutputPage $out OutputPage to print to * @param Skin $skin User skin to use * @param IDatabase $dbr Database (read) connection to use * @param ResultWrapper $res Result pointer * @param int $num Number of available result rows * @param int $offset Paging offset */ protected function outputResults($out, $skin, $dbr, $res, $num, $offset) { global $wgContLang; if ($num > 0) { $html = array(); if (!$this->listoutput) { $html[] = $this->openList($offset); } # $res might contain the whole 1,000 rows, so we read up to # $num [should update this to use a Pager] // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed for ($i = 0; $i < $num && ($row = $res->fetchObject()); $i++) { // @codingStandardsIgnoreEnd $line = $this->formatResult($skin, $row); if ($line) { $attr = isset($row->usepatrol) && $row->usepatrol && $row->patrolled == 0 ? ' class="not-patrolled"' : ''; $html[] = $this->listoutput ? $line : "<li{$attr}>{$line}</li>\n"; } } # Flush the final result if ($this->tryLastResult()) { $row = null; $line = $this->formatResult($skin, $row); if ($line) { $attr = isset($row->usepatrol) && $row->usepatrol && $row->patrolled == 0 ? ' class="not-patrolled"' : ''; $html[] = $this->listoutput ? $line : "<li{$attr}>{$line}</li>\n"; } } if (!$this->listoutput) { $html[] = $this->closeList(); } $html = $this->listoutput ? $wgContLang->listToText($html) : implode('', $html); $out->addHTML($html); } }
/** * Build and output the actual changes list. * * @param ResultWrapper $rows Database rows * @param FormOptions $opts */ public function outputChangesList($rows, $opts) { $dbr = $this->getDB(); $user = $this->getUser(); $output = $this->getOutput(); # Show a message about slave lag, if applicable $lag = wfGetLB()->safeGetLag($dbr); if ($lag > 0) { $output->showLagWarning($lag); } # If no rows to display, show message before try to render the list if ($rows->numRows() == 0) { $output->wrapWikiMsg("<div class='mw-changeslist-empty'>\n\$1\n</div>", 'recentchanges-noresult'); return; } $dbr->dataSeek($rows, 0); $list = ChangesList::newFromContext($this->getContext()); $list->setWatchlistDivs(); $list->initChangesListRows($rows); $dbr->dataSeek($rows, 0); $s = $list->beginRecentChangesList(); $counter = 1; foreach ($rows as $obj) { # Make RC entry $rc = RecentChange::newFromRow($obj); $rc->counter = $counter++; if ($this->getConfig()->get('ShowUpdatedMarker')) { $updated = $obj->wl_notificationtimestamp; } else { $updated = false; } if ($this->getConfig()->get('RCShowWatchingUsers') && $user->getOption('shownumberswatching')) { $rc->numberofWatchingusers = $dbr->selectField('watchlist', 'COUNT(*)', array('wl_namespace' => $obj->rc_namespace, 'wl_title' => $obj->rc_title), __METHOD__); } else { $rc->numberofWatchingusers = 0; } $changeLine = $list->recentChangesLine($rc, $updated, $counter); if ($changeLine !== false) { $s .= $changeLine; } } $s .= $list->endRecentChangesList(); $output->addHTML($s); }
/** * Invalidate a set of IDs, right now */ function invalidateIDs(ResultWrapper $res) { global $wgUseFileCache, $wgUseSquid; if ($res->numRows() == 0) { return; } $dbw =& wfGetDB(DB_MASTER); $timestamp = $dbw->timestamp(); $done = false; while (!$done) { # Get all IDs in this query into an array $ids = array(); for ($i = 0; $i < $this->mRowsPerQuery; $i++) { $row = $res->fetchRow(); if ($row) { $ids[] = $row[0]; } else { $done = true; break; } } if (!count($ids)) { break; } # Update page_touched $dbw->update('page', array('page_touched' => $timestamp), array('page_id IN (' . $dbw->makeList($ids) . ')'), __METHOD__); # Update squid if ($wgUseSquid || $wgUseFileCache) { $titles = Title::newFromIDs($ids); if ($wgUseSquid) { $u = SquidUpdate::newFromTitles($titles); $u->doUpdate(); } # Update file cache if ($wgUseFileCache) { foreach ($titles as $title) { $cm = new CacheManager($title); @unlink($cm->fileCacheName()); } } } } }