/**
  * 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))));
 }