/** * Called after all databases have been updated. Needs to purge any * cache that contained data about $oldUser * * @param integer $oldUserId * @param integer $newUserId */ public function finalizeMerge($oldUserId, $newUserId) { $dbw = $this->dbFactory->getDb(DB_MASTER); foreach ($this->config as $table => $config) { foreach ($config['userColumns'] as $column => $userTupleGetter) { $it = new EchoBatchRowIterator($dbw, $table, $config['pk'], 500); // The database is migrated, so look for the new user id $it->addConditions(array($column => $newUserId)); if (isset($config['loadColumns'])) { $it->setFetchColumns($config['loadColumns']); } $this->purgeTable($it, $oldUserId, $config['load'], $userTupleGetter); } } }
/** * At the moment, does three things: * 1. Finds UUID objects and returns their database representation. * 2. Checks for unarmoured raw SQL and errors out if it exists. * 3. Finds armoured raw SQL and expands it out. * * @param array $data Query conditions for DatabaseBase::select * @return array query conditions escaped for use * @throws DataModelException */ protected function preprocessSqlArray(array $data) { // Assuming that all databases have the same escaping settings. $db = $this->dbFactory->getDB(DB_SLAVE); $data = UUID::convertUUIDs($data, 'binary'); foreach ($data as $key => $value) { if ($value instanceof RawSql) { $data[$key] = $value->getSql($db); } elseif (is_numeric($key)) { throw new DataModelException("Unescaped raw SQL found in " . __METHOD__, 'process-data'); } elseif (!preg_match('/^[A-Za-z0-9\\._]+$/', $key)) { throw new DataModelException("Dangerous SQL field name '{$key}' found in " . __METHOD__, 'process-data'); } } return $data; }
/** * @param UUID|null $fromId * @param UUID|null $toId * @param int|null $namespace * @return array */ public function buildQueryConditions(UUID $fromId = null, UUID $toId = null, $namespace = null) { $dbr = $this->dbFactory->getDB(DB_SLAVE); $conditions = array(); // only find entries in a given range if ($fromId !== null) { $conditions[] = 'rev_id >= ' . $dbr->addQuotes($fromId->getBinary()); } if ($toId !== null) { $conditions[] = 'rev_id <= ' . $dbr->addQuotes($toId->getBinary()); } // find only within requested wiki/namespace $conditions['workflow_wiki'] = wfWikiId(); if ($namespace !== null) { $conditions['workflow_namespace'] = $namespace; } return $conditions; }
/** * Look up usernames while respecting ipblocks with two queries * * @param string $wiki * @param array $userIds * @return \ResultWrapper|bool * @throws FlowException */ public function execute($wiki, array $userIds) { if (!$wiki) { throw new FlowException('No wiki provided with user ids'); } $dbr = $this->dbFactory->getWikiDB(DB_SLAVE, array(), $wiki); $res = $dbr->select('ipblocks', 'ipb_user', array('ipb_user' => $userIds, 'ipb_deleted' => 1), __METHOD__); if (!$res) { return $res; } $blocked = array(); foreach ($res as $row) { $blocked[] = $row->ipb_user; } // return ids in $userIds that are not in $blocked $allowed = array_diff($userIds, $blocked); if (!$allowed) { return false; } return $dbr->select('user', array('user_id', 'user_name'), array('user_id' => $allowed), __METHOD__); }
/** * Collects the workflow and header (if it exists) and puts them into the database. Does * not commit yet. It is intended for prepareMove to be called from the TitleMove hook, * and committed from TitleMoveComplete hook. This ensures that if some error prevents the * core transaction from committing this transaction is also not committed. * * @param int $oldPageId Page ID before move/change * @param Title $newPage Page after move/change */ public function prepareMove($oldPageId, Title $newPage) { if ($this->dbw !== null) { throw new FlowException("Already prepared for move from {$oldPageId} to {$newPage->getArticleID()}"); } // All reads must go through master to help ensure consistency $this->dbFactory->forceMaster(); // Open a transaction, this will be closed from self::commit. $this->dbw = $this->dbFactory->getDB(DB_MASTER); $this->dbw->begin(); $this->cache->begin(); // @todo this loads every topic workflow this board has ever seen, // would prefer to update db directly but that won't work due to // the caching layer not getting updated. After dropping Flow\Data\Index\* // revisit this. $found = $this->storage->find('Workflow', array('workflow_wiki' => wfWikiId(), 'workflow_page_id' => $oldPageId)); if (!$found) { throw new FlowException("Could not locate workflow for {$oldPageId}"); } $discussionWorkflow = null; foreach ($found as $workflow) { if ($workflow->getType() === 'discussion') { $discussionWorkflow = $workflow; } $workflow->updateFromPageId($oldPageId, $newPage); $this->storage->put($workflow, array()); } if ($discussionWorkflow === null) { throw new FlowException("Main discussion workflow for {$oldPageId} not found"); } $found = $this->storage->find('Header', array('rev_type_id' => $discussionWorkflow->getId()), array('sort' => 'rev_id', 'order' => 'DESC', 'limit' => 1)); if ($found) { $this->header = reset($found); $nextHeader = $this->header->newNextRevision($this->nullEditUser, $this->header->getContentRaw(), $this->header->getContentFormat(), 'edit-header', $newPage); $this->storage->put($nextHeader, array('workflow' => $discussionWorkflow)); } }
/** * @param Workflow $workflow * @param AbstractBlock[] $blocks * @return array Map from committed block name to an array of metadata returned * about inserted objects. * @throws \Exception */ public function commit(Workflow $workflow, array $blocks) { $cache = $this->bufferedCache; $dbw = $this->dbFactory->getDB(DB_MASTER); /** * Ideally, I'd create the page in Workflow::toStorageRow, but * WikiPage::doEditContent uses transactions & our DB wrapper * doesn't allow nested transactions, so that part has moved. * * Don't allowCreation() here: a board has to be explicitly created, * or allowed via the occupyNamespace & occupyPages globals, in * which case allowCreation() won't be needed. * * @var OccupationController $occupationController */ $occupationController = Container::get('occupation_controller'); $title = $workflow->getOwnerTitle(); $occupationController->ensureFlowRevision(new \Article($title), $workflow); try { $dbw->begin(); $cache->begin(); $results = array(); foreach ($blocks as $block) { $results[$block->getName()] = $block->commit(); } $dbw->commit(); // Now commit to cache. If this fails, cache keys should have been // invalidated, but still log the failure. if (!$cache->commit()) { wfDebugLog('Flow', __METHOD__ . ': Committed to database but failed applying to cache'); } } catch (\Exception $e) { while (!$this->deferredQueue->isEmpty()) { $this->deferredQueue->dequeue(); } $dbw->rollback(); $cache->rollback(); throw $e; } while (!$this->deferredQueue->isEmpty()) { DeferredUpdates::addCallableUpdate($this->deferredQueue->dequeue()); } $workflow->getArticleTitle()->purgeSquid(); return $results; }
/** * @param array $conditions * @param int $limit * @param string $revisionClass Storage type (e.g. "PostRevision", "Header") * @return ResultWrapper|false false on failure * @throws \MWException */ protected function queryRevisions($conditions, $limit, $revisionClass) { $dbr = $this->dbFactory->getDB(DB_SLAVE); switch ($revisionClass) { case 'PostRevision': return $dbr->select(array('flow_revision', 'flow_tree_revision', 'flow_tree_node', 'flow_workflow'), array('*'), $conditions, __METHOD__, array('LIMIT' => $limit, 'ORDER BY' => 'rev_id DESC'), array('flow_tree_revision' => array('INNER JOIN', array('tree_rev_id = rev_id')), 'flow_tree_node' => array('INNER JOIN', array('tree_descendant_id = tree_rev_descendant_id')), 'flow_workflow' => array('INNER JOIN', array('workflow_id = tree_ancestor_id')))); break; case 'Header': return $dbr->select(array('flow_revision', 'flow_workflow'), array('*'), $conditions, __METHOD__, array('LIMIT' => $limit, 'ORDER BY' => 'rev_id DESC'), array('flow_workflow' => array('INNER JOIN', array('workflow_id = rev_type_id', 'rev_type' => 'header')))); break; case 'PostSummary': return $dbr->select(array('flow_revision', 'flow_workflow', 'flow_tree_node'), array('*'), $conditions + array('workflow_id = tree_ancestor_id', 'tree_descendant_id = rev_type_id', 'rev_type' => 'post-summary'), __METHOD__, array('LIMIT' => $limit, 'ORDER BY' => 'rev_id DESC')); break; default: throw new \MWException('Unsupported revision type ' . $revisionClass); break; } }
/** * @param UUID[] $nodes * @return UUID[] * @throws \Flow\Exception\DataModelException */ public function fetchParentMapFromDb(array $nodes) { // Find out who the parent is for those nodes $dbr = $this->dbFactory->getDB(DB_SLAVE); $res = $dbr->select($this->tableName, array('tree_ancestor_id', 'tree_descendant_id'), array('tree_descendant_id' => UUID::convertUUIDs($nodes), 'tree_depth' => 1), __METHOD__); if (!$res) { return array(); } $result = array(); foreach ($res as $node) { if (isset($result[$node->tree_descendant_id])) { throw new DataModelException('Already have a parent for ' . $node->tree_descendant_id, 'process-data'); } $descendant = UUID::create($node->tree_descendant_id); $result[$descendant->getAlphadecimal()] = UUID::create($node->tree_ancestor_id); } foreach ($nodes as $node) { if (!isset($result[$node->getAlphadecimal()])) { // $node is a root, it has no parent $result[$node->getAlphadecimal()] = null; } } return $result; }
public function __construct(Workflow $boardWorkflow, ManagerGroup $storage, ImportSourceStore $sourceStore, LoggerInterface $logger, BufferedCache $cache, DbFactory $dbFactory, Postprocessor $postprocessor, SplQueue $deferredQueue, $allowUnknownUsernames = false) { $this->storage = $storage; $this->boardWorkflow = $boardWorkflow; $this->sourceStore = $sourceStore; $this->logger = $logger; $this->cache = $cache; $this->dbw = $dbFactory->getDB(DB_MASTER); $this->postprocessor = $postprocessor; $this->deferredQueue = $deferredQueue; $this->allowUnknownUsernames = $allowUnknownUsernames; // Get our workflow UUID property $this->workflowIdProperty = new ReflectionProperty('Flow\\Model\\Workflow', 'id'); $this->workflowIdProperty->setAccessible(true); // Get our revision UUID properties $this->postIdProperty = new ReflectionProperty('Flow\\Model\\PostRevision', 'postId'); $this->postIdProperty->setAccessible(true); $this->revIdProperty = new ReflectionProperty('Flow\\Model\\AbstractRevision', 'revId'); $this->revIdProperty->setAccessible(true); $this->lastEditIdProperty = new ReflectionProperty('Flow\\Model\\AbstractRevision', 'lastEditId'); $this->lastEditIdProperty->setAccessible(true); }
/** * Look up usernames while respecting ipblocks with one query. * Unused, check to see if this is reasonable to use. * * @param string $wiki * @param array $userIds * @return \ResultWrapper|null */ public function execute($wiki, array $userIds) { $dbr = $this->dbFactory->getWikiDb(DB_SLAVE, array(), $wiki); return $dbr->select(array('user', 'ipblocks'), array('user_id', 'user_name'), array('user_id' => $userIds, 'ipb_deleted is null'), __METHOD__, array(), array('ipblocks' => array('LEFT OUTER', array('ipb_user' => 'user_id', 'ipb_deleted' => 1)))); }