Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 109
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
CategoryTreePage
0.00% covered (danger)
0.00%
0 / 109
0.00% covered (danger)
0.00%
0 / 7
552
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getOption
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 execute
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
56
 executeInputForm
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 1
20
 executeCategoryTree
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
20
 prefixSearchSubpages
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * © 2006 Daniel Kinzler
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup Extensions
22 * @author Daniel Kinzler, brightbyte.de
23 */
24
25namespace MediaWiki\Extension\CategoryTree;
26
27use MediaWiki\Html\Html;
28use MediaWiki\HTMLForm\HTMLForm;
29use MediaWiki\SpecialPage\SpecialPage;
30use MediaWiki\Title\Title;
31use SearchEngineFactory;
32use Wikimedia\Rdbms\IConnectionProvider;
33
34/**
35 * Special page for the CategoryTree extension, an AJAX based gadget
36 * to display the category structure of a wiki
37 */
38class CategoryTreePage extends SpecialPage {
39    public string $target = '';
40    private IConnectionProvider $dbProvider;
41    private SearchEngineFactory $searchEngineFactory;
42    public ?CategoryTree $tree = null;
43
44    public function __construct(
45        IConnectionProvider $dbProvider,
46        SearchEngineFactory $searchEngineFactory
47    ) {
48        parent::__construct( 'CategoryTree' );
49        $this->dbProvider = $dbProvider;
50        $this->searchEngineFactory = $searchEngineFactory;
51    }
52
53    /**
54     * @param string $name
55     * @return mixed
56     */
57    private function getOption( string $name ) {
58        if ( $this->tree ) {
59            return $this->tree->optionManager->getOption( $name );
60        } else {
61            return $this->getConfig()->get( 'CategoryTreeDefaultOptions' )[$name];
62        }
63    }
64
65    /**
66     * Main execution function
67     * @param string|null $par Parameters passed to the page
68     */
69    public function execute( $par ) {
70        $this->setHeaders();
71        $this->addHelpLink( 'Extension:CategoryTree' );
72        $request = $this->getRequest();
73        if ( $par ) {
74            $this->target = trim( $par );
75        } else {
76            $this->target = trim( $request->getText( 'target' ) );
77            if ( $this->target === '' ) {
78                $rootcategory = $this->msg( 'rootcategory' );
79                if ( $rootcategory->exists() ) {
80                    $this->target = $rootcategory->text();
81                }
82            }
83        }
84
85        $options = [];
86        $config = $this->getConfig();
87
88        # grab all known options from the request. Normalization is done by the CategoryTree class
89        $categoryTreeDefaultOptions = $config->get( 'CategoryTreeDefaultOptions' );
90        $categoryTreeSpecialPageOptions = $config->get( 'CategoryTreeSpecialPageOptions' );
91        foreach ( $categoryTreeDefaultOptions as $option => $default ) {
92            if ( isset( $categoryTreeSpecialPageOptions[$option] ) ) {
93                $default = $categoryTreeSpecialPageOptions[$option];
94            }
95
96            $options[$option] = $request->getVal( $option, $default );
97        }
98
99        $this->tree = new CategoryTree( $options, $config, $this->dbProvider, $this->getLinkRenderer() );
100
101        $this->getOutput()->addWikiMsg( 'categorytree-header' );
102
103        $this->executeInputForm();
104
105        if ( $this->target !== '' ) {
106            $this->executeCategoryTree();
107        }
108    }
109
110    /**
111     * Input form for entering a category
112     */
113    private function executeInputForm() {
114        $namespaces = $this->getRequest()->getRawVal( 'namespaces' ) ?? '';
115        // mode may be overriden by namespaces option
116        $mode = ( $namespaces === '' ? $this->getOption( 'mode' ) : CategoryTreeMode::ALL );
117        if ( $mode === CategoryTreeMode::CATEGORIES ) {
118            $modeDefault = 'categories';
119        } elseif ( $mode === CategoryTreeMode::PAGES ) {
120            $modeDefault = 'pages';
121        } else {
122            $modeDefault = 'all';
123        }
124
125        $formDescriptor = [
126            'category' => [
127                'type' => 'title',
128                'name' => 'target',
129                'label-message' => 'categorytree-category',
130                'namespace' => NS_CATEGORY,
131                'default' => str_replace( '_', ' ', $this->target ),
132            ],
133
134            'mode' => [
135                'type' => 'select',
136                'name' => 'mode',
137                'label-message' => 'categorytree-mode-label',
138                'options-messages' => [
139                    'categorytree-mode-categories' => 'categories',
140                    'categorytree-mode-pages' => 'pages',
141                    'categorytree-mode-all' => 'all',
142                ],
143                'default' => $modeDefault,
144                'nodata' => true,
145            ],
146
147            'namespace' => [
148                'type' => 'namespaceselect',
149                'name' => 'namespaces',
150                'label-message' => 'namespace',
151                'all' => '',
152            ],
153        ];
154
155        HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
156            ->setWrapperLegendMsg( 'categorytree-legend' )
157            ->setSubmitTextMsg( 'categorytree-go' )
158            ->setMethod( 'get' )
159            // Strip subpage
160            ->setTitle( $this->getPageTitle() )
161            ->prepareForm()
162            ->displayForm( false );
163    }
164
165    /**
166     * Show the category tree
167     */
168    private function executeCategoryTree() {
169        $output = $this->getOutput();
170        CategoryTree::setHeaders( $output );
171
172        $title = CategoryTree::makeTitle( $this->target );
173        if ( !$title || !$title->getArticleID() ) {
174            $output->addHTML( Html::rawElement( 'div', [ 'class' => 'CategoryTreeNotice' ],
175                $this->msg( 'categorytree-not-found' )
176                    ->plaintextParams( $this->target )
177                    ->parse()
178            ) );
179            return;
180        }
181
182        $parents = $this->tree->renderParents( $title );
183        if ( $parents === '' ) {
184            $parents = $this->msg( 'categorytree-no-parent-categories' )->parse();
185        }
186
187        $output->addHTML( Html::rawElement( 'div', [ 'class' => 'CategoryTreeParents' ],
188            $this->msg( 'categorytree-parents' )->parse() .
189            $this->msg( 'colon-separator' )->escaped() .
190            $parents
191        ) );
192
193        $output->addHTML( Html::rawElement( 'div',
194            [
195                'class' => 'CategoryTreeResult CategoryTreeTag',
196                'data-ct-options' => $this->tree->optionManager->getOptionsAsJsStructure(),
197            ],
198            $this->tree->renderNode( $title, 1 )
199        ) );
200    }
201
202    /**
203     * Return an array of subpages beginning with $search that this special page will accept.
204     *
205     * @param string $search Prefix to search for
206     * @param int $limit Maximum number of results to return (usually 10)
207     * @param int $offset Number of results to skip (usually 0)
208     * @return string[] Matching subpages
209     */
210    public function prefixSearchSubpages( $search, $limit, $offset ) {
211        $title = Title::newFromText( $search, NS_CATEGORY );
212        if ( $title && !$title->inNamespace( NS_CATEGORY ) ) {
213            // Someone searching for something like "Wikipedia:Foo"
214            $title = Title::makeTitleSafe( NS_CATEGORY, $search );
215        }
216        if ( !$title ) {
217            // No prefix suggestion outside of category namespace
218            return [];
219        }
220        $searchEngine = $this->searchEngineFactory->create();
221        $searchEngine->setLimitOffset( $limit, $offset );
222        // Autocomplete subpage the same as a normal search, but just for categories
223        $searchEngine->setNamespaces( [ NS_CATEGORY ] );
224        $result = $searchEngine->defaultPrefixSearch( $search );
225
226        return array_map( static function ( Title $t ) {
227            // Remove namespace in search suggestion
228            return $t->getText();
229        }, $result );
230    }
231
232    /**
233     * @inheritDoc
234     */
235    protected function getGroupName() {
236        return 'pages';
237    }
238
239}