| Current File : /home/jvzmxxx/wiki1/extensions/Flow/includes/Repository/RootPostLoader.php |
<?php
namespace Flow\Repository;
use Flow\Data\ManagerGroup;
use Flow\Model\PostRevision;
use Flow\Model\UUID;
use Flow\Exception\InvalidDataException;
use FormatJson;
/**
* I'm pretty sure this will generally work for any subtree, not just the topic
* root. The problem is once you allow any subtree you need to handle the
* depth and root post setters better, they make the assumption the root provided
* is actually a root.
*/
class RootPostLoader {
/**
* @var ManagerGroup
*/
protected $storage;
/**
* @var TreeRepository
*/
protected $treeRepo;
/**
* @param ManagerGroup $storage
* @param TreeRepository $treeRepo
*/
public function __construct( ManagerGroup $storage, TreeRepository $treeRepo ) {
$this->storage = $storage;
$this->treeRepo = $treeRepo;
}
/**
* Retrieves a single post and the related topic title.
*
* @param UUID|string $postId The uid of the post being requested
* @return PostRevision[]|null[] associative array with 'root' and 'post' keys. Array
* values may be null if not found.
* @throws InvalidDataException
*/
public function getWithRoot( $postId ) {
$postId = UUID::create( $postId );
$rootId = $this->treeRepo->findRoot( $postId );
$found = $this->storage->findMulti(
'PostRevision',
array(
array( 'rev_type_id' => $postId ),
array( 'rev_type_id' => $rootId ),
),
array( 'sort' => 'rev_id', 'order' => 'DESC', 'limit' => 1 )
);
$res = array(
'post' => null,
'root' => null,
);
if ( !$found ) {
return $res;
}
foreach ( $found as $result ) {
// limit = 1 means single result
$post = reset( $result );
if ( $postId->equals( $post->getPostId() ) ) {
$res['post'] = $post;
} elseif( $rootId->equals( $post->getPostId() ) ) {
$res['root'] = $post;
} else {
throw new InvalidDataException( 'Unmatched: ' . $post->getPostId()->getAlphadecimal() );
}
}
// The above doesn't catch this condition
if ( $postId->equals( $rootId ) ) {
$res['root'] = $res['post'];
}
return $res;
}
/**
* @param UUID $topicId
* @return PostRevision
* @throws InvalidDataException
*/
public function get( $topicId ) {
$result = $this->getMulti( array( $topicId ) );
return reset( $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;
}
/**
* @param UUID[] $postIds
* @return UUID[] Map from alphadecimal id to UUID object
*/
protected function fetchRelatedPostIds( array $postIds ) {
// list of all posts descendant from the provided $postIds
$nodeList = $this->treeRepo->fetchSubtreeNodeList( $postIds );
// merge all the children from the various posts into one array
if ( !$nodeList ) {
// It should have returned at least $postIds
// TODO: log errors?
$res = $postIds;
} elseif( count( $nodeList ) === 1 ) {
$res = reset( $nodeList );
} else {
$res = call_user_func_array( 'array_merge', $nodeList );
}
$retval = array();
foreach ( $res as $id ) {
$retval[$id->getAlphadecimal()] = $id;
}
return $retval;
}
/**
* @return TreeRepository
*/
public function getTreeRepo() {
return $this->treeRepo;
}
}