wikimedia/mediawiki-extensions-Wikibase

View on GitHub
client/includes/Specials/SpecialUnconnectedPages.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php

namespace Wikibase\Client\Specials;

use MediaWiki\Html\Html;
use MediaWiki\SpecialPage\QueryPage;
use MediaWiki\Title\NamespaceInfo;
use MediaWiki\Title\TitleFactory;
use Skin;
use Wikibase\Client\NamespaceChecker;
use Wikibase\Lib\Rdbms\ClientDomainDb;
use Wikibase\Lib\Rdbms\ClientDomainDbFactory;
use Wikimedia\Rdbms\FakeResultWrapper;
use Wikimedia\Rdbms\IConnectionProvider;
use Wikimedia\Rdbms\IResultWrapper;

/**
 * List client pages that are not connected to repository items.
 *
 * @license GPL-2.0-or-later
 * @author John Erling Blad < jeblad@gmail.com >
 * @author Amir Sarabadani < ladsgroup@gmail.com >
 * @author Daniel Kinzler
 */
class SpecialUnconnectedPages extends QueryPage {

    /**
     * @var int maximum supported offset
     */
    private const MAX_OFFSET = 10000;

    /** @var NamespaceInfo */
    private $namespaceInfo;

    /** @var TitleFactory */
    private $titleFactory;

    /** @var NamespaceChecker */
    private $namespaceChecker;

    /** @var ClientDomainDb */
    private $db;

    public function __construct(
        IConnectionProvider $dbProvider,
        NamespaceInfo $namespaceInfo,
        TitleFactory $titleFactory,
        ClientDomainDbFactory $db,
        NamespaceChecker $namespaceChecker
    ) {
        parent::__construct( 'UnconnectedPages' );
        $this->namespaceInfo = $namespaceInfo;
        $this->titleFactory = $titleFactory;
        $this->namespaceChecker = $namespaceChecker;
        $this->db = $db->newLocalDb();
        $this->setDatabaseProvider( $dbProvider );
    }

    /**
     * @see QueryPage::isSyndicated
     *
     * @return bool Always false because we do not want to build RSS/Atom feeds for this page.
     */
    public function isSyndicated() {
        return false;
    }

    /**
     * @see QueryPage::isCacheable
     *
     * @return bool Always false as our query can be parameterized (by namespace), and QueryPage
     *     doesn't support that.
     */
    public function isCacheable() {
        return false;
    }

    /**
     * Build conditionals for namespace
     */
    public function buildNamespaceConditionals(): array {
        $conds = [];

        $wbNamespaces = $this->namespaceChecker->getWikibaseNamespaces();
        $ns = $this->getRequest()->getIntOrNull( 'namespace' );

        if ( $ns !== null && in_array( $ns, $wbNamespaces ) ) {
            $conds['pp_sortkey'] = -$ns;
        } else {
            $conds['pp_sortkey'] = [];
            foreach ( $wbNamespaces as $wbNs ) {
                $conds['pp_sortkey'][] = -$wbNs;
            }
        }

        return $conds;
    }

    /**
     * @see QueryPage::getQueryInfo
     *
     * @return array[]
     */
    public function getQueryInfo() {
        return $this->db->connections()->getReadConnection()->newSelectQueryBuilder()
            ->select( [
                'value' => 'page_id',
                'namespace' => 'page_namespace',
                'title' => 'page_title',
            ] )
            ->from( 'page' )
            ->join( 'page_props', null, [
                'page_id = pp_page',
                'pp_propname' => 'unexpectedUnconnectedPage',
            ] )
            ->where( $this->buildNamespaceConditionals() )
            // sorting is determined by getOrderFields()
            ->getQueryInfo();
    }

    /**
     * @return string[]
     */
    protected function getOrderFields() {
        // Should make use of the "pp_propname_sortkey_page" index.
        return [ 'pp_sortkey', 'page_id' ];
    }

    protected function sortDescending(): bool {
        return true;
    }

    /**
     * @see QueryPage::reallyDoQuery
     *
     * @param int|bool $limit
     * @param int|bool $offset
     *
     * @return IResultWrapper
     */
    public function reallyDoQuery( $limit, $offset = false ) {
        if ( is_int( $offset ) && $offset > self::MAX_OFFSET ) {
            return new FakeResultWrapper( [] );
        }

        return parent::reallyDoQuery( $limit, $offset );
    }

    /**
     * @see QueryPage::formatResult
     *
     * @param Skin $skin
     * @param \stdClass $result
     *
     * @return string|bool
     */
    public function formatResult( $skin, $result ) {
        $title = $this->titleFactory->newFromID( $result->value );
        if ( $title === null ) {
            return false;
        }

        return $this->getLinkRenderer()->makeKnownLink( $title );
    }

    /**
     * @see QueryPage::getPageHeader
     *
     * @return string
     */
    public function getPageHeader() {
        $excludeNamespaces = array_diff(
            $this->namespaceInfo->getValidNamespaces(),
            $this->namespaceChecker->getWikibaseNamespaces()
        );

        $limit = $this->getRequest()->getIntOrNull( 'limit' );
        $ns = $this->getRequest()->getIntOrNull( 'namespace' );

        $titleInputHtml = '';
        $articlePath = $this->getConfig()->get( 'ArticlePath' );
        if ( strpos( $articlePath, '?' ) !== false ) {
            // Adopted from HTMLForm::getHiddenFields
            $titleInputHtml = Html::hidden( 'title', $this->getFullTitle()->getPrefixedText() ) . "\n";
        }

        return Html::openElement(
            'form',
            [
                'action' => $this->getPageTitle()->getLocalURL(),
            ]
        ) .
        $titleInputHtml .
        ( $limit === null ? '' : Html::hidden( 'limit', $limit ) ) .
        Html::namespaceSelector( [
            'selected' => $ns === null ? '' : $ns,
            'all' => '',
            'exclude' => $excludeNamespaces,
            'label' => $this->msg( 'namespace' )->text(),
        ] ) . ' ' .
        Html::submitButton(
            $this->msg( 'wikibase-unconnectedpages-submit' )->text(),
            []
        ) .
        Html::closeElement( 'form' );
    }

    /**
     * @see SpecialPage::getGroupName
     *
     * @return string
     */
    protected function getGroupName() {
        return 'maintenance';
    }

    /**
     * @see QueryPage::linkParameters
     *
     * @return array
     */
    public function linkParameters() {
        return [
            'namespace' => $this->getRequest()->getIntOrNull( 'namespace' ),
        ];
    }

}