/** * Executed when the user opens the DSMW administration special page * Calculates the PushFeed list and the pullfeed list (and everything that * is displayed on the psecial page * * @global <Object> $wgOut Output page instance * @global <String> $wgServerName * @global <String> $wgScriptPath * @return <bool> */ public function execute() { global $wgOut, $wgRequest, $wgServerName, $wgScriptPath, $wgDSMWIP, $wgServerName, $wgScriptPath, $wgUser; if (!$this->userCanExecute($wgUser)) { // If the user is not authorized, show an error. $this->displayRestrictionError(); return; } /**** Get status of refresh job, if any ****/ $dbr =& wfGetDB(DB_SLAVE); $row = $dbr->selectRow('job', '*', array('job_cmd' => 'DSMWUpdateJob'), __METHOD__); if ($row !== false) { // similar to Job::pop_type, but without deleting the job $title = Title::makeTitleSafe($row->job_namespace, $row->job_title); $updatejob = Job::factory($row->job_cmd, $title, Job::extractBlob($row->job_params), $row->job_id); } else { $updatejob = NULL; } $row1 = $dbr->selectRow('job', '*', array('job_cmd' => 'DSMWPropertyTypeJob'), __METHOD__); if ($row1 !== false) { // similar to Job::pop_type, but without deleting the job $title = Title::makeTitleSafe($row1->job_namespace, $row1->job_title); $propertiesjob = Job::factory($row1->job_cmd, $title, Job::extractBlob($row1->job_params), $row1->job_id); } else { $propertiesjob = NULL; } /**** Execute actions if any ****/ $action = $wgRequest->getText('action'); if ($action == 'logootize') { if ($updatejob === NULL) { // careful, there might be race conditions here $title = Title::makeTitle(NS_SPECIAL, 'DSMWAdmin'); $newjob = new DSMWUpdateJob($title); $newjob->insert(); $wgOut->addHTML('<p><font color="red"><b>' . wfMsg('dsmw-special-admin-articleupstarted') . '</b></font></p>'); } else { $wgOut->addHTML('<p><font color="red"><b>' . wfMsg('dsmw-special-admin-articleuprunning') . '</b></font></p>'); } } elseif ($action == 'addProperties') { if ($propertiesjob === NULL) { $title1 = Title::makeTitle(NS_SPECIAL, 'DSMWAdmin'); $newjob1 = new DSMWPropertyTypeJob($title1); $newjob1->insert(); $wgOut->addHTML('<p><font color="red"><b>' . wfMsg('dsmw-special-admin-typeupstarted') . '</b></font></p>'); } else { $wgOut->addHTML('<p><font color="red"><b>' . wfMsg('dsmw-special-admin-typeuprunning') . '</b></font></p>'); } } $wgOut->setPagetitle('DSMW Settings'); $wgOut->addHTML(Html::element('p', array(), wfMsg('dsmw-special-admin-intro'))); $wgOut->addHTML(Html::rawElement('form', array('name' => 'properties', 'action' => '', 'method' => 'POST'), Html::hidden('action', 'addProperties') . '<br />' . Html::element('h2', array(), wfMsg('dsmw-special-admin-propheader')) . Html::element('p', array(), wfMsg('dsmw-special-admin-proptext')) . Html::input('updateProperties', wfMsg('dsmw-special-admin-propheader'), 'submit'))); $wgOut->addHTML(Html::rawElement('form', array('name' => 'logoot', 'action' => '', 'method' => 'POST'), Html::hidden('action', 'logootize') . '<br />' . Html::element('h2', array(), wfMsg('dsmw-special-admin-upheader')) . Html::element('p', array(), wfMsg('dsmw-special-admin-uptext')) . Html::input('updateArticles', wfMsg('dsmw-special-admin-upbutton'), 'submit'))); return false; }
/** * Pop a job off the front of the queue * @static * @return Job or false if there's no jobs */ static function pop() { wfProfileIn(__METHOD__); $dbr =& wfGetDB(DB_SLAVE); // Get a job from the slave $row = $dbr->selectRow('job', '*', '', __METHOD__, array('ORDER BY' => 'job_id', 'LIMIT' => 1)); if ($row === false) { wfProfileOut(__METHOD__); return false; } // Try to delete it from the master $dbw =& wfGetDB(DB_MASTER); $dbw->delete('job', array('job_id' => $row->job_id), __METHOD__); $affected = $dbw->affectedRows(); $dbw->immediateCommit(); if (!$affected) { // Failed, someone else beat us to it // Try getting a random row $row = $dbw->selectRow('job', array('MIN(job_id) as minjob', 'MAX(job_id) as maxjob'), '', __METHOD__); if ($row === false || is_null($row->minjob) || is_null($row->maxjob)) { // No jobs to get wfProfileOut(__METHOD__); return false; } // Get the random row $row = $dbw->selectRow('job', '*', array('job_id' => mt_rand($row->minjob, $row->maxjob)), __METHOD__); if ($row === false) { // Random job gone before we got the chance to select it // Give up wfProfileOut(__METHOD__); return false; } // Delete the random row $dbw->delete('job', array('job_id' => $row->job_id), __METHOD__); $affected = $dbw->affectedRows(); $dbw->immediateCommit(); if (!$affected) { // Random job gone before we exclusively deleted it // Give up wfProfileOut(__METHOD__); return false; } } // If execution got to here, there's a row in $row that has been deleted from the database // by this thread. Hence the concurrent pop was successful. $namespace = $row->job_namespace; $dbkey = $row->job_title; $title = Title::makeTitleSafe($namespace, $dbkey); $job = Job::factory($row->job_cmd, $title, Job::extractBlob($row->job_params), $row->job_id); // Remove any duplicates it may have later in the queue $dbw->delete('job', $job->insertFields(), __METHOD__); wfProfileOut(__METHOD__); return $job; }
public function execute($par) { if (!$this->userCanExecute($this->getUser())) { // If the user is not authorized, show an error. $this->displayRestrictionError(); return; } $this->setHeaders(); $mwCollaboratorFactory = ApplicationFactory::getInstance()->newMwCollaboratorFactory(); $this->htmlFormRenderer = $mwCollaboratorFactory->newHtmlFormRenderer($this->getContext()->getTitle(), $this->getLanguage()); $this->messageBuilder = $this->htmlFormRenderer->getMessageBuilder(); $jobQueueLookup = $mwCollaboratorFactory->newJobQueueLookup($this->getStore()->getConnection('mw.db')); $row = $jobQueueLookup->selectJobRowFor('SMW\\RefreshJob'); if ($row !== false) { // similar to Job::pop_type, but without deleting the job $title = Title::makeTitleSafe($row->job_namespace, $row->job_title); $blob = (string) $row->job_params !== '' ? unserialize($row->job_params) : false; $refreshjob = Job::factory($row->job_cmd, $title, $blob, $row->job_id); } else { $refreshjob = null; } /**** Execute actions if any ****/ switch ($this->getRequest()->getText('action')) { case 'listsettings': return $this->doListConfigurationSettings(); case 'idlookup': return $this->doIdLookup($this->getRequest()->getVal('objectId')); case 'updatetables': return $this->doUpdateTables(); case 'refreshstore': return $this->doRefreshStore($refreshjob); } /**** Normal output ****/ $html = $this->htmlFormRenderer->setName('buildtables')->setMethod('post')->addParagraph($this->messageBuilder->getMessage('smw_smwadmin_docu')->text())->addHiddenField('action', 'updatetables')->addHeader('h2', $this->messageBuilder->getMessage('smw_smwadmin_db')->text())->addParagraph($this->messageBuilder->getMessage('smw_smwadmin_dbdocu')->text())->addParagraph($this->messageBuilder->getMessage('smw_smwadmin_permissionswarn')->text())->addHiddenField('udsure', 'yes')->addSubmitButton($this->messageBuilder->getMessage('smw_smwadmin_dbbutton')->text())->getForm(); $html .= Html::element('br', array(), ''); $this->htmlFormRenderer->setName('refreshwiki')->setMethod('post')->addHiddenField('action', 'refreshstore')->addHeader('h2', $this->messageBuilder->getMessage('smw_smwadmin_datarefresh')->text())->addParagraph($this->messageBuilder->getMessage('smw_smwadmin_datarefreshdocu')->text()); if (!is_null($refreshjob)) { $prog = $refreshjob->getProgress(); $progressBar = Html::rawElement('div', array('style' => 'float: left; background: #DDDDDD; border: 1px solid grey; width: 300px;'), Html::rawElement('div', array('style' => 'background: #AAF; width: ' . round($prog * 300) . 'px; height: 20px; '), '')); $this->htmlFormRenderer->addParagraph($this->messageBuilder->getMessage('smw_smwadmin_datarefreshprogress')->text())->addParagraph($progressBar . ' ' . round($prog * 100, 4) . '%')->addLineBreak(); if ($GLOBALS['smwgAdminRefreshStore']) { $this->htmlFormRenderer->addSubmitButton($this->messageBuilder->getMessage('smw_smwadmin_datarefreshstop')->text())->addCheckbox($this->messageBuilder->getMessage('smw_smwadmin_datarefreshstopconfirm')->escaped(), 'rfsure', 'stop'); } } elseif ($GLOBALS['smwgAdminRefreshStore']) { $this->htmlFormRenderer->addHiddenField('rfsure', 'yes')->addSubmitButton($this->messageBuilder->getMessage('smw_smwadmin_datarefreshbutton')->text()); } $html .= $this->htmlFormRenderer->getForm() . Html::element('br', array(), ''); $html .= $this->getSettingsSectionForm() . Html::element('br', array(), ''); $html .= $this->getIdLookupSectionForm() . Html::element('br', array(), ''); $html .= $this->getAnnounceSectionForm() . Html::element('br', array(), ''); $html .= $this->getSupportSectionForm(); $this->getOutput()->addHTML($html); }
/** * @see JobQueue::getAllQueuedJobs() * @return Iterator */ public function getAllQueuedJobs() { list($dbr, $scope) = $this->getSlaveDB(); return new MappedIterator($dbr->select('job', '*', array('job_cmd' => $this->getType(), 'job_token' => '')), function ($row) use($scope) { $job = Job::factory($row->job_cmd, Title::makeTitle($row->job_namespace, $row->job_title), strlen($row->job_params) ? unserialize($row->job_params) : false, $row->job_id); $job->metadata['id'] = $row->job_id; $job->id = $row->job_id; // XXX: work around broken subclasses return $job; }); }
/** * @param IJobSpecification $spec * * @return Job */ public function jobFromSpecInternal(IJobSpecification $spec) { return Job::factory($spec->getType(), $spec->getTitle(), $spec->getParams()); }
function queueRecursiveJobs() { wfProfileIn(__METHOD__); $batchSize = 100; $dbr =& wfGetDB(DB_SLAVE); $res = $dbr->select(array('templatelinks', 'page'), array('page_namespace', 'page_title'), array('page_id=tl_from', 'tl_namespace' => $this->mTitle->getNamespace(), 'tl_title' => $this->mTitle->getDBkey()), __METHOD__); $done = false; while (!$done) { $jobs = array(); for ($i = 0; $i < $batchSize; $i++) { $row = $dbr->fetchObject($res); if (!$row) { $done = true; break; } $title = Title::makeTitle($row->page_namespace, $row->page_title); $jobs[] = Job::factory('refreshLinks', $title); } Job::batchInsert($jobs); } $dbr->freeResult($res); wfProfileOut(__METHOD__); }
/** * @param array $fields * @return Job|bool */ protected function getJobFromFields(array $fields) { $title = Title::makeTitle($fields['namespace'], $fields['title']); $job = Job::factory($fields['type'], $title, $fields['params']); $job->metadata['uuid'] = $fields['uuid']; $job->metadata['timestamp'] = $fields['timestamp']; return $job; }
/** * Do the rename operation */ public function rename() { global $wgAuth, $wgUpdateRowsPerJob; // Grab the user's edit count first, used in log entry $contribs = User::newFromId($this->uid)->getEditCount(); $dbw = wfGetDB(DB_MASTER); $dbw->begin(); Hooks::run('RenameUserPreRename', array($this->uid, $this->old, $this->new)); // Rename and touch the user before re-attributing edits, // this avoids users still being logged in and making new edits while // being renamed, which leaves edits at the old name. $this->debug("Starting rename of {$this->old} to {$this->new}"); $dbw->update('user', array('user_name' => $this->new, 'user_touched' => $dbw->timestamp()), array('user_name' => $this->old, 'user_id' => $this->uid), __METHOD__); if (!$dbw->affectedRows() && $this->checkIfUserExists) { $dbw->rollback(); $this->debug("User {$this->old} does not exist, bailing out"); return false; } // Reset token to break login with central auth systems. // Again, avoids user being logged in with old name. $user = User::newFromId($this->uid); $authUser = $wgAuth->getUserInstance($user); $authUser->resetAuthToken(); // Delete from memcached. $user->invalidateCache(); // Update ipblock list if this user has a block in there. $dbw->update('ipblocks', array('ipb_address' => $this->new), array('ipb_user' => $this->uid, 'ipb_address' => $this->old), __METHOD__); // Update this users block/rights log. Ideally, the logs would be historical, // but it is really annoying when users have "clean" block logs by virtue of // being renamed, which makes admin tasks more of a pain... $oldTitle = Title::makeTitle(NS_USER, $this->old); $newTitle = Title::makeTitle(NS_USER, $this->new); $this->debug("Updating logging table for {$this->old} to {$this->new}"); if (is_callable('SpecialLog::getLogTypesOnUser')) { // 1.25+ $logTypesOnUser = SpecialLog::getLogTypesOnUser(); } else { // Fallback to hardcoded list $logTypesOnUser = array('block', 'rights'); } $dbw->update('logging', array('log_title' => $newTitle->getDBkey()), array('log_type' => $logTypesOnUser, 'log_namespace' => NS_USER, 'log_title' => $oldTitle->getDBkey()), __METHOD__); // Do immediate updates! foreach ($this->tables as $table => $fieldSet) { list($nameCol, $userCol) = $fieldSet; $dbw->update($table, array($nameCol => $this->new), array($nameCol => $this->old, $userCol => $this->uid), __METHOD__); } // Increase time limit (like CheckUser); this can take a while... if ($this->tablesJob) { wfSuppressWarnings(); set_time_limit(120); wfRestoreWarnings(); } $jobs = array(); // jobs for all tables // Construct jobqueue updates... // FIXME: if a bureaucrat renames a user in error, he/she // must be careful to wait until the rename finishes before // renaming back. This is due to the fact the the job "queue" // is not really FIFO, so we might end up with a bunch of edits // randomly mixed between the two new names. Some sort of rename // lock might be in order... foreach ($this->tablesJob as $table => $params) { $userTextC = $params[0]; // some *_user_text column $userIDC = $params[1]; // some *_user column $timestampC = $params[2]; // some *_timestamp column $res = $dbw->select($table, array($timestampC), array($userTextC => $this->old, $userIDC => $this->uid), __METHOD__, array('ORDER BY' => "{$timestampC} ASC")); $jobParams = array(); $jobParams['table'] = $table; $jobParams['column'] = $userTextC; $jobParams['uidColumn'] = $userIDC; $jobParams['timestampColumn'] = $timestampC; $jobParams['oldname'] = $this->old; $jobParams['newname'] = $this->new; $jobParams['userID'] = $this->uid; // Timestamp column data for index optimizations $jobParams['minTimestamp'] = '0'; $jobParams['maxTimestamp'] = '0'; $jobParams['count'] = 0; // Insert jobs into queue! while (true) { $row = $dbw->fetchObject($res); if (!$row) { # If there are any job rows left, add it to the queue as one job if ($jobParams['count'] > 0) { $jobs[] = Job::factory('renameUser', $oldTitle, $jobParams); } break; } # Since the ORDER BY is ASC, set the min timestamp with first row if ($jobParams['count'] === 0) { $jobParams['minTimestamp'] = $row->{$timestampC}; } # Keep updating the last timestamp, so it should be correct # when the last item is added. $jobParams['maxTimestamp'] = $row->{$timestampC}; # Update row counter $jobParams['count']++; # Once a job has $wgUpdateRowsPerJob rows, add it to the queue if ($jobParams['count'] >= $wgUpdateRowsPerJob) { $jobs[] = Job::factory('renameUser', $oldTitle, $jobParams); $jobParams['minTimestamp'] = '0'; $jobParams['maxTimestamp'] = '0'; $jobParams['count'] = 0; } } $dbw->freeResult($res); } $count = count($jobs); if ($count > 0) { JobQueueGroup::singleton()->push($jobs, JobQueue::QOS_ATOMIC); // don't commit yet $this->debug("Queued {$count} jobs for {$this->old} to {$this->new}"); } // Commit the transaction $dbw->commit(); // Delete from memcached again to make sure $user->invalidateCache(); // Clear caches and inform authentication plugins $user = User::newFromId($this->uid); $wgAuth->updateExternalDB($user); Hooks::run('RenameUserComplete', array($this->uid, $this->old, $this->new)); // Log it! $logEntry = new ManualLogEntry('renameuser', 'renameuser'); $logEntry->setPerformer($this->renamer); $logEntry->setTarget($oldTitle); $logEntry->setComment($this->reason); $logEntry->setParameters(array('4::olduser' => $this->old, '5::newuser' => $this->new, '6::edits' => $contribs)); $logid = $logEntry->insert(); $logEntry->publish($logid); $this->debug("Finished rename for {$this->old} to {$this->new}"); return true; }
public function wrap($command) { $job = \Job::factory($command, $this->title, $this->params); return $job->run(); }
/** * @param array $conds Query conditions * @return Iterator */ protected function getJobIterator(array $conds) { $dbr = $this->getSlaveDB(); try { return new MappedIterator($dbr->select('job', self::selectFields(), $conds), function ($row) { $job = Job::factory($row->job_cmd, Title::makeTitle($row->job_namespace, $row->job_title), strlen($row->job_params) ? unserialize($row->job_params) : array()); $job->metadata['id'] = $row->job_id; $job->metadata['timestamp'] = $row->job_timestamp; return $job; }); } catch (DBError $e) { $this->throwDBException($e); } }
/** * Executed when the user opens the DSMW administration special page * Calculates the PushFeed list and the pullfeed list (and everything that * is displayed on the psecial page * * @global <Object> $wgOut Output page instance * @global <String> $wgServerName * @global <String> $wgScriptPath * @return <bool> */ function execute() { global $wgOut, $wgRequest, $wgServerName, $wgScriptPath, $wgDSMWIP, $wgServerName, $wgScriptPath; /*, $wgSitename, $wgCachePages, $wgUser, $wgTitle, $wgDenyAccessMessage, $wgAllowAnonUsers, $wgRequest, $wgMessageCache, $wgWatchingMessages, $wgDBtype, $namespace_titles;*/ $urlServer = 'http://' . $wgServerName . $wgScriptPath; /**** Get status of refresh job, if any ****/ $dbr =& wfGetDB(DB_SLAVE); $row = $dbr->selectRow('job', '*', array('job_cmd' => 'DSMWUpdateJob'), __METHOD__); if ($row !== false) { // similar to Job::pop_type, but without deleting the job $title = Title::makeTitleSafe($row->job_namespace, $row->job_title); $updatejob = Job::factory($row->job_cmd, $title, Job::extractBlob($row->job_params), $row->job_id); } else { $updatejob = NULL; } $row1 = $dbr->selectRow('job', '*', array('job_cmd' => 'DSMWPropertyTypeJob'), __METHOD__); if ($row1 !== false) { // similar to Job::pop_type, but without deleting the job $title = Title::makeTitleSafe($row1->job_namespace, $row1->job_title); $propertiesjob = Job::factory($row1->job_cmd, $title, Job::extractBlob($row1->job_params), $row1->job_id); } else { $propertiesjob = NULL; } /**** Execute actions if any ****/ $action = $wgRequest->getText('action'); if ($action == 'logootize') { if ($updatejob === NULL) { // careful, there might be race conditions here $title = Title::makeTitle(NS_SPECIAL, 'DSMWAdmin'); $newjob = new DSMWUpdateJob($title); $newjob->insert(); $wgOut->addHTML('<p><font color="red"><b>Articles update process started.</b></font></p>'); } else { $wgOut->addHTML('<p><font color="red"><b>Articles update process is already running.</b></font></p>'); } } elseif ($action == 'addProperties') { if ($propertiesjob === NULL) { $title1 = Title::makeTitle(NS_SPECIAL, 'DSMWAdmin'); $newjob1 = new DSMWPropertyTypeJob($title1); $newjob1->insert(); $wgOut->addHTML('<p><font color="red"><b>Properties type update process started.</b></font></p>'); } else { $wgOut->addHTML('<p><font color="red"><b>Properties type update process is already running.</b></font></p>'); } } elseif ($action == 'updatetables') { $wgOut->disable(); // raw output ob_start(); print "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\" dir=\"ltr\">\n<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><title>Setting up Storage for Distributed Semantic MediaWiki</title></head><body><p><pre>"; header("Content-type: text/html; charset=UTF-8"); $db =& wfGetDB(DB_MASTER); $result = DSMWDBHelpers::setup($db); print '</pre></p>'; if ($result === true) { print '<p><b>The database was set up successfully.</b></p>'; } $returntitle = Title::makeTitle(NS_SPECIAL, 'DSMWAdmin'); print '<p> <a href="' . htmlspecialchars($returntitle->getFullURL()) . '">Special:DSMWAdmin</a></p>'; print '</body></html>'; ob_flush(); flush(); return; } $wgOut->setPagetitle("DSMW Settings"); $output = '<p>This page helps you during installation of Distributed Semantic MediaWiki.</p>'; // creating tables $output .= '<form name="buildtables" action="" method="POST">' . '<input type="hidden" name="action" value="updatetables">'; $output .= '<br /><h2>Database: DSMW tables installation</h2>' . '<p>Distributed Semantic MediaWiki requires some tables to be created in the database.</p>'; $output .= '<input type="submit" value="Initialise tables"/></form>'; // creating properties $output .= '<form name="properties" action="" method="POST">' . '<input type="hidden" name="action" value="addProperties">'; $output .= '<br /><h2>Update properties type</h2>' . '<p>Distributed Semantic MediaWiki requires some properties type to be set.</p>'; $output .= '<input type="submit" value="Update properties type"/></form>'; //Pass wiki article through Logoot $output .= '<form name="logoot" action="" method="POST">' . '<input type="hidden" name="action" value="logootize" />'; $output .= '<br /><h2>DSMW update older articles</h2>' . '<p>For reasons of conflict management, DSMW works only with articles created after it\'s installation. Therefore you need to update articles created before it\'s installation in order to edit them.</p>'; $output .= '<input type="submit" value="Articles update"/></form>'; $wgOut->addHTML($output); return false; }
/** * Pop a job off the front of the queue * * @param $offset Integer: Number of jobs to skip * @return Job or false if there's no jobs */ static function pop($offset = 0) { global $wgJobTypesExcludedFromDefaultQueue; wfProfileIn(__METHOD__); $dbr = wfGetDB(DB_SLAVE); /* Get a job from the slave, start with an offset, scan full set afterwards, avoid hitting purged rows NB: If random fetch previously was used, offset will always be ahead of few entries */ $conditions = array(); if (count($wgJobTypesExcludedFromDefaultQueue) != 0) { foreach ($wgJobTypesExcludedFromDefaultQueue as $cmdType) { $conditions[] = "job_cmd != " . $dbr->addQuotes($cmdType); } } $offset = intval($offset); $options = array('ORDER BY' => 'job_id', 'USE INDEX' => 'PRIMARY'); $row = $dbr->selectRow('job', '*', array_merge($conditions, array("job_id >= {$offset}")), __METHOD__, $options); // Refetching without offset is needed as some of job IDs could have had delayed commits // and have lower IDs than jobs already executed, blame concurrency :) // if ($row === false) { if ($offset != 0) { $row = $dbr->selectRow('job', '*', $conditions, __METHOD__, $options); } if ($row === false) { wfProfileOut(__METHOD__); return false; } } // Try to delete it from the master $dbw = wfGetDB(DB_MASTER); $dbw->delete('job', array('job_id' => $row->job_id), __METHOD__); $affected = $dbw->affectedRows(); $dbw->commit(); if (!$affected) { // Failed, someone else beat us to it // Try getting a random row $row = $dbw->selectRow('job', array('MIN(job_id) as minjob', 'MAX(job_id) as maxjob'), '1=1', __METHOD__); if ($row === false || is_null($row->minjob) || is_null($row->maxjob)) { // No jobs to get wfProfileOut(__METHOD__); return false; } // Get the random row $row = $dbw->selectRow('job', '*', 'job_id >= ' . mt_rand($row->minjob, $row->maxjob), __METHOD__); if ($row === false) { // Random job gone before we got the chance to select it // Give up wfProfileOut(__METHOD__); return false; } // Delete the random row $dbw->delete('job', array('job_id' => $row->job_id), __METHOD__); $affected = $dbw->affectedRows(); $dbw->commit(); if (!$affected) { // Random job gone before we exclusively deleted it // Give up wfProfileOut(__METHOD__); return false; } } // If execution got to here, there's a row in $row that has been deleted from the database // by this thread. Hence the concurrent pop was successful. wfIncrStats('job-pop'); $namespace = $row->job_namespace; $dbkey = $row->job_title; $title = Title::makeTitleSafe($namespace, $dbkey); $job = Job::factory($row->job_cmd, $title, Job::extractBlob($row->job_params), $row->job_id); // Remove any duplicates it may have later in the queue $job->removeDuplicates(); wfProfileOut(__METHOD__); return $job; }
/** * @param $suppress Bool * @param $by String * @param $reason String */ protected function doCrosswikiSuppression($suppress, $by, $reason) { global $wgCentralAuthWikisPerSuppressJob; $this->loadAttached(); if (count($this->mAttachedArray) <= $wgCentralAuthWikisPerSuppressJob) { foreach ($this->mAttachedArray as $wiki) { $this->doLocalSuppression($suppress, $wiki, $by, $reason); } } else { $jobParams = array('username' => $this->getName(), 'suppress' => $suppress, 'by' => $by, 'reason' => $reason); $jobs = array(); $chunks = array_chunk($this->mAttachedArray, $wgCentralAuthWikisPerSuppressJob); foreach ($chunks as $wikis) { $jobParams['wikis'] = $wikis; $jobs[] = Job::factory('crosswikiSuppressUser', Title::makeTitleSafe(NS_USER, $this->getName()), $jobParams); } JobQueueGroup::singleton()->push($jobs); } }
/** * @see JobQueue::getAllQueuedJobs() * @return Iterator */ public function getAllQueuedJobs() { $dbr = $this->getSlaveDB(); try { return new MappedIterator($dbr->select('job', self::selectFields(), array('job_cmd' => $this->getType(), 'job_token' => '')), function ($row) use($dbr) { $job = Job::factory($row->job_cmd, Title::makeTitle($row->job_namespace, $row->job_title), strlen($row->job_params) ? unserialize($row->job_params) : false); $job->metadata['id'] = $row->job_id; return $job; }); } catch (DBError $e) { $this->throwDBException($e); } }
/** * Sets up jobs to create and attach a local account for the given user on every wiki listed in * $wgCentralAuthAutoCreateWikis. * @param CentralAuthUser $centralUser */ private function autoCreateAccounts(CentralAuthUser $centralUser) { global $wgCentralAuthAutoCreateWikis; $name = $centralUser->getName(); $thisWiki = wfWikiID(); $session = RequestContext::getMain()->exportSession(); foreach ($wgCentralAuthAutoCreateWikis as $wiki) { if ($wiki === $thisWiki) { continue; } $job = Job::factory('CentralAuthCreateLocalAccountJob', Title::makeTitleSafe(NS_USER, $name), array('name' => $name, 'from' => $thisWiki, 'session' => $session)); JobQueueGroup::singleton($wiki)->push($job); } }
/** * Pop a job off the front of the queue * @static * @param $offset Number of jobs to skip * @return Job or false if there's no jobs */ static function pop($offset = 0) { wfProfileIn(__METHOD__); $dbr = wfGetDB(DB_SLAVE); /* Get a job from the slave, start with an offset, scan full set afterwards, avoid hitting purged rows NB: If random fetch previously was used, offset will always be ahead of few entries */ $row = $dbr->selectRow('job', '*', "job_id >= {$offset}", __METHOD__, array('ORDER BY' => 'job_id', 'LIMIT' => 1)); // Refetching without offset is needed as some of job IDs could have had delayed commits // and have lower IDs than jobs already executed, blame concurrency :) // if ($row === false) { if ($offset != 0) { $row = $dbr->selectRow('job', '*', '', __METHOD__, array('ORDER BY' => 'job_id', 'LIMIT' => 1)); } if ($row === false) { wfProfileOut(__METHOD__); return false; } } $offset = $row->job_id; // Try to delete it from the master $dbw = wfGetDB(DB_MASTER); $dbw->delete('job', array('job_id' => $row->job_id), __METHOD__); $affected = $dbw->affectedRows(); $dbw->immediateCommit(); if (!$affected) { // Failed, someone else beat us to it // Try getting a random row $row = $dbw->selectRow('job', array('MIN(job_id) as minjob', 'MAX(job_id) as maxjob'), "job_id >= {$offset}", __METHOD__); if ($row === false || is_null($row->minjob) || is_null($row->maxjob)) { // No jobs to get wfProfileOut(__METHOD__); return false; } // Get the random row $row = $dbw->selectRow('job', '*', 'job_id >= ' . mt_rand($row->minjob, $row->maxjob), __METHOD__); if ($row === false) { // Random job gone before we got the chance to select it // Give up wfProfileOut(__METHOD__); return false; } // Delete the random row $dbw->delete('job', array('job_id' => $row->job_id), __METHOD__); $affected = $dbw->affectedRows(); $dbw->immediateCommit(); if (!$affected) { // Random job gone before we exclusively deleted it // Give up wfProfileOut(__METHOD__); return false; } } // If execution got to here, there's a row in $row that has been deleted from the database // by this thread. Hence the concurrent pop was successful. $namespace = $row->job_namespace; $dbkey = $row->job_title; $title = Title::makeTitleSafe($namespace, $dbkey); $job = Job::factory($row->job_cmd, $title, Job::extractBlob($row->job_params), $row->job_id); // Remove any duplicates it may have later in the queue $dbw->delete('job', $job->insertFields(), __METHOD__); wfProfileOut(__METHOD__); return $job; }
/** * Do the rename operation */ function rename() { global $wgMemc, $wgAuth, $wgUpdateRowsPerJob; wfProfileIn(__METHOD__); $dbw = wfGetDB(DB_MASTER); $dbw->begin(); wfRunHooks('RenameUserPreRename', array($this->uid, $this->old, $this->new)); // Rename and touch the user before re-attributing edits, // this avoids users still being logged in and making new edits while // being renamed, which leaves edits at the old name. $dbw->update('user', array('user_name' => $this->new, 'user_touched' => $dbw->timestamp()), array('user_name' => $this->old), __METHOD__); if (!$dbw->affectedRows()) { $dbw->rollback(); wfProfileOut(__METHOD__); return false; } // Reset token to break login with central auth systems. // Again, avoids user being logged in with old name. $user = User::newFromId($this->uid); $authUser = $wgAuth->getUserInstance($user); $authUser->resetAuthToken(); // Delete from memcached. $wgMemc->delete(wfMemcKey('user', 'id', $this->uid)); // Update ipblock list if this user has a block in there. $dbw->update('ipblocks', array('ipb_address' => $this->new), array('ipb_user' => $this->uid, 'ipb_address' => $this->old), __METHOD__); // Update this users block/rights log. Ideally, the logs would be historical, // but it is really annoying when users have "clean" block logs by virtue of // being renamed, which makes admin tasks more of a pain... $oldTitle = Title::makeTitle(NS_USER, $this->old); $newTitle = Title::makeTitle(NS_USER, $this->new); $dbw->update('logging', array('log_title' => $newTitle->getDBkey()), array('log_type' => array('block', 'rights'), 'log_namespace' => NS_USER, 'log_title' => $oldTitle->getDBkey()), __METHOD__); // Do immediate updates! foreach ($this->tables as $table => $fieldSet) { list($nameCol, $userCol) = $fieldSet; $dbw->update($table, array($nameCol => $this->new), array($nameCol => $this->old, $userCol => $this->uid), __METHOD__); } // Increase time limit (like CheckUser); this can take a while... if ($this->tablesJob) { wfSuppressWarnings(); set_time_limit(120); wfRestoreWarnings(); } $jobs = array(); // jobs for all tables // Construct jobqueue updates... // FIXME: if a bureaucrat renames a user in error, he/she // must be careful to wait until the rename finishes before // renaming back. This is due to the fact the the job "queue" // is not really FIFO, so we might end up with a bunch of edits // randomly mixed between the two new names. Some sort of rename // lock might be in order... foreach ($this->tablesJob as $table => $params) { $userTextC = $params[0]; // some *_user_text column $userIDC = $params[1]; // some *_user column $timestampC = $params[2]; // some *_timestamp column $res = $dbw->select($table, array($timestampC), array($userTextC => $this->old, $userIDC => $this->uid), __METHOD__, array('ORDER BY' => "{$timestampC} ASC")); $jobParams = array(); $jobParams['table'] = $table; $jobParams['column'] = $userTextC; $jobParams['uidColumn'] = $userIDC; $jobParams['timestampColumn'] = $timestampC; $jobParams['oldname'] = $this->old; $jobParams['newname'] = $this->new; $jobParams['userID'] = $this->uid; // Timestamp column data for index optimizations $jobParams['minTimestamp'] = '0'; $jobParams['maxTimestamp'] = '0'; $jobParams['count'] = 0; // Insert jobs into queue! while (true) { $row = $dbw->fetchObject($res); if (!$row) { # If there are any job rows left, add it to the queue as one job if ($jobParams['count'] > 0) { $jobs[] = Job::factory('renameUser', $oldTitle, $jobParams); } break; } # Since the ORDER BY is ASC, set the min timestamp with first row if ($jobParams['count'] == 0) { $jobParams['minTimestamp'] = $row->{$timestampC}; } # Keep updating the last timestamp, so it should be correct # when the last item is added. $jobParams['maxTimestamp'] = $row->{$timestampC}; # Update row counter $jobParams['count']++; # Once a job has $wgUpdateRowsPerJob rows, add it to the queue if ($jobParams['count'] >= $wgUpdateRowsPerJob) { $jobs[] = Job::factory('renameUser', $oldTitle, $jobParams); $jobParams['minTimestamp'] = '0'; $jobParams['maxTimestamp'] = '0'; $jobParams['count'] = 0; } } $dbw->freeResult($res); } if (count($jobs) > 0) { Job::safeBatchInsert($jobs); // don't commit yet } // Commit the transaction $dbw->commit(); // Delete from memcached again to make sure $wgMemc->delete(wfMemcKey('user', 'id', $this->uid)); // Clear caches and inform authentication plugins $user = User::newFromId($this->uid); $wgAuth->updateExternalDB($user); wfRunHooks('RenameUserComplete', array($this->uid, $this->old, $this->new)); wfProfileOut(__METHOD__); return true; }
public function execute($par) { global $wgOut, $wgRequest, $wgServer, $wgArticlePath, $wgUser, $smwgAdminRefreshStore; if (!$this->userCanExecute($wgUser)) { // If the user is not authorized, show an error. $this->displayRestrictionError(); return; } $this->setHeaders(); /**** Get status of refresh job, if any ****/ $dbr = wfGetDB(DB_SLAVE); $row = $dbr->selectRow('job', '*', array('job_cmd' => 'SMWRefreshJob'), __METHOD__); if ($row !== false) { // similar to Job::pop_type, but without deleting the job $title = Title::makeTitleSafe($row->job_namespace, $row->job_title); $refreshjob = Job::factory($row->job_cmd, $title, Job::extractBlob($row->job_params), $row->job_id); } else { $refreshjob = null; } /**** Execute actions if any ****/ $action = $wgRequest->getText('action'); if ($action == 'updatetables') { $sure = $wgRequest->getText('udsure'); if ($sure == 'yes') { $wgOut->disable(); // raw output ob_start(); print "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\" dir=\"ltr\">\n<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><title>Setting up Storage for Semantic MediaWiki</title></head><body><p><pre>"; header("Content-type: text/html; charset=UTF-8"); $result = smwfGetStore()->setup(); wfRunHooks('smwInitializeTables'); print '</pre></p>'; if ($result === true) { print '<p><b>' . wfMsg('smw_smwadmin_setupsuccess') . "</b></p>\n"; } $returntitle = SpecialPage::getTitleFor('SMWAdmin'); print '<p> ' . wfMsg('smw_smwadmin_return', '<a href="' . htmlspecialchars($returntitle->getFullURL()) . '">Special:SMWAdmin</a>') . "</p>\n"; print '</body></html>'; ob_flush(); flush(); return; } } elseif ($smwgAdminRefreshStore && $action == 'refreshstore') { // managing refresh jobs for the store $sure = $wgRequest->getText('rfsure'); if ($sure == 'yes') { if (is_null($refreshjob)) { // careful, there might be race conditions here $title = SpecialPage::getTitleFor('SMWAdmin'); $jobParams = array('spos' => 1, 'prog' => 0, 'rc' => 2); // wikia change start - jobqueue migration $task = new \Wikia\Tasks\Tasks\JobWrapperTask(); $task->call('SMWRefreshJob', $title, $jobParams); $task->queue(); // wikia change end $wgOut->addHTML('<p>' . wfMsg('smw_smwadmin_updatestarted') . '</p>'); } else { $wgOut->addHTML('<p>' . wfMsg('smw_smwadmin_updatenotstarted') . '</p>'); } } elseif ($sure == 'stop') { $dbw = wfGetDB(DB_MASTER); // delete (all) existing iteration jobs $dbw->delete('job', array('job_cmd' => 'SMWRefreshJob'), __METHOD__); $wgOut->addHTML('<p>' . wfMsg('smw_smwadmin_updatestopped') . '</p>'); } else { $wgOut->addHTML('<p>' . wfMsg('smw_smwadmin_updatenotstopped') . '</p>'); } return; } /**** Normal output ****/ $html = '<p>' . wfMsg('smw_smwadmin_docu') . "</p>\n"; // creating tables and converting contents from older versions $html .= '<form name="buildtables" action="" method="POST">' . "\n" . '<input type="hidden" name="action" value="updatetables" />' . "\n"; $html .= '<br /><h2>' . wfMsg('smw_smwadmin_db') . "</h2>\n" . '<p>' . wfMsg('smw_smwadmin_dbdocu') . "</p>\n"; $html .= '<p>' . wfMsg('smw_smwadmin_permissionswarn') . "</p>\n" . '<input type="hidden" name="udsure" value="yes"/>' . '<input type="submit" value="' . wfMsg('smw_smwadmin_dbbutton') . '"/></form>' . "\n"; $html .= '<br /><h2>' . wfMsg('smw_smwadmin_datarefresh') . "</h2>\n" . '<p>' . wfMsg('smw_smwadmin_datarefreshdocu') . "</p>\n"; if (!is_null($refreshjob)) { $prog = $refreshjob->getProgress(); $html .= '<p>' . wfMsg('smw_smwadmin_datarefreshprogress') . "</p>\n" . '<p><div style="float: left; background: #DDDDDD; border: 1px solid grey; width: 300px; "><div style="background: #AAF; width: ' . round($prog * 300) . 'px; height: 20px; "> </div></div>  ' . round($prog * 100, 4) . '%</p><br /><br />'; if ($smwgAdminRefreshStore) { $html .= '<form name="refreshwiki" action="" method="POST">' . '<input type="hidden" name="action" value="refreshstore" />' . '<input type="submit" value="' . htmlspecialchars(wfMsg('smw_smwadmin_datarefreshstop')) . '" /> ' . ' <input type="checkbox" name="rfsure" value="stop"/> ' . htmlspecialchars(wfMsg('smw_smwadmin_datarefreshstopconfirm')) . '</form>' . "\n"; } } elseif ($smwgAdminRefreshStore) { $html .= '<form name="refreshwiki" action="" method="POST">' . '<input type="hidden" name="action" value="refreshstore" />' . '<input type="hidden" name="rfsure" value="yes"/>' . '<input type="submit" value="' . wfMsg('smw_smwadmin_datarefreshbutton') . '"/>' . '</form>' . "\n"; } $html .= '<br /><h2>' . wfMsg('smw_smwadmin_announce') . "</h2>\n" . '<p>' . wfMsg('smw_smwadmin_announcedocu') . "</p>\n" . '<p>' . wfMsg('smw_smwadmin_announcebutton') . "</p>\n" . '<form name="announcewiki" action="http://semantic-mediawiki.org/wiki/Special:SMWRegistry" method="GET">' . '<input type="hidden" name="url" value="' . $wgServer . str_replace('$1', '', $wgArticlePath) . '" />' . '<input type="hidden" name="return" value="Special:SMWAdmin" />' . '<input type="submit" value="' . wfMsg('smw_smwadmin_announce') . '"/></form>' . "\n"; $html .= '<br /><h2>' . wfMsg('smw_smwadmin_support') . "</h2>\n" . '<p>' . wfMsg('smw_smwadmin_supportdocu') . "</p>\n" . "<ul>\n" . '<li>' . wfMsg('smw_smwadmin_installfile') . "</li>\n" . '<li>' . wfMsg('smw_smwadmin_smwhomepage') . "</li>\n" . '<li>' . wfMsg('smw_smwadmin_mediazilla') . "</li>\n" . '<li>' . wfMsg('smw_smwadmin_questions') . "</li>\n" . "</ul>\n"; $wgOut->addHTML($html); }
/** * Do the rename operation */ function rename() { global $wgMemc, $wgDBname, $wgAuth; wfProfileIn(__METHOD__); $dbw = wfGetDB(DB_MASTER); // Rename and touch the user before re-attributing edits, // this avoids users still being logged in and making new edits while // being renamed, which leaves edits at the old name. $dbw->update('user', array('user_name' => $this->new, 'user_touched' => $dbw->timestamp()), array('user_name' => $this->old), __METHOD__); // Update ipblock list if this user has a block in there. $dbw->update('ipblocks', array('ipb_address' => $this->new), array('ipb_user' => $this->uid, 'ipb_address' => $this->old), __METHOD__); // Update this users block/rights log. Ideally, the logs would be historical, // but it is really annoying when users have "clean" block logs by virtue of // being renamed, which makes admin tasks more of a pain... $oldTitle = Title::makeTitle(NS_USER, $this->old); $newTitle = Title::makeTitle(NS_USER, $this->new); $dbw->update('logging', array('log_title' => $newTitle->getDBKey()), array('log_type' => array('block', 'rights'), 'log_namespace' => NS_USER, 'log_title' => $oldTitle->getDBKey()), __METHOD__); // Do immediate updates! foreach ($this->tables as $table => $field) { $dbw->update($table, array($field => $this->new), array($field => $this->old), __METHOD__); } // Construct jobqueue updates... foreach ($this->tablesJob as $table => $params) { $userTextC = $params[0]; // some *_user_text column $userIDC = $params[1]; // some *_user column $timestampC = $params[2]; // some *_timestamp column $res = $dbw->select($table, array($userTextC, $timestampC), array($userTextC => $this->old, $userIDC => $this->uid), __METHOD__, array('ORDER BY' => "{$timestampC} ASC")); global $wgUpdateRowsPerJob; $batchSize = 500; // Lets not flood the job table! $jobSize = $wgUpdateRowsPerJob; // How many rows per job? $jobParams = array(); $jobParams['table'] = $table; $jobParams['column'] = $userTextC; $jobParams['uidColumn'] = $userIDC; $jobParams['timestampColumn'] = $timestampC; $jobParams['oldname'] = $this->old; $jobParams['newname'] = $this->new; $jobParams['userID'] = $this->uid; // Timestamp column data for index optimizations $jobParams['minTimestamp'] = '0'; $jobParams['maxTimestamp'] = '0'; $jobParams['count'] = 0; // Insert into queue! $jobRows = 0; $done = false; while (!$done) { $jobs = array(); for ($i = 0; $i < $batchSize; $i++) { $row = $dbw->fetchObject($res); if (!$row) { # If there are any job rows left, add it to the queue as one job if ($jobRows > 0) { $jobParams['count'] = $jobRows; $jobs[] = Job::factory('renameUser', $oldTitle, $jobParams); $jobParams['minTimestamp'] = '0'; $jobParams['maxTimestamp'] = '0'; $jobParams['count'] = 0; $jobRows = 0; } $done = true; break; } # If we are adding the first item, since the ORDER BY is ASC, set # the min timestamp if ($jobRows == 0) { $jobParams['minTimestamp'] = $row->{$timestampC}; } # Keep updating the last timestamp, so it should be correct when the last item is added. $jobParams['maxTimestamp'] = $row->{$timestampC}; # Update nice counter $jobRows++; # Once a job has $jobSize rows, add it to the queue if ($jobRows >= $jobSize) { $jobParams['count'] = $jobRows; $jobs[] = Job::factory('renameUser', $oldTitle, $jobParams); $jobParams['minTimestamp'] = '0'; $jobParams['maxTimestamp'] = '0'; $jobParams['count'] = 0; $jobRows = 0; } } Job::batchInsert($jobs); } $dbw->freeResult($res); } // Clear caches and inform authentication plugins $user = User::newFromId($this->uid); $user->invalidateCache(); $wgAuth->updateExternalDB($user); wfRunHooks('RenameUserComplete', array($this->uid, $this->old, $this->new)); wfProfileOut(__METHOD__); }
/** * @see JobQueue::doPop() * @return Job|bool */ protected function doPop() { global $wgMemc; if ($wgMemc->get($this->getEmptinessCacheKey()) === 'true') { return false; // queue is empty } $dbw = $this->getMasterDB(); $dbw->commit(__METHOD__, 'flush'); // flush existing transaction $uuid = wfRandomString(32); // pop attempt $job = false; // job popped off $autoTrx = $dbw->getFlag(DBO_TRX); // automatic begin() enabled? $dbw->clearFlag(DBO_TRX); // make each query its own transaction try { do { // retry when our row is invalid or deleted as a duplicate // Try to reserve a row in the DB... if ($this->order === 'timestamp') { // oldest first $row = $this->claimOldest($uuid); } else { // random first $rand = mt_rand(0, self::MAX_JOB_RANDOM); // encourage concurrent UPDATEs $gte = (bool) mt_rand(0, 1); // find rows with rand before/after $rand $row = $this->claimRandom($uuid, $rand, $gte); if (!$row) { // need to try the other direction $row = $this->claimRandom($uuid, $rand, !$gte); } } // Check if we found a row to reserve... if (!$row) { $wgMemc->set($this->getEmptinessCacheKey(), 'true', self::CACHE_TTL); break; // nothing to do } // Get the job object from the row... $title = Title::makeTitleSafe($row->job_namespace, $row->job_title); if (!$title) { $dbw->delete('job', array('job_id' => $row->job_id), __METHOD__); wfIncrStats('job-pop'); wfDebugLog('JobQueueDB', "Row has invalid title '{$row->job_title}'."); continue; // try again } $job = Job::factory($row->job_cmd, $title, self::extractBlob($row->job_params), $row->job_id); // Delete any *other* duplicate jobs in the queue... if ($job->ignoreDuplicates() && strlen($row->job_sha1)) { $dbw->delete('job', array('job_sha1' => $row->job_sha1, "job_id != {$dbw->addQuotes($row->job_id)}"), __METHOD__); wfIncrStats('job-pop', $dbw->affectedRows()); } break; // done } while (true); } catch (DBError $e) { $dbw->setFlag($autoTrx ? DBO_TRX : 0); // restore automatic begin() throw $e; } $dbw->setFlag($autoTrx ? DBO_TRX : 0); // restore automatic begin() return $job; }