/**
  * 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;
     }
 }
 /**
  * Gets all the 'top' revisions within the topic, namely the posts and the
  * summary. These are used when a topic changes is visibility via moderation
  * to add or remove the relevant references.
  *
  * @param Workflow $workflow
  * @return AbstractRevision[]
  */
 protected function collectTopicRevisions(Workflow $workflow)
 {
     $found = $this->treeRepository->fetchSubtreeNodeList(array($workflow->getId()));
     $queries = array();
     foreach (reset($found) as $uuid) {
         $queries[] = array('rev_type_id' => $uuid);
     }
     $posts = $this->storage->findMulti('PostRevision', $queries, array('sort' => 'rev_id', 'order' => 'DESC', 'limit' => 1));
     // we also need the most recent topic summary if it exists
     $summaries = $this->storage->find('PostSummary', array('rev_type_id' => $workflow->getId()), array('sort' => 'rev_id', 'order' => 'DESC', 'limit' => 1));
     $result = $summaries;
     // we have to unwrap the posts since we used findMulti, it returns
     // a separate result set for each query
     foreach ($posts as $found) {
         $result[] = reset($found);
     }
     return $result;
 }
 /**
  * @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;
 }
 /**
  * Entry point for batch loading metadata for a variety of revisions
  * into the internal cache.
  *
  * @param AbstractRevision[]|ResultWrapper $results
  */
 protected function loadMetadataBatch($results)
 {
     // Batch load data related to a list of revisions
     $postIds = array();
     $workflowIds = array();
     $revisions = array();
     $previousRevisionIds = array();
     $collectionIds = array();
     foreach ($results as $result) {
         if ($result instanceof PostRevision) {
             // If top-level, then just get the workflow.
             // Otherwise we need to find the root post.
             $id = $result->getPostId();
             $alpha = $id->getAlphadecimal();
             if ($result->isTopicTitle()) {
                 $workflowIds[] = $id;
             } else {
                 $postIds[$alpha] = $id;
             }
             $this->postCache[$alpha] = $result;
         } elseif ($result instanceof Header) {
             $workflowIds[] = $result->getWorkflowId();
         } elseif ($result instanceof PostSummary) {
             // This would be the post id for the summary
             $id = $result->getSummaryTargetId();
             $postIds[$id->getAlphadecimal()] = $id;
         }
         $revisions[$result->getRevisionId()->getAlphadecimal()] = $result;
         if ($this->needsPreviousRevision($result)) {
             $previousRevisionIds[get_class($result)][] = $result->getPrevRevisionId();
         }
         $collection = $result->getCollection();
         $collectionIds[get_class($result)][] = $collection->getId();
     }
     // map from post Id to the related root post id
     $rootPostIds = array_filter($this->treeRepository->findRoots($postIds));
     $rootPostRequests = array();
     foreach ($rootPostIds as $postId) {
         $rootPostRequests[] = array('rev_type_id' => $postId);
     }
     // these tree identity maps are required for determining where a reply goes when
     //
     // replying to a specific post.
     $identityMap = $this->treeRepository->fetchSubtreeIdentityMap(array_unique($rootPostIds, SORT_REGULAR));
     $rootPostResult = $this->storage->findMulti('PostRevision', $rootPostRequests, array('SORT' => 'rev_id', 'ORDER' => 'DESC', 'LIMIT' => 1));
     $rootPosts = array();
     if (count($rootPostResult) > 0) {
         foreach ($rootPostResult as $found) {
             $root = reset($found);
             $rootPosts[$root->getPostId()->getAlphadecimal()] = $root;
             $revisions[$root->getRevisionId()->getAlphadecimal()] = $root;
         }
     }
     // Workflow IDs are the same as root post IDs
     // So any post IDs that *are* root posts + found root post IDs + header workflow IDs
     // should cover the lot.
     $workflows = $this->storage->getMulti('Workflow', array_merge($rootPostIds, $workflowIds));
     $workflows = $workflows ?: array();
     // preload all requested previous revisions
     foreach ($previousRevisionIds as $revisionType => $ids) {
         // get rid of null-values (for original revisions, without previous revision)
         $ids = array_filter($ids);
         /** @var AbstractRevision[] $found */
         $found = $this->storage->getMulti($revisionType, $ids);
         foreach ($found as $rev) {
             $revisions[$rev->getRevisionId()->getAlphadecimal()] = $rev;
         }
     }
     // preload all current versions
     foreach ($collectionIds as $revisionType => $ids) {
         $queries = array();
         foreach ($ids as $uuid) {
             $queries[] = array('rev_type_id' => $uuid);
         }
         $found = $this->storage->findMulti($revisionType, $queries, array('sort' => 'rev_id', 'order' => 'DESC', 'limit' => 1));
         /** @var AbstractRevision[] $result */
         foreach ($found as $result) {
             $rev = reset($result);
             $this->currentRevisionsCache[$rev->getCollectionId()->getAlphadecimal()] = $rev->getRevisionId();
             $revisions[$rev->getRevisionId()->getAlphadecimal()] = $rev;
         }
     }
     $this->revisionCache = array_merge($this->revisionCache, $revisions);
     $this->postCache = array_merge($this->postCache, $rootPosts);
     $this->rootPostIdCache = array_merge($this->rootPostIdCache, $rootPostIds);
     $this->workflowCache = array_merge($this->workflowCache, $workflows);
     $this->identityMap = array_merge($this->identityMap, $identityMap);
 }