| Current File : /home/jvzmxxx/wiki1/extensions/Flow/includes/Formatter/ContributionsQuery.php |
<?php
namespace Flow\Formatter;
use BagOStuff;
use ContribsPager;
use DeletedContribsPager;
use Flow\Container;
use Flow\Data\Storage\RevisionStorage;
use Flow\DbFactory;
use Flow\Data\ManagerGroup;
use Flow\Model\AbstractRevision;
use Flow\Model\UUID;
use Flow\Repository\TreeRepository;
use Flow\Exception\FlowException;
use Flow\FlowActions;
use ResultWrapper;
use User;
class ContributionsQuery extends AbstractQuery {
/**
* @var BagOStuff
*/
protected $cache;
/**
* @var DbFactory
*/
protected $dbFactory;
/**
* @var FlowActions
*/
protected $actions;
/**
* @param ManagerGroup $storage
* @param BagOStuff $cache
* @param TreeRepository $treeRepo
* @param DbFactory $dbFactory
* @param FlowActions $actions
*/
public function __construct(
ManagerGroup $storage,
TreeRepository $treeRepo,
BagOStuff $cache,
DbFactory $dbFactory,
FlowActions $actions )
{
parent::__construct( $storage, $treeRepo );
$this->cache = $cache;
$this->dbFactory = $dbFactory;
$this->actions = $actions;
}
/**
* @param ContribsPager|DeletedContribsPager $pager Object hooked into
* @param string $offset Index offset, inclusive
* @param int $limit Exact query limit
* @param bool $descending Query direction, false for ascending, true for descending
* @return FormatterRow[]
*/
public function getResults( $pager, $offset, $limit, $descending ) {
// build DB query conditions
$conditions = $this->buildConditions( $pager, $offset, $descending );
$types = array(
// revision class => block type
'PostRevision' => 'topic',
'Header' => 'header',
'PostSummary' => 'topicsummary'
);
$results = array();
foreach ( $types as $revisionClass => $blockType ) {
// query DB for requested revisions
$rows = $this->queryRevisions( $conditions, $limit, $revisionClass );
if ( !$rows ) {
continue;
}
// turn DB data into revision objects
$revisions = $this->loadRevisions( $rows, $revisionClass );
$this->loadMetadataBatch( $revisions );
foreach ( $revisions as $revision ) {
try {
if ( $this->excludeFromContributions( $revision ) ) {
continue;
}
$result = $pager instanceof ContribsPager ? new ContributionsRow : new DeletedContributionsRow;
$result = $this->buildResult( $revision, $pager->getIndexField(), $result );
$deleted = $result->currentRevision->isDeleted() || $result->workflow->isDeleted();
if (
$result instanceof ContributionsRow &&
( $deleted || $result->currentRevision->isSuppressed() )
) {
// don't show deleted or suppressed entries in Special:Contributions
continue;
}
if ( $result instanceof DeletedContributionsRow && !$deleted ) {
// only show deleted entries in Special:DeletedContributions
continue;
}
$results[] = $result;
} catch ( FlowException $e ) {
\MWExceptionHandler::logException( $e );
}
}
}
return $results;
}
/**
* @param AbstractRevision $revision
* @return bool
*/
private function excludeFromContributions( AbstractRevision $revision ) {
return (bool) $this->actions->getValue( $revision->getChangeType(), 'exclude_from_contributions' );
}
/**
* @param ContribsPager|DeletedContribsPager $pager Object hooked into
* @param string $offset Index offset, inclusive
* @param bool $descending Query direction, false for ascending, true for descending
* @return array Query conditions
*/
protected function buildConditions( $pager, $offset, $descending ) {
$conditions = array();
// Work out user condition
if ( property_exists( $pager, 'contribs' ) && $pager->contribs == 'newbie' ) {
list( $minUserId, $excludeUserIds ) = $this->getNewbieConditionInfo( $pager );
$conditions['rev_user_wiki'] = wfWikiID();
$conditions[] = 'rev_user_id > '. (int)$minUserId;
if ( $excludeUserIds ) {
// better safe than sorry - make sure everything's an int
$excludeUserIds = array_map( 'intval', $excludeUserIds );
$conditions[] = 'rev_user_id NOT IN ( ' . implode( ',', $excludeUserIds ) .' )';
$conditions['rev_user_ip'] = null;
}
} else {
$uid = User::idFromName( $pager->target );
if ( $uid ) {
$conditions['rev_user_id'] = $uid;
$conditions['rev_user_ip'] = null;
$conditions['rev_user_wiki'] = wfWikiID();
} else {
$conditions['rev_user_id'] = 0;
$conditions['rev_user_ip'] = $pager->target;
$conditions['rev_user_wiki'] = wfWikiID();
}
}
// Make offset parameter.
if ( $offset ) {
$dbr = $this->dbFactory->getDB( DB_SLAVE );
$offsetUUID = UUID::getComparisonUUID( $offset );
$direction = $descending ? '>' : '<';
$conditions[] = "rev_id $direction " . $dbr->addQuotes( $offsetUUID->getBinary() );
}
// Find only within requested wiki/namespace
$conditions['workflow_wiki'] = wfWikiID();
if ( $pager->namespace !== '' ) {
$conditions['workflow_namespace'] = $pager->namespace;
}
return $conditions;
}
/**
* @param array $conditions
* @param int $limit
* @param string $revisionClass Storage type (e.g. "PostRevision", "Header")
* @return ResultWrapper|false false on failure
* @throws \MWException
*/
protected function queryRevisions( $conditions, $limit, $revisionClass ) {
$dbr = $this->dbFactory->getDB( DB_SLAVE );
switch ( $revisionClass ) {
case 'PostRevision':
return $dbr->select(
array(
'flow_revision', // revisions to find
'flow_tree_revision', // resolve to post id
'flow_tree_node', // resolve to root post (topic title)
'flow_workflow', // resolve to workflow, to test if in correct wiki/namespace
),
array( '*' ),
$conditions,
__METHOD__,
array(
'LIMIT' => $limit,
'ORDER BY' => 'rev_id DESC',
),
array(
'flow_tree_revision' => array(
'INNER JOIN',
array( 'tree_rev_id = rev_id' )
),
'flow_tree_node' => array(
'INNER JOIN',
array(
'tree_descendant_id = tree_rev_descendant_id',
// the one with max tree_depth will be root,
// which will have the matching workflow id
)
),
'flow_workflow' => array(
'INNER JOIN',
array( 'workflow_id = tree_ancestor_id' )
),
)
);
break;
case 'Header':
return $dbr->select(
array( 'flow_revision', 'flow_workflow' ),
array( '*' ),
$conditions,
__METHOD__,
array(
'LIMIT' => $limit,
'ORDER BY' => 'rev_id DESC',
),
array(
'flow_workflow' => array(
'INNER JOIN',
array( 'workflow_id = rev_type_id' , 'rev_type' => 'header' )
),
)
);
break;
case 'PostSummary':
return $dbr->select(
array( 'flow_revision', 'flow_tree_node', 'flow_workflow' ),
array( '*' ),
$conditions,
__METHOD__,
array(
'LIMIT' => $limit,
'ORDER BY' => 'rev_id DESC',
),
array(
'flow_tree_node' => array(
'INNER JOIN',
array( 'tree_descendant_id = rev_type_id', 'rev_type' => 'post-summary' )
),
'flow_workflow' => array(
'INNER JOIN',
array( 'workflow_id = tree_ancestor_id' )
)
)
);
break;
default:
throw new \MWException( 'Unsupported revision type ' . $revisionClass );
break;
}
}
/**
* Turns DB data into revision objects.
*
* @param ResultWrapper $rows
* @param string $revisionClass Class of revision object to build: PostRevision|Header
* @return array
*/
protected function loadRevisions( ResultWrapper $rows, $revisionClass ) {
$revisions = array();
foreach ( $rows as $row ) {
$revisions[UUID::create( $row->rev_id )->getAlphadecimal()] = (array) $row;
}
// get content in external storage
$res = array( $revisions );
$res = RevisionStorage::mergeExternalContent( $res );
$revisions = reset( $res );
// we have all required data to build revision
$mapper = $this->storage->getStorage( $revisionClass )->getMapper();
$revisions = array_map( array( $mapper, 'fromStorageRow' ), $revisions );
// @todo: we may already be able to build workflowCache (and rootPostIdCache) from this DB data
return $revisions;
}
/**
* @param ContribsPager|DeletedContribsPager $pager
* @return array [minUserId, excludeUserIds]
*/
protected function getNewbieConditionInfo( $pager ) {
// unlike most of Flow, this one doesn't use wfForeignMemcKey; needs
// to be wiki-specific
$key = wfMemcKey( 'flow', '', 'maxUserId', Container::get( 'cache.version' ) );
$max = $this->cache->get( $key );
if ( $max === false ) {
// max user id not present in cache; fetch from db & save to cache for 1h
$max = (int) $pager->getDatabase()->selectField( 'user', 'MAX(user_id)', '', __METHOD__ );
$this->cache->set( $key, $max, 60 * 60 );
}
$minUserId = (int) ( $max - $max / 100 );
// exclude all users withing groups with bot permission
$excludeUserIds = array();
$groupsWithBotPermission = User::getGroupsWithPermission( 'bot' );
if ( count( $groupsWithBotPermission ) ) {
$rows = $pager->getDatabase()->select(
array( 'user', 'user_groups' ),
'user_id',
array(
'user_id > ' . $minUserId,
'ug_group' => $groupsWithBotPermission
),
__METHOD__,
array(),
array(
'user_groups' => array(
'INNER JOIN',
array( 'ug_user = user_id' )
)
)
);
$excludeUserIds = array();
foreach ( $rows as $row ) {
$excludeUserIds[] = $row->user_id;
}
}
return array( $minUserId, $excludeUserIds );
}
/**
* When retrieving revisions from DB, self::mergeExternalContent will be
* called to fetch the content. This could fail, resulting in the content
* being a 'false' value.
*
* {@inheritDoc}
*/
public function validate( array $row ) {
return !isset( $row['rev_content'] ) || $row['rev_content'] !== false;
}
}
class ContributionsRow extends FormatterRow {
public $rev_timestamp;
}
class DeletedContributionsRow extends FormatterRow {
public $ar_timestamp;
}