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