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