/**
  * Accepts a result set as sent out to the CategoryViewer::doCategoryQuery
  * hook.
  *
  * @param ResultWrapper|array $rows
  */
 public function loadMetadataBatch($rows)
 {
     $neededPosts = array();
     $neededWorkflows = array();
     foreach ($rows as $row) {
         if ($row->page_namespace != NS_TOPIC) {
             continue;
         }
         $uuid = UUID::create(strtolower($row->page_title));
         if ($uuid) {
             $alpha = $uuid->getAlphadecimal();
             $neededPosts[$alpha] = array('rev_type_id' => $uuid);
             $neededWorkflows[$alpha] = $uuid;
         }
     }
     if (!$neededPosts) {
         return;
     }
     $this->posts = $this->storage->findMulti('PostRevision', $neededPosts, array('sort' => 'rev_id', 'order' => 'DESC', 'limit' => 1));
     $workflows = $this->storage->getMulti('Workflow', $neededWorkflows);
     // @todo fixme: these should have come back with the apropriate array
     // key since we passed it in above, but didn't.
     foreach ($workflows as $workflow) {
         $this->workflows[$workflow->getId()->getAlphadecimal()] = $workflow;
     }
 }
 public function updateRevision($columnPrefix, DatabaseBase $dbw, $continue = null)
 {
     $rows = $dbw->select('flow_revision', array('rev_id', 'rev_type'), array('rev_id > ' . $dbw->addQuotes($continue), "{$columnPrefix}_id > 0", "{$columnPrefix}_ip IS NOT NULL"), __METHOD__, array('LIMIT' => $this->mBatchSize, 'ORDER BY' => 'rev_id'));
     $ids = $objs = array();
     foreach ($rows as $row) {
         $id = UUID::create($row->rev_id);
         $type = self::$types[$row->rev_type];
         $om = $this->storage->getStorage($type);
         $obj = $om->get($id);
         if ($obj) {
             $om->merge($obj);
             $ids[] = $row->rev_id;
             $objs[] = $obj;
         } else {
             $this->error(__METHOD__ . ": Failed loading {$type}: " . $id->getAlphadecimal());
         }
     }
     if (!$ids) {
         return null;
     }
     $dbw->update('flow_revision', array("{$columnPrefix}_ip" => null), array('rev_id' => $ids), __METHOD__);
     foreach ($objs as $obj) {
         $this->storage->cachePurge($obj);
     }
     $this->completeCount += count($ids);
     return end($ids);
 }
 /**
  * @dataProvider provideGetExistingReferences
  */
 public function testGetExistingReferences(array $references)
 {
     list($workflow, $revision, $title) = $this->getBlandTestObjects();
     $references = $this->expandReferences($workflow, $revision, $references);
     $this->storage->multiPut($references);
     $foundReferences = $this->recorder->getExistingReferences($revision->getRevisionType(), $revision->getCollectionId());
     $this->assertReferenceListsEqual($references, $foundReferences);
 }
 /**
  * Gets a Workflow object given its ID
  * @param  UUID   $workflowId The Workflow ID to retrieve.
  * @return Workflow           The Workflow.
  */
 protected function getWorkflowById(UUID $workflowId)
 {
     $alpha = $workflowId->getAlphadecimal();
     if (isset($this->workflowCache[$alpha])) {
         return $this->workflowCache[$alpha];
     } else {
         return $this->workflowCache[$alpha] = $this->storage->get('Workflow', $workflowId);
     }
 }
 /**
  * @param object $row Single row from database
  * @return AbstractRevision|null
  */
 protected function loadFromRevision($row)
 {
     $revTypes = array('header' => 'Flow\\Model\\Header', 'post-summary' => 'Flow\\Model\\PostSummary', 'post' => 'Flow\\Model\\PostRevision');
     if (!isset($revTypes[$row->rev_type])) {
         wfDebugLog('Flow', __METHOD__ . ': Unknown revision type ' . $row->rev_type . ' did not merge ' . UUID::create($row->rev_id)->getAlphadecimal());
         return null;
     }
     return $this->storage->get($revTypes[$row->rev_type], $row->rev_id);
 }
 /**
  * @param Title|false $title
  * @param UUID $workflowId
  * @return Workflow
  * @throws InvalidInputException
  */
 protected function loadWorkflowById($title, $workflowId)
 {
     /** @var Workflow $workflow */
     $workflow = $this->storage->getStorage('Workflow')->get($workflowId);
     if (!$workflow) {
         throw new UnknownWorkflowIdException('Invalid workflow requested by id', 'invalid-input');
     }
     if ($title !== false && $this->pageMoveInProgress === false && !$workflow->matchesTitle($title)) {
         throw new InvalidInputException('Flow workflow is for different page', 'invalid-input');
     }
     return $workflow;
 }
 /**
  * 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 UUID[] $topicIds
  * @return PostRevision[]
  * @throws InvalidDataException
  */
 public function getMulti(array $topicIds)
 {
     if (!$topicIds) {
         return array();
     }
     // load posts for all located post ids
     $allPostIds = $this->fetchRelatedPostIds($topicIds);
     $queries = array();
     foreach ($allPostIds as $postId) {
         $queries[] = array('rev_type_id' => $postId);
     }
     $found = $this->storage->findMulti('PostRevision', $queries, array('sort' => 'rev_id', 'order' => 'DESC', 'limit' => 1));
     /** @var PostRevision[] $posts */
     $posts = $children = array();
     foreach ($found as $indexResult) {
         $post = reset($indexResult);
         // limit => 1 means only 1 result per query
         if (isset($posts[$post->getPostId()->getAlphadecimal()])) {
             throw new InvalidDataException('Multiple results for id: ' . $post->getPostId()->getAlphadecimal(), 'fail-load-data');
         }
         $posts[$post->getPostId()->getAlphadecimal()] = $post;
     }
     $prettyPostIds = array();
     foreach ($allPostIds as $id) {
         $prettyPostIds[] = $id->getAlphadecimal();
     }
     $missing = array_diff($prettyPostIds, array_keys($posts));
     if ($missing) {
         // convert string uuid's into UUID objects
         /** @var UUID[] $missingUUID */
         $missingUUID = array_map(array('Flow\\Model\\UUID', 'create'), $missing);
         // we'll need to know parents to add stub post correctly in post hierarchy
         $parents = $this->treeRepo->fetchParentMap($missingUUID);
         $missingParents = array_diff($missing, array_keys($parents));
         if ($missingParents) {
             // if we can't fetch a post's original position in the tree
             // hierarchy, we can't create a stub post to display, so bail
             throw new InvalidDataException('Missing Posts & parents: ' . json_encode($missingParents), 'fail-load-data');
         }
         foreach ($missingUUID as $postId) {
             $content = wfMessage('flow-stub-post-content')->text();
             $username = wfMessage('flow-system-usertext')->text();
             $user = \User::newFromName($username);
             // create a stub post instead of failing completely
             $post = PostRevision::newFromId($postId, $user, $content, 'wikitext');
             $post->setReplyToId($parents[$postId->getAlphadecimal()]);
             $posts[$postId->getAlphadecimal()] = $post;
             wfWarn('Missing Posts: ' . FormatJson::encode($missing));
         }
     }
     // another helper to catch bugs in dev
     $extra = array_diff(array_keys($posts), $prettyPostIds);
     if ($extra) {
         throw new InvalidDataException('Found unrequested posts: ' . FormatJson::encode($extra), 'fail-load-data');
     }
     // populate array of children
     foreach ($posts as $post) {
         if ($post->getReplyToId()) {
             $children[$post->getReplyToId()->getAlphadecimal()][$post->getPostId()->getAlphadecimal()] = $post;
         }
     }
     $extraParents = array_diff(array_keys($children), $prettyPostIds);
     if ($extraParents) {
         throw new InvalidDataException('Found posts with unrequested parents: ' . FormatJson::encode($extraParents), 'fail-load-data');
     }
     foreach ($posts as $postId => $post) {
         $postChildren = array();
         $postDepth = 0;
         // link parents to their children
         if (isset($children[$postId])) {
             // sort children with oldest items first
             ksort($children[$postId]);
             $postChildren = $children[$postId];
         }
         // determine threading depth of post
         $replyToId = $post->getReplyToId();
         while ($replyToId && isset($children[$replyToId->getAlphadecimal()])) {
             $postDepth++;
             $replyToId = $posts[$replyToId->getAlphadecimal()]->getReplyToId();
         }
         $post->setChildren($postChildren);
         $post->setDepth($postDepth);
     }
     // return only the requested posts, rest are available as children.
     // Return in same order as requested
     /** @var PostRevision[] $roots */
     $roots = array();
     foreach ($topicIds as $id) {
         $roots[$id->getAlphadecimal()] = $posts[$id->getAlphadecimal()];
     }
     // Attach every post in the tree to its root. setRootPost
     // recursively applies it to all children as well.
     foreach ($roots as $post) {
         $post->setRootPost($post);
     }
     return $roots;
 }
 /**
  * Overwriting default writer because I want to use Flow storage methods so
  * the updates also affect cache, not just DB.
  *
  * @param array[] $updates
  */
 public function write(array $updates)
 {
     /*
      * from:
      * array(
      *     'primaryKey' => array( 'workflow_id' => $id ),
      *     'updates' => array( 'workflow_last_update_timestamp' => $timestamp ),
      * )
      * to:
      * array( $id => $timestamp );
      */
     $timestamps = array_combine($this->arrayColumn($this->arrayColumn($updates, 'primaryKey'), 'workflow_id'), $this->arrayColumn($this->arrayColumn($updates, 'changes'), 'workflow_last_update_timestamp'));
     /** @var UUID[] $uuids */
     $uuids = array_map(array('Flow\\Model\\UUID', 'create'), array_keys($timestamps));
     /** @var Workflow[] $workflows */
     $workflows = $this->storage->getMulti('Workflow', $uuids);
     foreach ($workflows as $workflow) {
         $timestamp = $timestamps[$workflow->getId()->getBinary()];
         $workflow->updateLastModified(UUID::getComparisonUUID($timestamp));
     }
     $this->storage->multiPut($workflows);
     // prevent memory from filling up
     $this->storage->clear();
     wfWaitForSlaves(false, false, $this->clusterName);
 }
 /**
  * Retrieves references that are already stored in the database for a given revision
  *
  * @param  string $revType The value returned from Revision::getRevisionType() for the revision.
  * @param  UUID $objectId   The revision's Object ID.
  * @return Reference[] Array of References.
  */
 public function getExistingReferences($revType, UUID $objectId)
 {
     $prevWikiReferences = $this->storage->find('WikiReference', array('ref_src_object_type' => $revType, 'ref_src_object_id' => $objectId));
     $prevUrlReferences = $this->storage->find('URLReference', array('ref_src_object_type' => $revType, 'ref_src_object_id' => $objectId));
     return array_merge((array) $prevWikiReferences, (array) $prevUrlReferences);
 }
 /**
  * Gets the top revision of an item by ID
  *
  * @param  string $type The type of the object to return (e.g. PostRevision).
  * @param  UUID   $id   The ID (e.g. post ID, topic ID, etc)
  * @return object|false The top revision of the requested object, or false if not found.
  */
 public function getTopRevision($type, UUID $id)
 {
     $result = $this->storage->find($type, array('rev_type_id' => $id), array('sort' => 'rev_id', 'order' => 'DESC', 'limit' => 1));
     if (count($result)) {
         return reset($result);
     } else {
         return false;
     }
 }