Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialRecentChangesLinked
0.00% covered (danger)
0.00%
0 / 82
0.00% covered (danger)
0.00%
0 / 9
600
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 getDefaultOptions
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 parseParameters
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 modifyQuery
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
90
 setTopText
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getExtraOptions
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getTargetTitle
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 prefixSearchSubpages
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 outputNoResults
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\Specials;
8
9use MediaWiki\Html\FormOptions;
10use MediaWiki\Html\Html;
11use MediaWiki\Language\MessageParser;
12use MediaWiki\RecentChanges\ChangesListQuery\ChangesListQuery;
13use MediaWiki\RecentChanges\ChangesListQuery\ChangesListQueryFactory;
14use MediaWiki\RecentChanges\RecentChangeFactory;
15use MediaWiki\Search\SearchEngineFactory;
16use MediaWiki\Title\Title;
17use MediaWiki\User\Options\UserOptionsLookup;
18use MediaWiki\User\TempUser\TempUserConfig;
19use MediaWiki\User\UserIdentityUtils;
20use MediaWiki\Watchlist\WatchedItemStoreInterface;
21
22/**
23 * This is to display changes made to all articles linked in an article.
24 *
25 * @ingroup RecentChanges
26 * @ingroup SpecialPage
27 */
28class SpecialRecentChangesLinked extends SpecialRecentChanges {
29    /** @var bool|Title */
30    protected $rclTargetTitle;
31
32    private SearchEngineFactory $searchEngineFactory;
33
34    public function __construct(
35        WatchedItemStoreInterface $watchedItemStore,
36        MessageParser $messageParser,
37        UserOptionsLookup $userOptionsLookup,
38        SearchEngineFactory $searchEngineFactory,
39        UserIdentityUtils $userIdentityUtils,
40        TempUserConfig $tempUserConfig,
41        RecentChangeFactory $recentChangeFactory,
42        ChangesListQueryFactory $changesListQueryFactory,
43    ) {
44        parent::__construct(
45            $watchedItemStore,
46            $messageParser,
47            $userOptionsLookup,
48            $userIdentityUtils,
49            $tempUserConfig,
50            $recentChangeFactory,
51            $changesListQueryFactory,
52        );
53        $this->mName = 'Recentchangeslinked';
54        $this->searchEngineFactory = $searchEngineFactory;
55    }
56
57    /** @inheritDoc */
58    public function getDefaultOptions() {
59        $opts = parent::getDefaultOptions();
60        $opts->add( 'target', '' );
61        $opts->add( 'showlinkedto', false );
62
63        return $opts;
64    }
65
66    /** @inheritDoc */
67    public function parseParameters( $par, FormOptions $opts ) {
68        $opts['target'] = $par;
69    }
70
71    /**
72     * @inheritDoc
73     */
74    protected function modifyQuery( ChangesListQuery $query, FormOptions $opts ) {
75        $target = $opts['target'];
76        $showlinkedto = $opts['showlinkedto'];
77
78        if ( $target === '' ) {
79            $query->forceEmptySet();
80            return;
81        }
82        $outputPage = $this->getOutput();
83        $title = Title::newFromText( $target );
84        if ( !$title || $title->isExternal() ) {
85            $outputPage->addModuleStyles( 'mediawiki.codex.messagebox.styles' );
86            $outputPage->addHTML(
87                Html::errorBox( $this->msg( 'allpagesbadtitle' )->parse(), '', 'mw-recentchangeslinked-errorbox' )
88            );
89            $query->forceEmptySet();
90            return;
91        }
92
93        $outputPage->setPageTitleMsg(
94            $this->msg( 'recentchangeslinked-title' )->plaintextParams( $title->getPrefixedText() )
95        );
96
97        $ns = $title->getNamespace();
98        if ( $ns === NS_CATEGORY && !$showlinkedto ) {
99            // special handling for categories
100            // XXX: should try to make this less kludgy
101            $link_tables = [ 'categorylinks' ];
102            $showlinkedto = true;
103        } else {
104            // for now, always join on these tables; really should be configurable as in whatlinkshere
105            $link_tables = [ 'pagelinks', 'templatelinks' ];
106            // imagelinks only contains links to pages in NS_FILE
107            if ( $ns === NS_FILE || !$showlinkedto ) {
108                $link_tables[] = 'imagelinks';
109            }
110        }
111
112        $query->requireLink(
113            $showlinkedto ? ChangesListQuery::LINKS_TO : ChangesListQuery::LINKS_FROM,
114            $link_tables,
115            $title
116        );
117    }
118
119    public function setTopText( FormOptions $opts ) {
120        $target = $this->getTargetTitle();
121        if ( $target ) {
122            $this->getOutput()->addBacklinkSubtitle( $target );
123            $this->getSkin()->setRelevantTitle( $target );
124        }
125    }
126
127    /**
128     * Get options to be displayed in a form
129     *
130     * @param FormOptions $opts
131     * @return array
132     */
133    public function getExtraOptions( $opts ) {
134        $extraOpts = parent::getExtraOptions( $opts );
135
136        $opts->consumeValues( [ 'showlinkedto', 'target' ] );
137
138        $extraOpts['target'] = [ $this->msg( 'recentchangeslinked-page' )->escaped(),
139            Html::input( 'target', str_replace( '_', ' ', $opts['target'] ), 'text', [ 'size' => 40 ] ) . ' ' .
140            Html::check( 'showlinkedto', $opts['showlinkedto'], [ 'id' => 'showlinkedto' ] ) . ' ' .
141            Html::label( $this->msg( 'recentchangeslinked-to' )->text(), 'showlinkedto' ) ];
142
143        $this->addHelpLink( 'Help:Related changes' );
144        return $extraOpts;
145    }
146
147    /**
148     * @return Title
149     */
150    private function getTargetTitle() {
151        if ( $this->rclTargetTitle === null ) {
152            $opts = $this->getOptions();
153            if ( isset( $opts['target'] ) && $opts['target'] !== '' ) {
154                $this->rclTargetTitle = Title::newFromText( $opts['target'] );
155            } else {
156                $this->rclTargetTitle = false;
157            }
158        }
159
160        return $this->rclTargetTitle;
161    }
162
163    /**
164     * Return an array of subpages beginning with $search that this special page will accept.
165     *
166     * @param string $search Prefix to search for
167     * @param int $limit Maximum number of results to return (usually 10)
168     * @param int $offset Number of results to skip (usually 0)
169     * @return string[] Matching subpages
170     */
171    public function prefixSearchSubpages( $search, $limit, $offset ) {
172        return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );
173    }
174
175    protected function outputNoResults() {
176        $targetTitle = $this->getTargetTitle();
177        if ( $targetTitle === false ) {
178            $this->getOutput()->addHTML(
179                Html::rawElement(
180                    'div',
181                    [ 'class' => [ 'mw-changeslist-empty', 'mw-changeslist-notargetpage' ] ],
182                    $this->msg( 'recentchanges-notargetpage' )->parse()
183                )
184            );
185        } elseif ( !$targetTitle || $targetTitle->isExternal() ) {
186            $this->getOutput()->addHTML(
187                Html::rawElement(
188                    'div',
189                    [ 'class' => [ 'mw-changeslist-empty', 'mw-changeslist-invalidtargetpage' ] ],
190                    $this->msg( 'allpagesbadtitle' )->parse()
191                )
192            );
193        } else {
194            parent::outputNoResults();
195        }
196    }
197}
198
199/**
200 * Retain the old class name for backwards compatibility.
201 * @deprecated since 1.41
202 */
203class_alias( SpecialRecentChangesLinked::class, 'SpecialRecentChangesLinked' );