1<?php 2 3use MediaWiki\MediaWikiServices; 4 5/** 6 * Represents a single site. 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License along 19 * with this program; if not, write to the Free Software Foundation, Inc., 20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 * http://www.gnu.org/copyleft/gpl.html 22 * 23 * @since 1.21 24 * 25 * @file 26 * @ingroup Site 27 * 28 * @license GPL-2.0-or-later 29 * @author Jeroen De Dauw < jeroendedauw@gmail.com > 30 */ 31class Site implements Serializable { 32 public const TYPE_UNKNOWN = 'unknown'; 33 public const TYPE_MEDIAWIKI = 'mediawiki'; 34 35 public const GROUP_NONE = 'none'; 36 37 public const ID_INTERWIKI = 'interwiki'; 38 public const ID_EQUIVALENT = 'equivalent'; 39 40 public const SOURCE_LOCAL = 'local'; 41 42 public const PATH_LINK = 'link'; 43 44 /** 45 * A version ID that identifies the serialization structure used by getSerializationData() 46 * and unserialize(). This is useful for constructing cache keys in cases where the cache relies 47 * on serialization for storing the SiteList. 48 * 49 * @var string A string uniquely identifying the version of the serialization structure. 50 */ 51 public const SERIAL_VERSION_ID = '2013-01-23'; 52 53 /** 54 * @since 1.21 55 * 56 * @var string|null 57 */ 58 protected $globalId = null; 59 60 /** 61 * @since 1.21 62 * 63 * @var string 64 */ 65 protected $type = self::TYPE_UNKNOWN; 66 67 /** 68 * @since 1.21 69 * 70 * @var string 71 */ 72 protected $group = self::GROUP_NONE; 73 74 /** 75 * @since 1.21 76 * 77 * @var string 78 */ 79 protected $source = self::SOURCE_LOCAL; 80 81 /** 82 * @since 1.21 83 * 84 * @var string|null 85 */ 86 protected $languageCode = null; 87 88 /** 89 * Holds the local ids for this site. 90 * local id type => [ ids for this type (strings) ] 91 * 92 * @since 1.21 93 * 94 * @var array[]|false 95 */ 96 protected $localIds = []; 97 98 /** 99 * @since 1.21 100 * 101 * @var array 102 */ 103 protected $extraData = []; 104 105 /** 106 * @since 1.21 107 * 108 * @var array 109 */ 110 protected $extraConfig = []; 111 112 /** 113 * @since 1.21 114 * 115 * @var bool 116 */ 117 protected $forward = false; 118 119 /** 120 * @since 1.21 121 * 122 * @var int|null 123 */ 124 protected $internalId = null; 125 126 /** 127 * @since 1.21 128 * 129 * @param string $type 130 */ 131 public function __construct( $type = self::TYPE_UNKNOWN ) { 132 $this->type = $type; 133 } 134 135 /** 136 * Returns the global site identifier (ie enwiktionary). 137 * 138 * @since 1.21 139 * 140 * @return string|null 141 */ 142 public function getGlobalId() { 143 return $this->globalId; 144 } 145 146 /** 147 * Sets the global site identifier (ie enwiktionary). 148 * 149 * @since 1.21 150 * 151 * @param string|null $globalId 152 * 153 * @throws MWException 154 */ 155 public function setGlobalId( $globalId ) { 156 if ( $globalId !== null && !is_string( $globalId ) ) { 157 throw new MWException( '$globalId needs to be string or null' ); 158 } 159 160 $this->globalId = $globalId; 161 } 162 163 /** 164 * Returns the type of the site (ie mediawiki). 165 * 166 * @since 1.21 167 * 168 * @return string 169 */ 170 public function getType() { 171 return $this->type; 172 } 173 174 /** 175 * Gets the group of the site (ie wikipedia). 176 * 177 * @since 1.21 178 * 179 * @return string 180 */ 181 public function getGroup() { 182 return $this->group; 183 } 184 185 /** 186 * Sets the group of the site (ie wikipedia). 187 * 188 * @since 1.21 189 * 190 * @param string $group 191 * 192 * @throws MWException 193 */ 194 public function setGroup( $group ) { 195 if ( !is_string( $group ) ) { 196 throw new MWException( '$group needs to be a string' ); 197 } 198 199 $this->group = $group; 200 } 201 202 /** 203 * Returns the source of the site data (ie 'local', 'wikidata', 'my-magical-repo'). 204 * 205 * @since 1.21 206 * 207 * @return string 208 */ 209 public function getSource() { 210 return $this->source; 211 } 212 213 /** 214 * Sets the source of the site data (ie 'local', 'wikidata', 'my-magical-repo'). 215 * 216 * @since 1.21 217 * 218 * @param string $source 219 * 220 * @throws MWException 221 */ 222 public function setSource( $source ) { 223 if ( !is_string( $source ) ) { 224 throw new MWException( '$source needs to be a string' ); 225 } 226 227 $this->source = $source; 228 } 229 230 /** 231 * Gets if site.tld/path/key:pageTitle should forward users to the page on 232 * the actual site, where "key" is the local identifier. 233 * 234 * @since 1.21 235 * 236 * @return bool 237 */ 238 public function shouldForward() { 239 return $this->forward; 240 } 241 242 /** 243 * Sets if site.tld/path/key:pageTitle should forward users to the page on 244 * the actual site, where "key" is the local identifier. 245 * 246 * @since 1.21 247 * 248 * @param bool $shouldForward 249 * 250 * @throws MWException 251 */ 252 public function setForward( $shouldForward ) { 253 if ( !is_bool( $shouldForward ) ) { 254 throw new MWException( '$shouldForward needs to be a boolean' ); 255 } 256 257 $this->forward = $shouldForward; 258 } 259 260 /** 261 * Returns the domain of the site, ie en.wikipedia.org 262 * Or false if it's not known. 263 * 264 * @since 1.21 265 * 266 * @return string|null 267 */ 268 public function getDomain() { 269 $path = $this->getLinkPath(); 270 271 if ( $path === null ) { 272 return null; 273 } 274 275 return parse_url( $path, PHP_URL_HOST ); 276 } 277 278 /** 279 * Returns the protocol of the site. 280 * 281 * @since 1.21 282 * 283 * @throws MWException 284 * @return string 285 */ 286 public function getProtocol() { 287 $path = $this->getLinkPath(); 288 289 if ( $path === null ) { 290 return ''; 291 } 292 293 $protocol = parse_url( $path, PHP_URL_SCHEME ); 294 295 // Malformed URL 296 if ( $protocol === false ) { 297 throw new MWException( "failed to parse URL '$path'" ); 298 } 299 300 // No schema 301 if ( $protocol === null ) { 302 // Used for protocol relative URLs 303 $protocol = ''; 304 } 305 306 return $protocol; 307 } 308 309 /** 310 * Sets the path used to construct links with. 311 * Shall be equivalent to setPath( getLinkPathType(), $fullUrl ). 312 * 313 * @param string $fullUrl 314 * 315 * @since 1.21 316 * 317 * @throws MWException 318 */ 319 public function setLinkPath( $fullUrl ) { 320 $type = $this->getLinkPathType(); 321 322 if ( $type === null ) { 323 throw new MWException( "This Site does not support link paths." ); 324 } 325 326 $this->setPath( $type, $fullUrl ); 327 } 328 329 /** 330 * Returns the path used to construct links with or false if there is no such path. 331 * 332 * Shall be equivalent to getPath( getLinkPathType() ). 333 * 334 * @return string|null 335 */ 336 public function getLinkPath() { 337 $type = $this->getLinkPathType(); 338 return $type === null ? null : $this->getPath( $type ); 339 } 340 341 /** 342 * Returns the main path type, that is the type of the path that should 343 * generally be used to construct links to the target site. 344 * 345 * This default implementation returns Site::PATH_LINK as the default path 346 * type. Subclasses can override this to define a different default path 347 * type, or return false to disable site links. 348 * 349 * @since 1.21 350 * 351 * @return string|null 352 */ 353 public function getLinkPathType() { 354 return self::PATH_LINK; 355 } 356 357 /** 358 * Returns the full URL for the given page on the site. 359 * Or null if the needed information is not known. 360 * 361 * This generated URL is usually based upon the path returned by getLinkPath(), 362 * but this is not a requirement. 363 * 364 * This implementation returns a URL constructed using the path returned by getLinkPath(). 365 * 366 * @since 1.21 367 * 368 * @param bool|string $pageName 369 * 370 * @return string|null 371 */ 372 public function getPageUrl( $pageName = false ) { 373 $url = $this->getLinkPath(); 374 375 if ( $url === null ) { 376 return null; 377 } 378 379 if ( $pageName !== false ) { 380 $url = str_replace( '$1', rawurlencode( $pageName ), $url ); 381 } 382 383 return $url; 384 } 385 386 /** 387 * Attempt to normalize the page name in some fashion. 388 * May return false to indicate various kinds of failure. 389 * 390 * This implementation returns $pageName without changes. 391 * 392 * @see Site::normalizePageName 393 * 394 * @since 1.21 395 * 396 * @param string $pageName 397 * 398 * @return string|false 399 */ 400 public function normalizePageName( $pageName ) { 401 return $pageName; 402 } 403 404 /** 405 * Returns the type specific fields. 406 * 407 * @since 1.21 408 * 409 * @return array 410 */ 411 public function getExtraData() { 412 return $this->extraData; 413 } 414 415 /** 416 * Sets the type specific fields. 417 * 418 * @since 1.21 419 * 420 * @param array $extraData 421 */ 422 public function setExtraData( array $extraData ) { 423 $this->extraData = $extraData; 424 } 425 426 /** 427 * Returns the type specific config. 428 * 429 * @since 1.21 430 * 431 * @return array 432 */ 433 public function getExtraConfig() { 434 return $this->extraConfig; 435 } 436 437 /** 438 * Sets the type specific config. 439 * 440 * @since 1.21 441 * 442 * @param array $extraConfig 443 */ 444 public function setExtraConfig( array $extraConfig ) { 445 $this->extraConfig = $extraConfig; 446 } 447 448 /** 449 * Returns language code of the sites primary language. 450 * Or null if it's not known. 451 * 452 * @since 1.21 453 * 454 * @return string|null 455 */ 456 public function getLanguageCode() { 457 return $this->languageCode; 458 } 459 460 /** 461 * Sets language code of the sites primary language. 462 * 463 * @since 1.21 464 * 465 * @param string|null $languageCode 466 */ 467 public function setLanguageCode( $languageCode ) { 468 if ( $languageCode !== null 469 && !MediaWikiServices::getInstance() 470 ->getLanguageNameUtils() 471 ->isValidCode( $languageCode ) 472 ) { 473 throw new InvalidArgumentException( "$languageCode is not a valid language code." ); 474 } 475 $this->languageCode = $languageCode; 476 } 477 478 /** 479 * Returns the set internal identifier for the site. 480 * 481 * @since 1.21 482 * 483 * @return string|null 484 */ 485 public function getInternalId() { 486 return $this->internalId; 487 } 488 489 /** 490 * Sets the internal identifier for the site. 491 * This typically is a primary key in a db table. 492 * 493 * @since 1.21 494 * 495 * @param int|null $internalId 496 */ 497 public function setInternalId( $internalId = null ) { 498 $this->internalId = $internalId; 499 } 500 501 /** 502 * Adds a local identifier. 503 * 504 * @since 1.21 505 * 506 * @param string $type 507 * @param string $identifier 508 */ 509 public function addLocalId( $type, $identifier ) { 510 if ( $this->localIds === false ) { 511 $this->localIds = []; 512 } 513 514 if ( !array_key_exists( $type, $this->localIds ) ) { 515 $this->localIds[$type] = []; 516 } 517 518 if ( !in_array( $identifier, $this->localIds[$type] ) ) { 519 $this->localIds[$type][] = $identifier; 520 } 521 } 522 523 /** 524 * Adds an interwiki id to the site. 525 * 526 * @since 1.21 527 * 528 * @param string $identifier 529 */ 530 public function addInterwikiId( $identifier ) { 531 $this->addLocalId( self::ID_INTERWIKI, $identifier ); 532 } 533 534 /** 535 * Adds a navigation id to the site. 536 * 537 * @since 1.21 538 * 539 * @param string $identifier 540 */ 541 public function addNavigationId( $identifier ) { 542 $this->addLocalId( self::ID_EQUIVALENT, $identifier ); 543 } 544 545 /** 546 * Returns the interwiki link identifiers that can be used for this site. 547 * 548 * @since 1.21 549 * 550 * @return string[] 551 */ 552 public function getInterwikiIds() { 553 return array_key_exists( self::ID_INTERWIKI, $this->localIds ) 554 ? $this->localIds[self::ID_INTERWIKI] 555 : []; 556 } 557 558 /** 559 * Returns the equivalent link identifiers that can be used to make 560 * the site show up in interfaces such as the "language links" section. 561 * 562 * @since 1.21 563 * 564 * @return string[] 565 */ 566 public function getNavigationIds() { 567 return array_key_exists( self::ID_EQUIVALENT, $this->localIds ) 568 ? $this->localIds[self::ID_EQUIVALENT] : 569 []; 570 } 571 572 /** 573 * Returns all local ids 574 * 575 * @since 1.21 576 * 577 * @return array[] 578 */ 579 public function getLocalIds() { 580 return $this->localIds; 581 } 582 583 /** 584 * Sets the path used to construct links with. 585 * Shall be equivalent to setPath( getLinkPathType(), $fullUrl ). 586 * 587 * @since 1.21 588 * 589 * @param string $pathType 590 * @param string $fullUrl 591 * 592 * @throws MWException 593 */ 594 public function setPath( $pathType, $fullUrl ) { 595 if ( !is_string( $fullUrl ) ) { 596 throw new MWException( '$fullUrl needs to be a string' ); 597 } 598 599 if ( !array_key_exists( 'paths', $this->extraData ) ) { 600 $this->extraData['paths'] = []; 601 } 602 603 $this->extraData['paths'][$pathType] = $fullUrl; 604 } 605 606 /** 607 * Returns the path of the provided type or false if there is no such path. 608 * 609 * @since 1.21 610 * 611 * @param string $pathType 612 * 613 * @return string|null 614 */ 615 public function getPath( $pathType ) { 616 $paths = $this->getAllPaths(); 617 return array_key_exists( $pathType, $paths ) ? $paths[$pathType] : null; 618 } 619 620 /** 621 * Returns the paths as associative array. 622 * The keys are path types, the values are the path urls. 623 * 624 * @since 1.21 625 * 626 * @return string[] 627 */ 628 public function getAllPaths() { 629 return array_key_exists( 'paths', $this->extraData ) ? $this->extraData['paths'] : []; 630 } 631 632 /** 633 * Removes the path of the provided type if it's set. 634 * 635 * @since 1.21 636 * 637 * @param string $pathType 638 */ 639 public function removePath( $pathType ) { 640 if ( array_key_exists( 'paths', $this->extraData ) ) { 641 unset( $this->extraData['paths'][$pathType] ); 642 } 643 } 644 645 /** 646 * @since 1.21 647 * 648 * @param string $siteType 649 * 650 * @return Site 651 */ 652 public static function newForType( $siteType ) { 653 global $wgSiteTypes; 654 655 if ( array_key_exists( $siteType, $wgSiteTypes ) ) { 656 return new $wgSiteTypes[$siteType](); 657 } 658 659 return new Site(); 660 } 661 662 /** 663 * @see Serializable::serialize 664 * 665 * @since 1.21 666 * 667 * @return string 668 */ 669 public function serialize() { 670 $fields = [ 671 'globalid' => $this->globalId, 672 'type' => $this->type, 673 'group' => $this->group, 674 'source' => $this->source, 675 'language' => $this->languageCode, 676 'localids' => $this->localIds, 677 'config' => $this->extraConfig, 678 'data' => $this->extraData, 679 'forward' => $this->forward, 680 'internalid' => $this->internalId, 681 682 ]; 683 684 return serialize( $fields ); 685 } 686 687 /** 688 * @see Serializable::unserialize 689 * 690 * @since 1.21 691 * 692 * @param string $serialized 693 */ 694 public function unserialize( $serialized ) { 695 $fields = unserialize( $serialized ); 696 697 $this->__construct( $fields['type'] ); 698 699 $this->setGlobalId( $fields['globalid'] ); 700 $this->setGroup( $fields['group'] ); 701 $this->setSource( $fields['source'] ); 702 $this->setLanguageCode( $fields['language'] ); 703 $this->localIds = $fields['localids']; 704 $this->setExtraConfig( $fields['config'] ); 705 $this->setExtraData( $fields['data'] ); 706 $this->setForward( $fields['forward'] ); 707 $this->setInternalId( $fields['internalid'] ); 708 } 709} 710