Use American English spelling for behavior
[lhc/web/wiklou.git] / includes / site / MediaWikiSite.php
1 <?php
2 /**
3 * Class representing a MediaWiki site.
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 Site
22 * @license GNU GPL v2+
23 * @author John Erling Blad < jeblad@gmail.com >
24 * @author Daniel Kinzler
25 * @author Jeroen De Dauw < jeroendedauw@gmail.com >
26 */
27
28 /**
29 * Class representing a MediaWiki site.
30 *
31 * @since 1.21
32 *
33 * @ingroup Site
34 */
35 class MediaWikiSite extends Site {
36
37 const PATH_FILE = 'file_path';
38 const PATH_PAGE = 'page_path';
39
40 /**
41 * @since 1.21
42 * @deprecated Just use the constructor or the factory Site::newForType
43 *
44 * @param integer $globalId
45 *
46 * @return MediaWikiSite
47 */
48 public static function newFromGlobalId( $globalId ) {
49 $site = new static();
50 $site->setGlobalId( $globalId );
51 return $site;
52 }
53
54 /**
55 * Constructor.
56 *
57 * @since 1.21
58 *
59 * @param string $type
60 */
61 public function __construct( $type = self::TYPE_MEDIAWIKI ) {
62 parent::__construct( $type );
63 }
64
65 /**
66 * Returns the database form of the given title.
67 *
68 * @since 1.21
69 *
70 * @param String $title the target page's title, in normalized form.
71 *
72 * @return String
73 */
74 public function toDBKey( $title ) {
75 return str_replace( ' ', '_', $title );
76 }
77
78 /**
79 * Returns the normalized form of the given page title, using the normalization rules of the given site.
80 * If the given title is a redirect, the redirect weill be resolved and the redirect target is returned.
81 *
82 * @note : This actually makes an API request to the remote site, so beware that this function is slow and depends
83 * on an external service.
84 *
85 * @note : If MW_PHPUNIT_TEST is defined, the call to the external site is skipped, and the title
86 * is normalized using the local normalization rules as implemented by the Title class.
87 *
88 * @see Site::normalizePageName
89 *
90 * @since 1.21
91 *
92 * @param string $pageName
93 *
94 * @return string
95 * @throws MWException
96 */
97 public function normalizePageName( $pageName ) {
98
99 // Check if we have strings as arguments.
100 if ( !is_string( $pageName ) ) {
101 throw new MWException( '$pageName must be a string' );
102 }
103
104 // Go on call the external site
105 if ( defined( 'MW_PHPUNIT_TEST' ) ) {
106 // If the code is under test, don't call out to other sites, just normalize locally.
107 // Note: this may cause results to be inconsistent with the actual normalization used by the respective remote site!
108
109 $t = Title::newFromText( $pageName );
110 return $t->getPrefixedText();
111 } else {
112
113 // Make sure the string is normalized into NFC (due to the bug 40017)
114 // but do nothing to the whitespaces, that should work appropriately.
115 // @see https://bugzilla.wikimedia.org/show_bug.cgi?id=40017
116 $pageName = UtfNormal::cleanUp( $pageName );
117
118 // Build the args for the specific call
119 $args = array(
120 'action' => 'query',
121 'prop' => 'info',
122 'redirects' => true,
123 'converttitles' => true,
124 'format' => 'json',
125 'titles' => $pageName,
126 //@todo: options for maxlag and maxage
127 // Note that maxlag will lead to a long delay before a reply is made,
128 // but that maxage can avoid the extreme delay. On the other hand
129 // maxage could be nice to use anyhow as it stops unnecessary requests.
130 // Also consider smaxage if maxage is used.
131 );
132
133 $url = $this->getFileUrl( 'api.php' ) . '?' . wfArrayToCgi( $args );
134
135 // Go on call the external site
136 //@todo: we need a good way to specify a timeout here.
137 $ret = Http::get( $url );
138 }
139
140 if ( $ret === false ) {
141 wfDebugLog( "MediaWikiSite", "call to external site failed: $url" );
142 return false;
143 }
144
145 $data = FormatJson::decode( $ret, true );
146
147 if ( !is_array( $data ) ) {
148 wfDebugLog( "MediaWikiSite", "call to <$url> returned bad json: " . $ret );
149 return false;
150 }
151
152 $page = static::extractPageRecord( $data, $pageName );
153
154 if ( isset( $page['missing'] ) ) {
155 wfDebugLog( "MediaWikiSite", "call to <$url> returned a marker for a missing page title! " . $ret );
156 return false;
157 }
158
159 if ( isset( $page['invalid'] ) ) {
160 wfDebugLog( "MediaWikiSite", "call to <$url> returned a marker for an invalid page title! " . $ret );
161 return false;
162 }
163
164 if ( !isset( $page['title'] ) ) {
165 wfDebugLog( "MediaWikiSite", "call to <$url> did not return a page title! " . $ret );
166 return false;
167 }
168
169 return $page['title'];
170 }
171
172
173 /**
174 * Get normalization record for a given page title from an API response.
175 *
176 * @since 1.21
177 *
178 * @param array $externalData A reply from the API on a external server.
179 * @param string $pageTitle Identifies the page at the external site, needing normalization.
180 *
181 * @return array|boolean a 'page' structure representing the page identified by $pageTitle.
182 */
183 private static function extractPageRecord( $externalData, $pageTitle ) {
184 // If there is a special case with only one returned page
185 // we can cheat, and only return
186 // the single page in the "pages" substructure.
187 if ( isset( $externalData['query']['pages'] ) ) {
188 $pages = array_values( $externalData['query']['pages'] );
189 if ( count( $pages) === 1 ) {
190 return $pages[0];
191 }
192 }
193 // This is only used during internal testing, as it is assumed
194 // a more optimal (and lossfree) storage.
195 // Make initial checks and return if prerequisites are not meet.
196 if ( !is_array( $externalData ) || !isset( $externalData['query'] ) ) {
197 return false;
198 }
199 // Loop over the tree different named structures, that otherwise are similar
200 $structs = array(
201 'normalized' => 'from',
202 'converted' => 'from',
203 'redirects' => 'from',
204 'pages' => 'title'
205 );
206 foreach ( $structs as $listId => $fieldId ) {
207 // Check if the substructure exist at all.
208 if ( !isset( $externalData['query'][$listId] ) ) {
209 continue;
210 }
211 // Filter the substructure down to what we actually are using.
212 $collectedHits = array_filter(
213 array_values( $externalData['query'][$listId] ),
214 function( $a ) use ( $fieldId, $pageTitle ) {
215 return $a[$fieldId] === $pageTitle;
216 }
217 );
218 // If still looping over normalization, conversion or redirects,
219 // then we need to keep the new page title for later rounds.
220 if ( $fieldId === 'from' && is_array( $collectedHits ) ) {
221 switch ( count( $collectedHits ) ) {
222 case 0:
223 break;
224 case 1:
225 $pageTitle = $collectedHits[0]['to'];
226 break;
227 default:
228 return false;
229 }
230 }
231 // If on the pages structure we should prepare for returning.
232 elseif ( $fieldId === 'title' && is_array( $collectedHits ) ) {
233 switch ( count( $collectedHits ) ) {
234 case 0:
235 return false;
236 case 1:
237 return array_shift( $collectedHits );
238 default:
239 return false;
240 }
241 }
242 }
243 // should never be here
244 return false;
245 }
246
247 /**
248 * @see Site::getLinkPathType
249 * Returns Site::PATH_PAGE
250 *
251 * @since 1.21
252 *
253 * @return string
254 */
255 public function getLinkPathType() {
256 return self::PATH_PAGE;
257 }
258
259 /**
260 * Returns the relative page path.
261 *
262 * @since 1.21
263 *
264 * @return string
265 */
266 public function getRelativePagePath() {
267 return parse_url( $this->getPath( self::PATH_PAGE ), PHP_URL_PATH );
268 }
269
270 /**
271 * Returns the relative file path.
272 *
273 * @since 1.21
274 *
275 * @return string
276 */
277 public function getRelativeFilePath() {
278 return parse_url( $this->getPath( self::PATH_FILE ), PHP_URL_PATH );
279 }
280
281 /**
282 * Sets the relative page path.
283 *
284 * @since 1.21
285 *
286 * @param string $path
287 */
288 public function setPagePath( $path ) {
289 $this->setPath( self::PATH_PAGE, $path );
290 }
291
292 /**
293 * Sets the relative file path.
294 *
295 * @since 1.21
296 *
297 * @param string $path
298 */
299 public function setFilePath( $path ) {
300 $this->setPath( self::PATH_FILE, $path );
301 }
302
303 /**
304 * @see Site::getPageUrl
305 *
306 * This implementation returns a URL constructed using the path returned by getLinkPath().
307 * In addition to the default behavior implemented by Site::getPageUrl(), this
308 * method converts the $pageName to DBKey-format by replacing spaces with underscores
309 * before using it in the URL.
310 *
311 * @since 1.21
312 *
313 * @param string|boolean $pageName Page name or false (default: false)
314 *
315 * @return string
316 */
317 public function getPageUrl( $pageName = false ) {
318 $url = $this->getLinkPath();
319
320 if ( $url === false ) {
321 return false;
322 }
323
324 if ( $pageName !== false ) {
325 $pageName = $this->toDBKey( trim( $pageName ) );
326 $url = str_replace( '$1', wfUrlencode( $pageName ), $url );
327 }
328
329 return $url;
330 }
331
332 /**
333 * Returns the full file path (ie site url + relative file path).
334 * The path should go at the $1 marker. If the $path
335 * argument is provided, the marker will be replaced by it's value.
336 *
337 * @since 1.21
338 *
339 * @param string|boolean $path
340 *
341 * @return string
342 */
343 public function getFileUrl( $path = false ) {
344 $filePath = $this->getPath( self::PATH_FILE );
345
346 if ( $filePath !== false ) {
347 $filePath = str_replace( '$1', $path, $filePath );
348 }
349
350 return $filePath;
351 }
352
353 }