1<?php 2/** 3 * Implements Special:PagesWithProp 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 * @since 1.21 21 * @file 22 * @ingroup SpecialPage 23 */ 24 25/** 26 * Special:PagesWithProp to search the page_props table 27 * @ingroup SpecialPage 28 * @since 1.21 29 */ 30class SpecialPagesWithProp extends QueryPage { 31 32 /** 33 * @var string|null 34 */ 35 private $propName = null; 36 37 /** 38 * @var string[]|null 39 */ 40 private $existingPropNames = null; 41 42 /** 43 * @var int|null 44 */ 45 private $ns; 46 47 /** 48 * @var bool 49 */ 50 private $reverse = false; 51 52 /** 53 * @var bool 54 */ 55 private $sortByValue = false; 56 57 public function __construct( $name = 'PagesWithProp' ) { 58 parent::__construct( $name ); 59 } 60 61 public function isCacheable() { 62 return false; 63 } 64 65 public function execute( $par ) { 66 $this->setHeaders(); 67 $this->outputHeader(); 68 $this->getOutput()->addModuleStyles( 'mediawiki.special' ); 69 70 $request = $this->getRequest(); 71 $propname = $request->getVal( 'propname', $par ); 72 $this->ns = $request->getIntOrNull( 'namespace' ); 73 $this->reverse = $request->getBool( 'reverse' ); 74 $this->sortByValue = $request->getBool( 'sortbyvalue' ); 75 76 $propnames = $this->getExistingPropNames(); 77 78 $fields = [ 79 'propname' => [ 80 'type' => 'combobox', 81 'name' => 'propname', 82 'options' => $propnames, 83 'default' => $propname, 84 'label-message' => 'pageswithprop-prop', 85 'required' => true, 86 ], 87 'namespace' => [ 88 'type' => 'namespaceselect', 89 'name' => 'namespace', 90 'label-message' => 'namespace', 91 'all' => '', 92 'default' => $this->ns, 93 ], 94 'reverse' => [ 95 'type' => 'check', 96 'name' => 'reverse', 97 'default' => $this->reverse, 98 'label-message' => 'pageswithprop-reverse', 99 'required' => false, 100 ], 101 'sortbyvalue' => [ 102 'type' => 'check', 103 'name' => 'sortbyvalue', 104 'default' => $this->sortByValue, 105 'label-message' => 'pageswithprop-sortbyvalue', 106 'required' => false, 107 ] 108 ]; 109 110 $context = new DerivativeContext( $this->getContext() ); 111 $context->setTitle( $this->getPageTitle() ); // Remove subpage 112 $form = HTMLForm::factory( 'ooui', $fields, $context ); 113 114 $form->setMethod( 'get' ); 115 $form->setSubmitCallback( [ $this, 'onSubmit' ] ); 116 $form->setWrapperLegendMsg( 'pageswithprop-legend' ); 117 $form->addHeaderText( $this->msg( 'pageswithprop-text' )->parseAsBlock() ); 118 $form->setSubmitTextMsg( 'pageswithprop-submit' ); 119 120 $form->prepareForm(); 121 $form->displayForm( false ); 122 if ( $propname !== '' && $propname !== null ) { 123 $form->trySubmit(); 124 } 125 } 126 127 public function onSubmit( $data, $form ) { 128 $this->propName = $data['propname']; 129 parent::execute( $data['propname'] ); 130 } 131 132 /** 133 * Return an array of subpages beginning with $search that this special page will accept. 134 * 135 * @param string $search Prefix to search for 136 * @param int $limit Maximum number of results to return 137 * @param int $offset Number of pages to skip 138 * @return string[] Matching subpages 139 */ 140 public function prefixSearchSubpages( $search, $limit, $offset ) { 141 $subpages = array_keys( $this->queryExistingProps( $limit, $offset ) ); 142 // We've already limited and offsetted, set to N and 0 respectively. 143 return self::prefixSearchArray( $search, count( $subpages ), $subpages, 0 ); 144 } 145 146 /** 147 * Disable RSS/Atom feeds 148 * @return bool 149 */ 150 public function isSyndicated() { 151 return false; 152 } 153 154 public function getQueryInfo() { 155 $query = [ 156 'tables' => [ 'page_props', 'page' ], 157 'fields' => [ 158 'page_id' => 'pp_page', 159 'page_namespace', 160 'page_title', 161 'page_len', 162 'page_is_redirect', 163 'page_latest', 164 'pp_value', 165 ], 166 'conds' => [ 167 'pp_propname' => $this->propName, 168 ], 169 'join_conds' => [ 170 'page' => [ 'JOIN', 'page_id = pp_page' ] 171 ], 172 'options' => [] 173 ]; 174 175 if ( $this->ns !== null ) { 176 $query['conds']['page_namespace'] = $this->ns; 177 } 178 179 return $query; 180 } 181 182 protected function getOrderFields() { 183 $sort = [ 'page_id' ]; 184 if ( $this->sortByValue ) { 185 array_unshift( $sort, 'pp_sortkey' ); 186 } 187 return $sort; 188 } 189 190 /** 191 * @return bool 192 */ 193 public function sortDescending() { 194 return !$this->reverse; 195 } 196 197 /** 198 * @param Skin $skin 199 * @param object $result Result row 200 * @return string 201 */ 202 public function formatResult( $skin, $result ) { 203 $title = Title::newFromRow( $result ); 204 $ret = $this->getLinkRenderer()->makeKnownLink( $title ); 205 if ( $result->pp_value !== '' ) { 206 // Do not show very long or binary values on the special page 207 $valueLength = strlen( $result->pp_value ); 208 $isBinary = strpos( $result->pp_value, "\0" ) !== false; 209 $isTooLong = $valueLength > 1024; 210 211 if ( $isBinary || $isTooLong ) { 212 $message = $this 213 ->msg( $isBinary ? 'pageswithprop-prophidden-binary' : 'pageswithprop-prophidden-long' ) 214 ->params( $this->getLanguage()->formatSize( $valueLength ) ); 215 216 $propValue = Html::element( 'span', [ 'class' => 'prop-value-hidden' ], $message->text() ); 217 } else { 218 $propValue = Html::element( 'span', [ 'class' => 'prop-value' ], $result->pp_value ); 219 } 220 221 $ret .= $this->msg( 'colon-separator' )->escaped() . $propValue; 222 } 223 224 return $ret; 225 } 226 227 public function getExistingPropNames() { 228 if ( $this->existingPropNames === null ) { 229 $this->existingPropNames = $this->queryExistingProps(); 230 } 231 return $this->existingPropNames; 232 } 233 234 protected function queryExistingProps( $limit = null, $offset = 0 ) { 235 $opts = [ 236 'DISTINCT', 'ORDER BY' => 'pp_propname' 237 ]; 238 if ( $limit ) { 239 $opts['LIMIT'] = $limit; 240 } 241 if ( $offset ) { 242 $opts['OFFSET'] = $offset; 243 } 244 245 $res = wfGetDB( DB_REPLICA )->select( 246 'page_props', 247 'pp_propname', 248 '', 249 __METHOD__, 250 $opts 251 ); 252 253 $propnames = []; 254 foreach ( $res as $row ) { 255 $propnames[$row->pp_propname] = $row->pp_propname; 256 } 257 258 return $propnames; 259 } 260 261 protected function getGroupName() { 262 return 'pages'; 263 } 264} 265