Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 110
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 / 110
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 / 26
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 HTMLForm;
28use MediaWiki\Html\Html;
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    /** @var string */
40    public $target = '';
41
42    /** @var IConnectionProvider */
43    private $dbProvider;
44
45    /** @var SearchEngineFactory */
46    private $searchEngineFactory;
47
48    /** @var CategoryTree */
49    public $tree = null;
50
51    /**
52     * @param IConnectionProvider $dbProvider
53     * @param SearchEngineFactory $searchEngineFactory
54     */
55    public function __construct(
56        IConnectionProvider $dbProvider,
57        SearchEngineFactory $searchEngineFactory
58    ) {
59        parent::__construct( 'CategoryTree' );
60        $this->dbProvider = $dbProvider;
61        $this->searchEngineFactory = $searchEngineFactory;
62    }
63
64    /**
65     * @param string $name
66     * @return mixed
67     */
68    private function getOption( $name ) {
69        if ( $this->tree ) {
70            return $this->tree->optionManager->getOption( $name );
71        } else {
72            return $this->getConfig()->get( 'CategoryTreeDefaultOptions' )[$name];
73        }
74    }
75
76    /**
77     * Main execution function
78     * @param string|null $par Parameters passed to the page
79     */
80    public function execute( $par ) {
81        $this->setHeaders();
82        $this->addHelpLink( 'Extension:CategoryTree' );
83        $request = $this->getRequest();
84        if ( $par ) {
85            $this->target = trim( $par );
86        } else {
87            $this->target = trim( $request->getText( 'target' ) );
88            if ( $this->target === '' ) {
89                $rootcategory = $this->msg( 'rootcategory' );
90                if ( $rootcategory->exists() ) {
91                    $this->target = $rootcategory->text();
92                }
93            }
94        }
95
96        $options = [];
97        $config = $this->getConfig();
98
99        # grab all known options from the request. Normalization is done by the CategoryTree class
100        $categoryTreeDefaultOptions = $config->get( 'CategoryTreeDefaultOptions' );
101        $categoryTreeSpecialPageOptions = $config->get( 'CategoryTreeSpecialPageOptions' );
102        foreach ( $categoryTreeDefaultOptions as $option => $default ) {
103            if ( isset( $categoryTreeSpecialPageOptions[$option] ) ) {
104                $default = $categoryTreeSpecialPageOptions[$option];
105            }
106
107            $options[$option] = $request->getVal( $option, $default );
108        }
109
110        $this->tree = new CategoryTree( $options, $config, $this->dbProvider, $this->getLinkRenderer() );
111
112        $this->getOutput()->addWikiMsg( 'categorytree-header' );
113
114        $this->executeInputForm();
115
116        if ( $this->target !== '' ) {
117            $this->executeCategoryTree();
118        }
119    }
120
121    /**
122     * Input form for entering a category
123     */
124    private function executeInputForm() {
125        $namespaces = $this->getRequest()->getRawVal( 'namespaces' );
126        // mode may be overriden by namespaces option
127        $mode = ( $namespaces === null ? $this->getOption( 'mode' ) : CategoryTreeMode::ALL );
128        if ( $mode === CategoryTreeMode::CATEGORIES ) {
129            $modeDefault = 'categories';
130        } elseif ( $mode === CategoryTreeMode::PAGES ) {
131            $modeDefault = 'pages';
132        } else {
133            $modeDefault = 'all';
134        }
135
136        $formDescriptor = [
137            'category' => [
138                'type' => 'title',
139                'name' => 'target',
140                'label-message' => 'categorytree-category',
141                'namespace' => NS_CATEGORY,
142                'default' => str_replace( '_', ' ', $this->target ),
143            ],
144
145            'mode' => [
146                'type' => 'select',
147                'name' => 'mode',
148                'label-message' => 'categorytree-mode-label',
149                'options-messages' => [
150                    'categorytree-mode-categories' => 'categories',
151                    'categorytree-mode-pages' => 'pages',
152                    'categorytree-mode-all' => 'all',
153                ],
154                'default' => $modeDefault,
155                'nodata' => true,
156            ],
157
158            'namespace' => [
159                'type' => 'namespaceselect',
160                'name' => 'namespaces',
161                'label-message' => 'namespace',
162                'all' => '',
163            ],
164        ];
165
166        HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
167            ->setWrapperLegendMsg( 'categorytree-legend' )
168            ->setSubmitTextMsg( 'categorytree-go' )
169            ->setMethod( 'get' )
170            // Strip subpage
171            ->setTitle( $this->getPageTitle() )
172            ->prepareForm()
173            ->displayForm( false );
174    }
175
176    /**
177     * Show the category tree
178     */
179    private function executeCategoryTree() {
180        $output = $this->getOutput();
181        CategoryTree::setHeaders( $output );
182
183        $title = CategoryTree::makeTitle( $this->target );
184        if ( !$title || !$title->getArticleID() ) {
185            $output->addHTML( Html::rawElement( 'div', [ 'class' => 'CategoryTreeNotice' ],
186                $this->msg( 'categorytree-not-found' )
187                    ->plaintextParams( $this->target )
188                    ->parse()
189            ) );
190            return;
191        }
192
193        $parents = $this->tree->renderParents( $title );
194        if ( $parents === '' ) {
195            $parents = $this->msg( 'categorytree-no-parent-categories' )->parse();
196        }
197
198        $output->addHTML( Html::rawElement( 'div', [ 'class' => 'CategoryTreeParents' ],
199            $this->msg( 'categorytree-parents' )->parse() .
200            $this->msg( 'colon-separator' )->escaped() .
201            $parents
202        ) );
203
204        $output->addHTML( Html::rawElement( 'div',
205            [
206                'class' => 'CategoryTreeResult CategoryTreeTag',
207                'data-ct-mode' => $this->tree->optionManager->getOption( 'mode' ),
208                'data-ct-options' => $this->tree->optionManager->getOptionsAsJsStructure(),
209            ],
210            $this->tree->renderNode( $title, 1 )
211        ) );
212    }
213
214    /**
215     * Return an array of subpages beginning with $search that this special page will accept.
216     *
217     * @param string $search Prefix to search for
218     * @param int $limit Maximum number of results to return (usually 10)
219     * @param int $offset Number of results to skip (usually 0)
220     * @return string[] Matching subpages
221     */
222    public function prefixSearchSubpages( $search, $limit, $offset ) {
223        $title = Title::newFromText( $search, NS_CATEGORY );
224        if ( $title && !$title->inNamespace( NS_CATEGORY ) ) {
225            // Someone searching for something like "Wikipedia:Foo"
226            $title = Title::makeTitleSafe( NS_CATEGORY, $search );
227        }
228        if ( !$title ) {
229            // No prefix suggestion outside of category namespace
230            return [];
231        }
232        $searchEngine = $this->searchEngineFactory->create();
233        $searchEngine->setLimitOffset( $limit, $offset );
234        // Autocomplete subpage the same as a normal search, but just for categories
235        $searchEngine->setNamespaces( [ NS_CATEGORY ] );
236        $result = $searchEngine->defaultPrefixSearch( $search );
237
238        return array_map( static function ( Title $t ) {
239            // Remove namespace in search suggestion
240            return $t->getText();
241        }, $result );
242    }
243
244    /**
245     * @inheritDoc
246     */
247    protected function getGroupName() {
248        return 'pages';
249    }
250
251}