PageRenderTime 75ms CodeModel.GetById 34ms RepoModel.GetById 1ms app.codeStats 0ms

/language/mw-classes/Language.php

https://github.com/Hedonil/intuition
PHP | 3532 lines | 2030 code | 277 blank | 1225 comment | 461 complexity | da036510418cd8df254141b590cb91c5 MD5 | raw file
Possible License(s): GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Internationalisation code
  4. *
  5. * @file
  6. * @ingroup Language
  7. */
  8. /**
  9. * @defgroup Language Language
  10. */
  11. # HACK: Intuition
  12. # @{
  13. if ( !defined( 'INTUITION' ) ) {
  14. echo "This file is not a valid entry point\n";
  15. # @}
  16. exit( 1 );
  17. }
  18. # Read language names
  19. global $wgLanguageNames;
  20. require_once( dirname( __FILE__ ) . '/Names.php' );
  21. if ( function_exists( 'mb_strtoupper' ) ) {
  22. mb_internal_encoding( 'UTF-8' );
  23. }
  24. /**
  25. * a fake language converter
  26. *
  27. * @ingroup Language
  28. */
  29. class FakeConverter {
  30. var $mLang;
  31. function __construct( $langobj ) { $this->mLang = $langobj; }
  32. function autoConvertToAllVariants( $text ) { return array( $this->mLang->getCode() => $text ); }
  33. function convert( $t ) { return $t; }
  34. function convertTitle( $t ) { return $t->getPrefixedText(); }
  35. function getVariants() { return array( $this->mLang->getCode() ); }
  36. function getPreferredVariant() { return $this->mLang->getCode(); }
  37. function getDefaultVariant() { return $this->mLang->getCode(); }
  38. function getURLVariant() { return ''; }
  39. function getConvRuleTitle() { return false; }
  40. function findVariantLink( &$l, &$n, $ignoreOtherCond = false ) { }
  41. function getExtraHashOptions() { return ''; }
  42. function getParsedTitle() { return ''; }
  43. function markNoConversion( $text, $noParse = false ) { return $text; }
  44. function convertCategoryKey( $key ) { return $key; }
  45. function convertLinkToAllVariants( $text ) { return $this->autoConvertToAllVariants( $text ); }
  46. function armourMath( $text ) { return $text; }
  47. }
  48. /**
  49. * Internationalisation code
  50. * @ingroup Language
  51. */
  52. class Language {
  53. var $mConverter, $mVariants, $mCode, $mLoaded = false;
  54. var $mMagicExtensions = array(), $mMagicHookDone = false;
  55. var $mNamespaceIds, $namespaceNames, $namespaceAliases;
  56. var $dateFormatStrings = array();
  57. var $mExtendedSpecialPageAliases;
  58. /**
  59. * ReplacementArray object caches
  60. */
  61. var $transformData = array();
  62. /**
  63. * @var LocalisationCache
  64. */
  65. static public $dataCache;
  66. static public $mLangObjCache = array();
  67. static public $mWeekdayMsgs = array(
  68. 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
  69. 'friday', 'saturday'
  70. );
  71. static public $mWeekdayAbbrevMsgs = array(
  72. 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
  73. );
  74. static public $mMonthMsgs = array(
  75. 'january', 'february', 'march', 'april', 'may_long', 'june',
  76. 'july', 'august', 'september', 'october', 'november',
  77. 'december'
  78. );
  79. static public $mMonthGenMsgs = array(
  80. 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
  81. 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
  82. 'december-gen'
  83. );
  84. static public $mMonthAbbrevMsgs = array(
  85. 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
  86. 'sep', 'oct', 'nov', 'dec'
  87. );
  88. static public $mIranianCalendarMonthMsgs = array(
  89. 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
  90. 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
  91. 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
  92. 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
  93. );
  94. static public $mHebrewCalendarMonthMsgs = array(
  95. 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
  96. 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
  97. 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
  98. 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
  99. 'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
  100. );
  101. static public $mHebrewCalendarMonthGenMsgs = array(
  102. 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
  103. 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
  104. 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
  105. 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
  106. 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
  107. );
  108. static public $mHijriCalendarMonthMsgs = array(
  109. 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
  110. 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
  111. 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
  112. 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
  113. );
  114. /**
  115. * Get a cached language object for a given language code
  116. * @param $code String
  117. * @return Language
  118. */
  119. static function factory( $code ) {
  120. if ( !isset( self::$mLangObjCache[$code] ) ) {
  121. if ( count( self::$mLangObjCache ) > 10 ) {
  122. // Don't keep a billion objects around, that's stupid.
  123. self::$mLangObjCache = array();
  124. }
  125. self::$mLangObjCache[$code] = self::newFromCode( $code );
  126. }
  127. return self::$mLangObjCache[$code];
  128. }
  129. /**
  130. * Create a language object for a given language code
  131. * @param $code String
  132. * @return Language
  133. */
  134. protected static function newFromCode( $code ) {
  135. global $IP;
  136. static $recursionLevel = 0;
  137. // Protect against path traversal below
  138. if ( !Language::isValidCode( $code )
  139. || strcspn( $code, ":/\\\000" ) !== strlen( $code ) )
  140. {
  141. throw new MWException( "Invalid language code \"$code\"" );
  142. }
  143. if ( !Language::isValidBuiltInCode( $code ) ) {
  144. // It's not possible to customise this code with class files, so
  145. // just return a Language object. This is to support uselang= hacks.
  146. $lang = new Language;
  147. $lang->setCode( $code );
  148. return $lang;
  149. }
  150. if ( $code == 'en' ) {
  151. $class = 'Language';
  152. } else {
  153. $class = 'Language' . str_replace( '-', '_', ucfirst( $code ) );
  154. if ( !defined( 'MW_COMPILED' ) ) {
  155. // Preload base classes to work around APC/PHP5 bug
  156. if ( file_exists( "$IP/languages/classes/$class.deps.php" ) ) {
  157. include_once( "$IP/languages/classes/$class.deps.php" );
  158. }
  159. if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
  160. include_once( "$IP/languages/classes/$class.php" );
  161. }
  162. }
  163. }
  164. if ( $recursionLevel > 5 ) {
  165. throw new MWException( "Language fallback loop detected when creating class $class\n" );
  166. }
  167. if ( !MWInit::classExists( $class ) ) {
  168. $fallback = Language::getFallbackFor( $code );
  169. ++$recursionLevel;
  170. $lang = Language::newFromCode( $fallback );
  171. --$recursionLevel;
  172. $lang->setCode( $code );
  173. } else {
  174. $lang = new $class;
  175. }
  176. return $lang;
  177. }
  178. /**
  179. * Returns true if a language code string is of a valid form, whether or
  180. * not it exists. This includes codes which are used solely for
  181. * customisation via the MediaWiki namespace.
  182. *
  183. * @param $code string
  184. *
  185. * @return bool
  186. */
  187. public static function isValidCode( $code ) {
  188. return
  189. strcspn( $code, ":/\\\000" ) === strlen( $code )
  190. && !preg_match( Title::getTitleInvalidRegex(), $code );
  191. }
  192. /**
  193. * Returns true if a language code is of a valid form for the purposes of
  194. * internal customisation of MediaWiki, via Messages*.php.
  195. *
  196. * @param $code string
  197. *
  198. * @return bool
  199. */
  200. public static function isValidBuiltInCode( $code ) {
  201. return preg_match( '/^[a-z0-9-]*$/i', $code );
  202. }
  203. /**
  204. * Get the LocalisationCache instance
  205. *
  206. * @return LocalisationCache
  207. */
  208. public static function getLocalisationCache() {
  209. if ( is_null( self::$dataCache ) ) {
  210. global $wgLocalisationCacheConf;
  211. $class = $wgLocalisationCacheConf['class'];
  212. self::$dataCache = new $class( $wgLocalisationCacheConf );
  213. }
  214. return self::$dataCache;
  215. }
  216. function __construct() {
  217. // Set the code to the name of the descendant
  218. if ( get_class( $this ) == 'Language' ) {
  219. $this->mCode = 'en';
  220. } else {
  221. $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
  222. }
  223. }
  224. /**
  225. * Reduce memory usage
  226. */
  227. function __destruct() {
  228. foreach ( $this as $name => $value ) {
  229. unset( $this->$name );
  230. }
  231. }
  232. /**
  233. * Hook which will be called if this is the content language.
  234. * Descendants can use this to register hook functions or modify globals
  235. */
  236. function initContLang() { }
  237. /**
  238. * @return array|bool
  239. */
  240. function getFallbackLanguageCode() {
  241. if ( $this->mCode === 'en' ) {
  242. return false;
  243. } else {
  244. return self::$dataCache->getItem( $this->mCode, 'fallback' );
  245. }
  246. }
  247. /**
  248. * Exports $wgBookstoreListEn
  249. * @return array
  250. */
  251. function getBookstoreList() {
  252. return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
  253. }
  254. /**
  255. * @return array
  256. */
  257. function getNamespaces() {
  258. if ( is_null( $this->namespaceNames ) ) {
  259. global $wgMetaNamespace, $wgMetaNamespaceTalk, $wgExtraNamespaces;
  260. $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
  261. $validNamespaces = MWNamespace::getCanonicalNamespaces();
  262. $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces;
  263. $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
  264. if ( $wgMetaNamespaceTalk ) {
  265. $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
  266. } else {
  267. $talk = $this->namespaceNames[NS_PROJECT_TALK];
  268. $this->namespaceNames[NS_PROJECT_TALK] =
  269. $this->fixVariableInNamespace( $talk );
  270. }
  271. # Sometimes a language will be localised but not actually exist on this wiki.
  272. foreach( $this->namespaceNames as $key => $text ) {
  273. if ( !isset( $validNamespaces[$key] ) ) {
  274. unset( $this->namespaceNames[$key] );
  275. }
  276. }
  277. # The above mixing may leave namespaces out of canonical order.
  278. # Re-order by namespace ID number...
  279. ksort( $this->namespaceNames );
  280. }
  281. return $this->namespaceNames;
  282. }
  283. /**
  284. * A convenience function that returns the same thing as
  285. * getNamespaces() except with the array values changed to ' '
  286. * where it found '_', useful for producing output to be displayed
  287. * e.g. in <select> forms.
  288. *
  289. * @return array
  290. */
  291. function getFormattedNamespaces() {
  292. $ns = $this->getNamespaces();
  293. foreach ( $ns as $k => $v ) {
  294. $ns[$k] = strtr( $v, '_', ' ' );
  295. }
  296. return $ns;
  297. }
  298. /**
  299. * Get a namespace value by key
  300. * <code>
  301. * $mw_ns = $wgContLang->getNsText( NS_MEDIAWIKI );
  302. * echo $mw_ns; // prints 'MediaWiki'
  303. * </code>
  304. *
  305. * @param $index Int: the array key of the namespace to return
  306. * @return mixed, string if the namespace value exists, otherwise false
  307. */
  308. function getNsText( $index ) {
  309. $ns = $this->getNamespaces();
  310. return isset( $ns[$index] ) ? $ns[$index] : false;
  311. }
  312. /**
  313. * A convenience function that returns the same thing as
  314. * getNsText() except with '_' changed to ' ', useful for
  315. * producing output.
  316. *
  317. * @param $index string
  318. *
  319. * @return array
  320. */
  321. function getFormattedNsText( $index ) {
  322. $ns = $this->getNsText( $index );
  323. return strtr( $ns, '_', ' ' );
  324. }
  325. /**
  326. * Returns gender-dependent namespace alias if available.
  327. * @param $index Int: namespace index
  328. * @param $gender String: gender key (male, female... )
  329. * @return String
  330. * @since 1.18
  331. */
  332. function getGenderNsText( $index, $gender ) {
  333. $ns = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
  334. return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index );
  335. }
  336. /**
  337. * Whether this language makes distinguishes genders for example in
  338. * namespaces.
  339. * @return bool
  340. * @since 1.18
  341. */
  342. function needsGenderDistinction() {
  343. $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
  344. return count( $aliases ) > 0;
  345. }
  346. /**
  347. * Get a namespace key by value, case insensitive.
  348. * Only matches namespace names for the current language, not the
  349. * canonical ones defined in Namespace.php.
  350. *
  351. * @param $text String
  352. * @return mixed An integer if $text is a valid value otherwise false
  353. */
  354. function getLocalNsIndex( $text ) {
  355. $lctext = $this->lc( $text );
  356. $ids = $this->getNamespaceIds();
  357. return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
  358. }
  359. /**
  360. * @return array
  361. */
  362. function getNamespaceAliases() {
  363. if ( is_null( $this->namespaceAliases ) ) {
  364. $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
  365. if ( !$aliases ) {
  366. $aliases = array();
  367. } else {
  368. foreach ( $aliases as $name => $index ) {
  369. if ( $index === NS_PROJECT_TALK ) {
  370. unset( $aliases[$name] );
  371. $name = $this->fixVariableInNamespace( $name );
  372. $aliases[$name] = $index;
  373. }
  374. }
  375. }
  376. $genders = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
  377. foreach ( $genders as $index => $forms ) {
  378. foreach ( $forms as $alias ) {
  379. $aliases[$alias] = $index;
  380. }
  381. }
  382. $this->namespaceAliases = $aliases;
  383. }
  384. return $this->namespaceAliases;
  385. }
  386. /**
  387. * @return array
  388. */
  389. function getNamespaceIds() {
  390. if ( is_null( $this->mNamespaceIds ) ) {
  391. global $wgNamespaceAliases;
  392. # Put namespace names and aliases into a hashtable.
  393. # If this is too slow, then we should arrange it so that it is done
  394. # before caching. The catch is that at pre-cache time, the above
  395. # class-specific fixup hasn't been done.
  396. $this->mNamespaceIds = array();
  397. foreach ( $this->getNamespaces() as $index => $name ) {
  398. $this->mNamespaceIds[$this->lc( $name )] = $index;
  399. }
  400. foreach ( $this->getNamespaceAliases() as $name => $index ) {
  401. $this->mNamespaceIds[$this->lc( $name )] = $index;
  402. }
  403. if ( $wgNamespaceAliases ) {
  404. foreach ( $wgNamespaceAliases as $name => $index ) {
  405. $this->mNamespaceIds[$this->lc( $name )] = $index;
  406. }
  407. }
  408. }
  409. return $this->mNamespaceIds;
  410. }
  411. /**
  412. * Get a namespace key by value, case insensitive. Canonical namespace
  413. * names override custom ones defined for the current language.
  414. *
  415. * @param $text String
  416. * @return mixed An integer if $text is a valid value otherwise false
  417. */
  418. function getNsIndex( $text ) {
  419. $lctext = $this->lc( $text );
  420. if ( ( $ns = MWNamespace::getCanonicalIndex( $lctext ) ) !== null ) {
  421. return $ns;
  422. }
  423. $ids = $this->getNamespaceIds();
  424. return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
  425. }
  426. /**
  427. * short names for language variants used for language conversion links.
  428. *
  429. * @param $code String
  430. * @return string
  431. */
  432. function getVariantname( $code ) {
  433. return $this->getMessageFromDB( "variantname-$code" );
  434. }
  435. /**
  436. * @param $name string
  437. * @return string
  438. */
  439. function specialPage( $name ) {
  440. $aliases = $this->getSpecialPageAliases();
  441. if ( isset( $aliases[$name][0] ) ) {
  442. $name = $aliases[$name][0];
  443. }
  444. return $this->getNsText( NS_SPECIAL ) . ':' . $name;
  445. }
  446. /**
  447. * @return array
  448. */
  449. function getQuickbarSettings() {
  450. return array(
  451. $this->getMessage( 'qbsettings-none' ),
  452. $this->getMessage( 'qbsettings-fixedleft' ),
  453. $this->getMessage( 'qbsettings-fixedright' ),
  454. $this->getMessage( 'qbsettings-floatingleft' ),
  455. $this->getMessage( 'qbsettings-floatingright' )
  456. );
  457. }
  458. /**
  459. * @return array
  460. */
  461. function getDatePreferences() {
  462. return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
  463. }
  464. /**
  465. * @return array
  466. */
  467. function getDateFormats() {
  468. return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
  469. }
  470. /**
  471. * @return array|string
  472. */
  473. function getDefaultDateFormat() {
  474. $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
  475. if ( $df === 'dmy or mdy' ) {
  476. global $wgAmericanDates;
  477. return $wgAmericanDates ? 'mdy' : 'dmy';
  478. } else {
  479. return $df;
  480. }
  481. }
  482. /**
  483. * @return array
  484. */
  485. function getDatePreferenceMigrationMap() {
  486. return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
  487. }
  488. /**
  489. * @param $image
  490. * @return array|null
  491. */
  492. function getImageFile( $image ) {
  493. return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
  494. }
  495. /**
  496. * @return array
  497. */
  498. function getDefaultUserOptionOverrides() {
  499. return self::$dataCache->getItem( $this->mCode, 'defaultUserOptionOverrides' );
  500. }
  501. /**
  502. * @return array
  503. */
  504. function getExtraUserToggles() {
  505. return self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
  506. }
  507. /**
  508. * @param $tog
  509. * @return string
  510. */
  511. function getUserToggle( $tog ) {
  512. return $this->getMessageFromDB( "tog-$tog" );
  513. }
  514. /**
  515. * Get language names, indexed by code.
  516. * If $customisedOnly is true, only returns codes with a messages file
  517. *
  518. * @param $customisedOnly bool
  519. *
  520. * @return array
  521. */
  522. public static function getLanguageNames( $customisedOnly = false ) {
  523. global $wgExtraLanguageNames;
  524. static $coreLanguageNames;
  525. if ( $coreLanguageNames === null ) {
  526. include( MWInit::compiledPath( 'languages/Names.php' ) );
  527. }
  528. $allNames = $wgExtraLanguageNames + $coreLanguageNames;
  529. if ( !$customisedOnly ) {
  530. return $allNames;
  531. }
  532. global $IP;
  533. $names = array();
  534. $dir = opendir( "$IP/languages/messages" );
  535. while ( false !== ( $file = readdir( $dir ) ) ) {
  536. $code = self::getCodeFromFileName( $file, 'Messages' );
  537. if ( $code && isset( $allNames[$code] ) ) {
  538. $names[$code] = $allNames[$code];
  539. }
  540. }
  541. closedir( $dir );
  542. return $names;
  543. }
  544. /**
  545. * Get translated language names. This is done on best effort and
  546. * by default this is exactly the same as Language::getLanguageNames.
  547. * The CLDR extension provides translated names.
  548. * @param $code String Language code.
  549. * @return Array language code => language name
  550. * @since 1.18.0
  551. */
  552. public static function getTranslatedLanguageNames( $code ) {
  553. $names = array();
  554. wfRunHooks( 'LanguageGetTranslatedLanguageNames', array( &$names, $code ) );
  555. foreach ( self::getLanguageNames() as $code => $name ) {
  556. if ( !isset( $names[$code] ) ) $names[$code] = $name;
  557. }
  558. return $names;
  559. }
  560. /**
  561. * Get a message from the MediaWiki namespace.
  562. *
  563. * @param $msg String: message name
  564. * @return string
  565. */
  566. function getMessageFromDB( $msg ) {
  567. return wfMsgExt( $msg, array( 'parsemag', 'language' => $this ) );
  568. }
  569. /**
  570. * @param $code string
  571. * @return string
  572. */
  573. function getLanguageName( $code ) {
  574. $names = self::getLanguageNames();
  575. if ( !array_key_exists( $code, $names ) ) {
  576. return '';
  577. }
  578. return $names[$code];
  579. }
  580. /**
  581. * @param $key string
  582. * @return string
  583. */
  584. function getMonthName( $key ) {
  585. return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
  586. }
  587. /**
  588. * @return array
  589. */
  590. function getMonthNamesArray() {
  591. $monthNames = array( '' );
  592. for ( $i=1; $i < 13; $i++ ) {
  593. $monthNames[] = $this->getMonthName( $i );
  594. }
  595. return $monthNames;
  596. }
  597. /**
  598. * @param $key string
  599. * @return string
  600. */
  601. function getMonthNameGen( $key ) {
  602. return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
  603. }
  604. /**
  605. * @param $key string
  606. * @return string
  607. */
  608. function getMonthAbbreviation( $key ) {
  609. return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
  610. }
  611. /**
  612. * @return array
  613. */
  614. function getMonthAbbreviationsArray() {
  615. $monthNames = array( '' );
  616. for ( $i=1; $i < 13; $i++ ) {
  617. $monthNames[] = $this->getMonthAbbreviation( $i );
  618. }
  619. return $monthNames;
  620. }
  621. /**
  622. * @param $key string
  623. * @return string
  624. */
  625. function getWeekdayName( $key ) {
  626. return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
  627. }
  628. /**
  629. * @param $key string
  630. * @return string
  631. */
  632. function getWeekdayAbbreviation( $key ) {
  633. return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
  634. }
  635. /**
  636. * @param $key string
  637. * @return string
  638. */
  639. function getIranianCalendarMonthName( $key ) {
  640. return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
  641. }
  642. /**
  643. * @param $key string
  644. * @return string
  645. */
  646. function getHebrewCalendarMonthName( $key ) {
  647. return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
  648. }
  649. /**
  650. * @param $key string
  651. * @return string
  652. */
  653. function getHebrewCalendarMonthNameGen( $key ) {
  654. return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
  655. }
  656. /**
  657. * @param $key string
  658. * @return string
  659. */
  660. function getHijriCalendarMonthName( $key ) {
  661. return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
  662. }
  663. /**
  664. * Used by date() and time() to adjust the time output.
  665. *
  666. * @param $ts Int the time in date('YmdHis') format
  667. * @param $tz Mixed: adjust the time by this amount (default false, mean we
  668. * get user timecorrection setting)
  669. * @return int
  670. */
  671. function userAdjust( $ts, $tz = false ) {
  672. global $wgUser, $wgLocalTZoffset;
  673. if ( $tz === false ) {
  674. $tz = $wgUser->getOption( 'timecorrection' );
  675. }
  676. $data = explode( '|', $tz, 3 );
  677. if ( $data[0] == 'ZoneInfo' ) {
  678. wfSuppressWarnings();
  679. $userTZ = timezone_open( $data[2] );
  680. wfRestoreWarnings();
  681. if ( $userTZ !== false ) {
  682. $date = date_create( $ts, timezone_open( 'UTC' ) );
  683. date_timezone_set( $date, $userTZ );
  684. $date = date_format( $date, 'YmdHis' );
  685. return $date;
  686. }
  687. # Unrecognized timezone, default to 'Offset' with the stored offset.
  688. $data[0] = 'Offset';
  689. }
  690. $minDiff = 0;
  691. if ( $data[0] == 'System' || $tz == '' ) {
  692. # Global offset in minutes
  693. if ( isset( $wgLocalTZoffset ) ) {
  694. $minDiff = $wgLocalTZoffset;
  695. }
  696. } elseif ( $data[0] == 'Offset' ) {
  697. $minDiff = intval( $data[1] );
  698. } else {
  699. $data = explode( ':', $tz );
  700. if ( count( $data ) == 2 ) {
  701. $data[0] = intval( $data[0] );
  702. $data[1] = intval( $data[1] );
  703. $minDiff = abs( $data[0] ) * 60 + $data[1];
  704. if ( $data[0] < 0 ) {
  705. $minDiff = -$minDiff;
  706. }
  707. } else {
  708. $minDiff = intval( $data[0] ) * 60;
  709. }
  710. }
  711. # No difference ? Return time unchanged
  712. if ( 0 == $minDiff ) {
  713. return $ts;
  714. }
  715. wfSuppressWarnings(); // E_STRICT system time bitching
  716. # Generate an adjusted date; take advantage of the fact that mktime
  717. # will normalize out-of-range values so we don't have to split $minDiff
  718. # into hours and minutes.
  719. $t = mktime( (
  720. (int)substr( $ts, 8, 2 ) ), # Hours
  721. (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
  722. (int)substr( $ts, 12, 2 ), # Seconds
  723. (int)substr( $ts, 4, 2 ), # Month
  724. (int)substr( $ts, 6, 2 ), # Day
  725. (int)substr( $ts, 0, 4 ) ); # Year
  726. $date = date( 'YmdHis', $t );
  727. wfRestoreWarnings();
  728. return $date;
  729. }
  730. /**
  731. * This is a workalike of PHP's date() function, but with better
  732. * internationalisation, a reduced set of format characters, and a better
  733. * escaping format.
  734. *
  735. * Supported format characters are dDjlNwzWFmMntLoYyaAgGhHiscrU. See the
  736. * PHP manual for definitions. There are a number of extensions, which
  737. * start with "x":
  738. *
  739. * xn Do not translate digits of the next numeric format character
  740. * xN Toggle raw digit (xn) flag, stays set until explicitly unset
  741. * xr Use roman numerals for the next numeric format character
  742. * xh Use hebrew numerals for the next numeric format character
  743. * xx Literal x
  744. * xg Genitive month name
  745. *
  746. * xij j (day number) in Iranian calendar
  747. * xiF F (month name) in Iranian calendar
  748. * xin n (month number) in Iranian calendar
  749. * xiY Y (full year) in Iranian calendar
  750. *
  751. * xjj j (day number) in Hebrew calendar
  752. * xjF F (month name) in Hebrew calendar
  753. * xjt t (days in month) in Hebrew calendar
  754. * xjx xg (genitive month name) in Hebrew calendar
  755. * xjn n (month number) in Hebrew calendar
  756. * xjY Y (full year) in Hebrew calendar
  757. *
  758. * xmj j (day number) in Hijri calendar
  759. * xmF F (month name) in Hijri calendar
  760. * xmn n (month number) in Hijri calendar
  761. * xmY Y (full year) in Hijri calendar
  762. *
  763. * xkY Y (full year) in Thai solar calendar. Months and days are
  764. * identical to the Gregorian calendar
  765. * xoY Y (full year) in Minguo calendar or Juche year.
  766. * Months and days are identical to the
  767. * Gregorian calendar
  768. * xtY Y (full year) in Japanese nengo. Months and days are
  769. * identical to the Gregorian calendar
  770. *
  771. * Characters enclosed in double quotes will be considered literal (with
  772. * the quotes themselves removed). Unmatched quotes will be considered
  773. * literal quotes. Example:
  774. *
  775. * "The month is" F => The month is January
  776. * i's" => 20'11"
  777. *
  778. * Backslash escaping is also supported.
  779. *
  780. * Input timestamp is assumed to be pre-normalized to the desired local
  781. * time zone, if any.
  782. *
  783. * @param $format String
  784. * @param $ts String: 14-character timestamp
  785. * YYYYMMDDHHMMSS
  786. * 01234567890123
  787. * @todo handling of "o" format character for Iranian, Hebrew, Hijri & Thai?
  788. *
  789. * @return string
  790. */
  791. function sprintfDate( $format, $ts ) {
  792. $s = '';
  793. $raw = false;
  794. $roman = false;
  795. $hebrewNum = false;
  796. $unix = false;
  797. $rawToggle = false;
  798. $iranian = false;
  799. $hebrew = false;
  800. $hijri = false;
  801. $thai = false;
  802. $minguo = false;
  803. $tenno = false;
  804. for ( $p = 0; $p < strlen( $format ); $p++ ) {
  805. $num = false;
  806. $code = $format[$p];
  807. if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
  808. $code .= $format[++$p];
  809. }
  810. if ( ( $code === 'xi' || $code == 'xj' || $code == 'xk' || $code == 'xm' || $code == 'xo' || $code == 'xt' ) && $p < strlen( $format ) - 1 ) {
  811. $code .= $format[++$p];
  812. }
  813. switch ( $code ) {
  814. case 'xx':
  815. $s .= 'x';
  816. break;
  817. case 'xn':
  818. $raw = true;
  819. break;
  820. case 'xN':
  821. $rawToggle = !$rawToggle;
  822. break;
  823. case 'xr':
  824. $roman = true;
  825. break;
  826. case 'xh':
  827. $hebrewNum = true;
  828. break;
  829. case 'xg':
  830. $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
  831. break;
  832. case 'xjx':
  833. if ( !$hebrew ) $hebrew = self::tsToHebrew( $ts );
  834. $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
  835. break;
  836. case 'd':
  837. $num = substr( $ts, 6, 2 );
  838. break;
  839. case 'D':
  840. if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
  841. $s .= $this->getWeekdayAbbreviation( gmdate( 'w', $unix ) + 1 );
  842. break;
  843. case 'j':
  844. $num = intval( substr( $ts, 6, 2 ) );
  845. break;
  846. case 'xij':
  847. if ( !$iranian ) {
  848. $iranian = self::tsToIranian( $ts );
  849. }
  850. $num = $iranian[2];
  851. break;
  852. case 'xmj':
  853. if ( !$hijri ) {
  854. $hijri = self::tsToHijri( $ts );
  855. }
  856. $num = $hijri[2];
  857. break;
  858. case 'xjj':
  859. if ( !$hebrew ) {
  860. $hebrew = self::tsToHebrew( $ts );
  861. }
  862. $num = $hebrew[2];
  863. break;
  864. case 'l':
  865. if ( !$unix ) {
  866. $unix = wfTimestamp( TS_UNIX, $ts );
  867. }
  868. $s .= $this->getWeekdayName( gmdate( 'w', $unix ) + 1 );
  869. break;
  870. case 'N':
  871. if ( !$unix ) {
  872. $unix = wfTimestamp( TS_UNIX, $ts );
  873. }
  874. $w = gmdate( 'w', $unix );
  875. $num = $w ? $w : 7;
  876. break;
  877. case 'w':
  878. if ( !$unix ) {
  879. $unix = wfTimestamp( TS_UNIX, $ts );
  880. }
  881. $num = gmdate( 'w', $unix );
  882. break;
  883. case 'z':
  884. if ( !$unix ) {
  885. $unix = wfTimestamp( TS_UNIX, $ts );
  886. }
  887. $num = gmdate( 'z', $unix );
  888. break;
  889. case 'W':
  890. if ( !$unix ) {
  891. $unix = wfTimestamp( TS_UNIX, $ts );
  892. }
  893. $num = gmdate( 'W', $unix );
  894. break;
  895. case 'F':
  896. $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
  897. break;
  898. case 'xiF':
  899. if ( !$iranian ) {
  900. $iranian = self::tsToIranian( $ts );
  901. }
  902. $s .= $this->getIranianCalendarMonthName( $iranian[1] );
  903. break;
  904. case 'xmF':
  905. if ( !$hijri ) {
  906. $hijri = self::tsToHijri( $ts );
  907. }
  908. $s .= $this->getHijriCalendarMonthName( $hijri[1] );
  909. break;
  910. case 'xjF':
  911. if ( !$hebrew ) {
  912. $hebrew = self::tsToHebrew( $ts );
  913. }
  914. $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
  915. break;
  916. case 'm':
  917. $num = substr( $ts, 4, 2 );
  918. break;
  919. case 'M':
  920. $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
  921. break;
  922. case 'n':
  923. $num = intval( substr( $ts, 4, 2 ) );
  924. break;
  925. case 'xin':
  926. if ( !$iranian ) {
  927. $iranian = self::tsToIranian( $ts );
  928. }
  929. $num = $iranian[1];
  930. break;
  931. case 'xmn':
  932. if ( !$hijri ) {
  933. $hijri = self::tsToHijri ( $ts );
  934. }
  935. $num = $hijri[1];
  936. break;
  937. case 'xjn':
  938. if ( !$hebrew ) {
  939. $hebrew = self::tsToHebrew( $ts );
  940. }
  941. $num = $hebrew[1];
  942. break;
  943. case 't':
  944. if ( !$unix ) {
  945. $unix = wfTimestamp( TS_UNIX, $ts );
  946. }
  947. $num = gmdate( 't', $unix );
  948. break;
  949. case 'xjt':
  950. if ( !$hebrew ) {
  951. $hebrew = self::tsToHebrew( $ts );
  952. }
  953. $num = $hebrew[3];
  954. break;
  955. case 'L':
  956. if ( !$unix ) {
  957. $unix = wfTimestamp( TS_UNIX, $ts );
  958. }
  959. $num = gmdate( 'L', $unix );
  960. break;
  961. case 'o':
  962. if ( !$unix ) {
  963. $unix = wfTimestamp( TS_UNIX, $ts );
  964. }
  965. $num = date( 'o', $unix );
  966. break;
  967. case 'Y':
  968. $num = substr( $ts, 0, 4 );
  969. break;
  970. case 'xiY':
  971. if ( !$iranian ) {
  972. $iranian = self::tsToIranian( $ts );
  973. }
  974. $num = $iranian[0];
  975. break;
  976. case 'xmY':
  977. if ( !$hijri ) {
  978. $hijri = self::tsToHijri( $ts );
  979. }
  980. $num = $hijri[0];
  981. break;
  982. case 'xjY':
  983. if ( !$hebrew ) {
  984. $hebrew = self::tsToHebrew( $ts );
  985. }
  986. $num = $hebrew[0];
  987. break;
  988. case 'xkY':
  989. if ( !$thai ) {
  990. $thai = self::tsToYear( $ts, 'thai' );
  991. }
  992. $num = $thai[0];
  993. break;
  994. case 'xoY':
  995. if ( !$minguo ) {
  996. $minguo = self::tsToYear( $ts, 'minguo' );
  997. }
  998. $num = $minguo[0];
  999. break;
  1000. case 'xtY':
  1001. if ( !$tenno ) {
  1002. $tenno = self::tsToYear( $ts, 'tenno' );
  1003. }
  1004. $num = $tenno[0];
  1005. break;
  1006. case 'y':
  1007. $num = substr( $ts, 2, 2 );
  1008. break;
  1009. case 'a':
  1010. $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
  1011. break;
  1012. case 'A':
  1013. $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
  1014. break;
  1015. case 'g':
  1016. $h = substr( $ts, 8, 2 );
  1017. $num = $h % 12 ? $h % 12 : 12;
  1018. break;
  1019. case 'G':
  1020. $num = intval( substr( $ts, 8, 2 ) );
  1021. break;
  1022. case 'h':
  1023. $h = substr( $ts, 8, 2 );
  1024. $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
  1025. break;
  1026. case 'H':
  1027. $num = substr( $ts, 8, 2 );
  1028. break;
  1029. case 'i':
  1030. $num = substr( $ts, 10, 2 );
  1031. break;
  1032. case 's':
  1033. $num = substr( $ts, 12, 2 );
  1034. break;
  1035. case 'c':
  1036. if ( !$unix ) {
  1037. $unix = wfTimestamp( TS_UNIX, $ts );
  1038. }
  1039. $s .= gmdate( 'c', $unix );
  1040. break;
  1041. case 'r':
  1042. if ( !$unix ) {
  1043. $unix = wfTimestamp( TS_UNIX, $ts );
  1044. }
  1045. $s .= gmdate( 'r', $unix );
  1046. break;
  1047. case 'U':
  1048. if ( !$unix ) {
  1049. $unix = wfTimestamp( TS_UNIX, $ts );
  1050. }
  1051. $num = $unix;
  1052. break;
  1053. case '\\':
  1054. # Backslash escaping
  1055. if ( $p < strlen( $format ) - 1 ) {
  1056. $s .= $format[++$p];
  1057. } else {
  1058. $s .= '\\';
  1059. }
  1060. break;
  1061. case '"':
  1062. # Quoted literal
  1063. if ( $p < strlen( $format ) - 1 ) {
  1064. $endQuote = strpos( $format, '"', $p + 1 );
  1065. if ( $endQuote === false ) {
  1066. # No terminating quote, assume literal "
  1067. $s .= '"';
  1068. } else {
  1069. $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
  1070. $p = $endQuote;
  1071. }
  1072. } else {
  1073. # Quote at end of string, assume literal "
  1074. $s .= '"';
  1075. }
  1076. break;
  1077. default:
  1078. $s .= $format[$p];
  1079. }
  1080. if ( $num !== false ) {
  1081. if ( $rawToggle || $raw ) {
  1082. $s .= $num;
  1083. $raw = false;
  1084. } elseif ( $roman ) {
  1085. $s .= self::romanNumeral( $num );
  1086. $roman = false;
  1087. } elseif ( $hebrewNum ) {
  1088. $s .= self::hebrewNumeral( $num );
  1089. $hebrewNum = false;
  1090. } else {
  1091. $s .= $this->formatNum( $num, true );
  1092. }
  1093. }
  1094. }
  1095. return $s;
  1096. }
  1097. private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
  1098. private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 );
  1099. /**
  1100. * Algorithm by Roozbeh Pournader and Mohammad Toossi to convert
  1101. * Gregorian dates to Iranian dates. Originally written in C, it
  1102. * is released under the terms of GNU Lesser General Public
  1103. * License. Conversion to PHP was performed by Niklas Laxström.
  1104. *
  1105. * Link: http://www.farsiweb.info/jalali/jalali.c
  1106. *
  1107. * @param $ts string
  1108. *
  1109. * @return string
  1110. */
  1111. private static function tsToIranian( $ts ) {
  1112. $gy = substr( $ts, 0, 4 ) -1600;
  1113. $gm = substr( $ts, 4, 2 ) -1;
  1114. $gd = substr( $ts, 6, 2 ) -1;
  1115. # Days passed from the beginning (including leap years)
  1116. $gDayNo = 365 * $gy
  1117. + floor( ( $gy + 3 ) / 4 )
  1118. - floor( ( $gy + 99 ) / 100 )
  1119. + floor( ( $gy + 399 ) / 400 );
  1120. // Add days of the past months of this year
  1121. for ( $i = 0; $i < $gm; $i++ ) {
  1122. $gDayNo += self::$GREG_DAYS[$i];
  1123. }
  1124. // Leap years
  1125. if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
  1126. $gDayNo++;
  1127. }
  1128. // Days passed in current month
  1129. $gDayNo += $gd;
  1130. $jDayNo = $gDayNo - 79;
  1131. $jNp = floor( $jDayNo / 12053 );
  1132. $jDayNo %= 12053;
  1133. $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
  1134. $jDayNo %= 1461;
  1135. if ( $jDayNo >= 366 ) {
  1136. $jy += floor( ( $jDayNo - 1 ) / 365 );
  1137. $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
  1138. }
  1139. for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
  1140. $jDayNo -= self::$IRANIAN_DAYS[$i];
  1141. }
  1142. $jm = $i + 1;
  1143. $jd = $jDayNo + 1;
  1144. return array( $jy, $jm, $jd );
  1145. }
  1146. /**
  1147. * Converting Gregorian dates to Hijri dates.
  1148. *
  1149. * Based on a PHP-Nuke block by Sharjeel which is released under GNU/GPL license
  1150. *
  1151. * @link http://phpnuke.org/modules.php?name=News&file=article&sid=8234&mode=thread&order=0&thold=0
  1152. *
  1153. * @param $ts string
  1154. *
  1155. * @return string
  1156. */
  1157. private static function tsToHijri( $ts ) {
  1158. $year = substr( $ts, 0, 4 );
  1159. $month = substr( $ts, 4, 2 );
  1160. $day = substr( $ts, 6, 2 );
  1161. $zyr = $year;
  1162. $zd = $day;
  1163. $zm = $month;
  1164. $zy = $zyr;
  1165. if (
  1166. ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
  1167. ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
  1168. )
  1169. {
  1170. $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
  1171. (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
  1172. (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
  1173. $zd - 32075;
  1174. } else {
  1175. $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
  1176. (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
  1177. }
  1178. $zl = $zjd -1948440 + 10632;
  1179. $zn = (int)( ( $zl - 1 ) / 10631 );
  1180. $zl = $zl - 10631 * $zn + 354;
  1181. $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) + ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
  1182. $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) - ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
  1183. $zm = (int)( ( 24 * $zl ) / 709 );
  1184. $zd = $zl - (int)( ( 709 * $zm ) / 24 );
  1185. $zy = 30 * $zn + $zj - 30;
  1186. return array( $zy, $zm, $zd );
  1187. }
  1188. /**
  1189. * Converting Gregorian dates to Hebrew dates.
  1190. *
  1191. * Based on a JavaScript code by Abu Mami and Yisrael Hersch
  1192. * (abu-mami@kaluach.net, http://www.kaluach.net), who permitted
  1193. * to translate the relevant functions into PHP and release them under
  1194. * GNU GPL.
  1195. *
  1196. * The months are counted from Tishrei = 1. In a leap year, Adar I is 13
  1197. * and Adar II is 14. In a non-leap year, Adar is 6.
  1198. *
  1199. * @param $ts string
  1200. *
  1201. * @return string
  1202. */
  1203. private static function tsToHebrew( $ts ) {
  1204. # Parse date
  1205. $year = substr( $ts, 0, 4 );
  1206. $month = substr( $ts, 4, 2 );
  1207. $day = substr( $ts, 6, 2 );
  1208. # Calculate Hebrew year
  1209. $hebrewYear = $year + 3760;
  1210. # Month number when September = 1, August = 12
  1211. $month += 4;
  1212. if ( $month > 12 ) {
  1213. # Next year
  1214. $month -= 12;
  1215. $year++;
  1216. $hebrewYear++;
  1217. }
  1218. # Calculate day of year from 1 September
  1219. $dayOfYear = $day;
  1220. for ( $i = 1; $i < $month; $i++ ) {
  1221. if ( $i == 6 ) {
  1222. # February
  1223. $dayOfYear += 28;
  1224. # Check if the year is leap
  1225. if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
  1226. $dayOfYear++;
  1227. }
  1228. } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
  1229. $dayOfYear += 30;
  1230. } else {
  1231. $dayOfYear += 31;
  1232. }
  1233. }
  1234. # Calculate the start of the Hebrew year
  1235. $start = self::hebrewYearStart( $hebrewYear );
  1236. # Calculate next year's start
  1237. if ( $dayOfYear <= $start ) {
  1238. # Day is before the start of the year - it is the previous year
  1239. # Next year's start
  1240. $nextStart = $start;
  1241. # Previous year
  1242. $year--;
  1243. $hebrewYear--;
  1244. # Add days since previous year's 1 September
  1245. $dayOfYear += 365;
  1246. if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
  1247. # Leap year
  1248. $dayOfYear++;
  1249. }
  1250. # Start of the new (previous) year
  1251. $start = self::hebrewYearStart( $hebrewYear );
  1252. } else {
  1253. # Next year's start
  1254. $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
  1255. }
  1256. # Calculate Hebrew day of year
  1257. $hebrewDayOfYear = $dayOfYear - $start;
  1258. # Difference between year's days
  1259. $diff = $nextStart - $start;
  1260. # Add 12 (or 13 for leap years) days to ignore the difference between
  1261. # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
  1262. # difference is only about the year type
  1263. if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
  1264. $diff += 13;
  1265. } else {
  1266. $diff += 12;
  1267. }
  1268. # Check the year pattern, and is leap year
  1269. # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
  1270. # This is mod 30, to work on both leap years (which add 30 days of Adar I)
  1271. # and non-leap years
  1272. $yearPattern = $diff % 30;
  1273. # Check if leap year
  1274. $isLeap = $diff >= 30;
  1275. # Calculate day in the month from number of day in the Hebrew year
  1276. # Don't check Adar - if the day is not in Adar, we will stop before;
  1277. # if it is in Adar, we will use it to check if it is Adar I or Adar II
  1278. $hebrewDay = $hebrewDayOfYear;
  1279. $hebrewMonth = 1;
  1280. $days = 0;
  1281. while ( $hebrewMonth <= 12 ) {
  1282. # Calculate days in this month
  1283. if ( $isLeap && $hebrewMonth == 6 ) {
  1284. # Adar in a leap year
  1285. if ( $isLeap ) {
  1286. # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
  1287. $days = 30;
  1288. if ( $hebrewDay <= $days ) {
  1289. # Day in Adar I
  1290. $hebrewMonth = 13;
  1291. } else {
  1292. # Subtract the days of Adar I
  1293. $hebrewDay -= $days;
  1294. # Try Adar II
  1295. $days = 29;
  1296. if ( $hebrewDay <= $days ) {
  1297. # Day in Adar II
  1298. $hebrewMonth = 14;
  1299. }
  1300. }
  1301. }
  1302. } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
  1303. # Cheshvan in a complete year (otherwise as the rule below)
  1304. $days = 30;
  1305. } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
  1306. # Kislev in an incomplete year (otherwise as the rule below)
  1307. $days = 29;
  1308. } else {
  1309. # Odd months have 30 days, even have 29
  1310. $days = 30 - ( $hebrewMonth - 1 ) % 2;
  1311. }
  1312. if ( $hebrewDay <= $days ) {
  1313. # In the current month
  1314. break;
  1315. } else {
  1316. # Subtract the days of the current month
  1317. $hebrewDay -= $days;
  1318. # Try in the next month
  1319. $hebrewMonth++;
  1320. }
  1321. }
  1322. return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days );
  1323. }
  1324. /**
  1325. * This calculates the Hebrew year start, as days since 1 September.
  1326. * Based on Carl Friedrich Gauss algorithm for finding Easter date.
  1327. * Used for Hebrew date.
  1328. *
  1329. * @param $year int
  1330. *
  1331. * @return string
  1332. */
  1333. private static function hebrewYearStart( $year ) {
  1334. $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
  1335. $b = intval( ( $year - 1 ) % 4 );
  1336. $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
  1337. if ( $m < 0 ) {
  1338. $m--;
  1339. }
  1340. $Mar = intval( $m );
  1341. if ( $m < 0 ) {
  1342. $m++;
  1343. }
  1344. $m -= $Mar;
  1345. $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
  1346. if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
  1347. $Mar++;
  1348. } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
  1349. $Mar += 2;
  1350. } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
  1351. $Mar++;
  1352. }
  1353. $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
  1354. return $Mar;
  1355. }
  1356. /**
  1357. * Algorithm to convert Gregorian dates to Thai solar dates,
  1358. * Minguo dates or Minguo dates.
  1359. *
  1360. * Link: http://en.wikipedia.org/wiki/Thai_solar_calendar
  1361. * http://en.wikipedia.org/wiki/Minguo_calendar
  1362. * http://en.wikipedia.org/wiki/Japanese_era_name
  1363. *
  1364. * @param $ts String: 14-character timestamp
  1365. * @param $cName String: calender name
  1366. * @return Array: converted year, month, day
  1367. */
  1368. private static function tsToYear( $ts, $cName ) {
  1369. $gy = substr( $ts, 0, 4 );
  1370. $gm = substr( $ts, 4, 2 );
  1371. $gd = substr( $ts, 6, 2 );
  1372. if ( !strcmp( $cName, 'thai' ) ) {
  1373. # Thai solar dates
  1374. # Add 543 years to the Gregorian calendar
  1375. # Months and days are identical
  1376. $gy_offset = $gy + 543;
  1377. } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
  1378. # Minguo dates
  1379. # Deduct 1911 years from the Gregorian calendar
  1380. # Months and days are identical
  1381. $gy_offset = $gy - 1911;
  1382. } elseif ( !strcmp( $cName, 'tenno' ) ) {
  1383. # Nengō dates up to Meiji period
  1384. # Deduct years from the Gregorian calendar
  1385. # depending on the nengo periods
  1386. # Months and days are identical
  1387. if ( ( $gy < 1912 ) || ( ( $gy == 1912 ) && ( $gm < 7 ) ) || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) ) ) {
  1388. # Meiji period
  1389. $gy_gannen = $gy - 1868 + 1;
  1390. $gy_offset = $gy_gannen;
  1391. if ( $gy_gannen == 1 ) {
  1392. $gy_offset = '元';
  1393. }
  1394. $gy_offset = '明治' . $gy_offset;
  1395. } elseif (
  1396. ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
  1397. ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
  1398. ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
  1399. ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
  1400. ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
  1401. )
  1402. {
  1403. # Taishō period
  1404. $gy_gannen = $gy - 1912 + 1;
  1405. $gy_offset = $gy_gannen;
  1406. if ( $gy_gannen == 1 ) {
  1407. $gy_offset = '元';
  1408. }
  1409. $gy_offset = '大正' . $gy_offset;
  1410. } elseif (
  1411. ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
  1412. ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
  1413. ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
  1414. )
  1415. {
  1416. # Shōwa period
  1417. $gy_gannen = $gy - 1926 + 1;
  1418. $gy_offset = $gy_gannen;
  1419. if ( $gy_gannen == 1 ) {
  1420. $gy_offset = '元';
  1421. }
  1422. $gy_offset = '昭和' . $gy_offset;
  1423. } else {
  1424. # Heisei period
  1425. $gy_gannen = $gy - 1989 + 1;
  1426. $gy_offset = $gy_gannen;
  1427. if ( $gy_gannen == 1 ) {
  1428. $gy_offset = '元';
  1429. }
  1430. $gy_offset = '平成' . $gy_offset;
  1431. }
  1432. } else {
  1433. $gy_offset = $gy;
  1434. }
  1435. return array( $gy_offset, $gm, $gd );
  1436. }
  1437. /**
  1438. * Roman number formatting up to 3000
  1439. *
  1440. * @param $num int
  1441. *
  1442. * @return string
  1443. */
  1444. static function romanNumeral( $num ) {
  1445. static $table = array(
  1446. array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
  1447. array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
  1448. array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
  1449. array( '', 'M', 'MM', 'MMM' )
  1450. );
  1451. $num = intval( $num );
  1452. if ( $num > 3000 || $num <= 0 ) {
  1453. return $num;
  1454. }
  1455. $s = '';
  1456. for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
  1457. if ( $num >= $pow10 ) {
  1458. $s .= $table[$i][floor( $num / $pow10 )];
  1459. }
  1460. $num = $num % $pow10;
  1461. }
  1462. return $s;
  1463. }
  1464. /**
  1465. * Hebrew Gematria number formatting up to 9999
  1466. *
  1467. * @param $num int
  1468. *
  1469. * @return string
  1470. */
  1471. static function hebrewNumeral( $num ) {
  1472. static $table = array(
  1473. array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ),
  1474. array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ),
  1475. array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ),
  1476. array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' )
  1477. );
  1478. $num = intval( $num );
  1479. if ( $num > 9999 || $num <= 0 ) {
  1480. return $num;
  1481. }
  1482. $s = '';
  1483. for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
  1484. if ( $num >= $pow10 ) {
  1485. if ( $num == 15 || $num == 16 ) {
  1486. $s .= $table[0][9] . $table[0][$num - 9];
  1487. $num = 0;
  1488. } else {
  1489. $s .= $table[$i][intval( ( $num / $pow10 ) )];
  1490. if ( $pow10 == 1000 ) {
  1491. $s .= "'";
  1492. }
  1493. }
  1494. }
  1495. $num = $num % $pow10;
  1496. }
  1497. if ( strlen( $s ) == 2 ) {
  1498. $str = $s . "'";
  1499. } else {
  1500. $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
  1501. $str .= substr( $s, strlen( $s ) - 2, 2 );
  1502. }
  1503. $start = substr( $str, 0, strlen( $str ) - 2 );
  1504. $end = substr( $str, strlen( $str ) - 2 );
  1505. switch( $end ) {
  1506. case 'כ':
  1507. $str = $start . 'ך';
  1508. break;
  1509. case 'מ':
  1510. $str = $start . 'ם';
  1511. break;
  1512. case 'נ':
  1513. $str = $start . 'ן';
  1514. break;
  1515. case 'פ':
  1516. $str = $start . 'ף';
  1517. break;
  1518. case 'צ':
  1519. $str = $start . 'ץ';
  1520. break;
  1521. }
  1522. return $str;
  1523. }
  1524. /**
  1525. * This is meant to be used by time(), date(), and timeanddate() to get
  1526. * the date preference they're supposed to use, it should be used in
  1527. * all children.
  1528. *
  1529. *<code>
  1530. * function timeanddate([...], $format = true) {
  1531. * $datePreference = $this->dateFormat($format);
  1532. * [...]
  1533. * }
  1534. *</code>
  1535. *
  1536. * @param $usePrefs Mixed: if true, the user's preference is used
  1537. * if false, the site/language default is used
  1538. * if int/string, assumed to be a format.
  1539. * @return string
  1540. */
  1541. function dateFormat( $usePrefs = true ) {
  1542. global $wgUser;
  1543. if ( is_bool( $usePrefs ) ) {
  1544. if ( $usePrefs ) {
  1545. $datePreference = $wgUser->getDatePreference();
  1546. } else {
  1547. $datePreference = (string)User::getDefaultOption( 'date' );
  1548. }
  1549. } else {
  1550. $datePreference = (string)$usePrefs;
  1551. }
  1552. // return int
  1553. if ( $datePreference == '' ) {
  1554. return 'default';
  1555. }
  1556. return $datePreference;
  1557. }
  1558. /**
  1559. * Get a format string for a given type and preference
  1560. * @param $type string May be date, time or both
  1561. * @param $pref string The format name as it appears in Messages*.php
  1562. *
  1563. * @return string
  1564. */
  1565. function getDateFormatString( $type, $pref ) {
  1566. if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
  1567. if ( $pref == 'default' ) {
  1568. $pref = $this->getDefaultDateFormat();
  1569. $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
  1570. } else {
  1571. $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
  1572. if ( is_null( $df ) ) {
  1573. $pref = $this->getDefaultDateFormat();
  1574. $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
  1575. }
  1576. }
  1577. $this->dateFormatStrings[$type][$pref] = $df;
  1578. }
  1579. return $this->dateFormatStrings[$type][$pref];
  1580. }
  1581. /**
  1582. * @param $ts Mixed: the time format which needs to be turned into a
  1583. * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
  1584. * @param $adj Bool: whether to adjust the time output according to the
  1585. * user configured offset ($timecorrection)
  1586. * @param $format Mixed: true to use user's date format preference
  1587. * @param $timecorrection String|bool the time offset as returned by
  1588. * validateTimeZone() in Special:Preferences
  1589. * @return string
  1590. */
  1591. function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
  1592. $ts = wfTimestamp( TS_MW, $ts );
  1593. if ( $adj ) {
  1594. $ts = $this->userAdjust( $ts, $timecorrection );
  1595. }
  1596. $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
  1597. return $this->sprintfDate( $df, $ts );
  1598. }
  1599. /**
  1600. * @param $ts Mixed: the time format which needs to be turned into a
  1601. * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
  1602. * @param $adj Bool: whether to adjust the time output according to the
  1603. * user configured offset ($timecorrection)
  1604. * @param $format Mixed: true to use user's date format preference
  1605. * @param $timecorrection String|bool the time offset as returned by
  1606. * validateTimeZone() in Special:Preferences
  1607. * @return string
  1608. */
  1609. function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
  1610. $ts = wfTimestamp( TS_MW, $ts );
  1611. if ( $adj ) {
  1612. $ts = $this->userAdjust( $ts, $timecorrection );
  1613. }
  1614. $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
  1615. return $this->sprintfDate( $df, $ts );
  1616. }
  1617. /**
  1618. * @param $ts Mixed: the time format which needs to be turned into a
  1619. * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
  1620. * @param $adj Bool: whether to adjust the time output according to the
  1621. * user configured offset ($timecorrection)
  1622. * @param $format Mixed: what format to return, if it's false output the
  1623. * default one (default true)
  1624. * @param $timecorrection String|bool the time offset as returned by
  1625. * validateTimeZone() in Special:Preferences
  1626. * @return string
  1627. */
  1628. function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
  1629. $ts = wfTimestamp( TS_MW, $ts );
  1630. if ( $adj ) {
  1631. $ts = $this->userAdjust( $ts, $timecorrection );
  1632. }
  1633. $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
  1634. return $this->sprintfDate( $df, $ts );
  1635. }
  1636. /**
  1637. * @param $key string
  1638. * @return array|null
  1639. */
  1640. function getMessage( $key ) {
  1641. return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
  1642. }
  1643. /**
  1644. * @return array
  1645. */
  1646. function getAllMessages() {
  1647. return self::$dataCache->getItem( $this->mCode, 'messages' );
  1648. }
  1649. /**
  1650. * @param $in
  1651. * @param $out
  1652. * @param $string
  1653. * @return string
  1654. */
  1655. function iconv( $in, $out, $string ) {
  1656. # This is a wrapper for iconv in all languages except esperanto,
  1657. # which does some nasty x-conversions beforehand
  1658. # Even with //IGNORE iconv can whine about illegal characters in
  1659. # *input* string. We just ignore those too.
  1660. # REF: http://bugs.php.net/bug.php?id=37166
  1661. # REF: https://bugzilla.wikimedia.org/show_bug.cgi?id=16885
  1662. wfSuppressWarnings();
  1663. $text = iconv( $in, $out . '//IGNORE', $string );
  1664. wfRestoreWarnings();
  1665. return $text;
  1666. }
  1667. // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
  1668. /**
  1669. * @param $matches array
  1670. * @return mixed|string
  1671. */
  1672. function ucwordbreaksCallbackAscii( $matches ) {
  1673. return $this->ucfirst( $matches[1] );
  1674. }
  1675. /**
  1676. * @param $matches array
  1677. * @return string
  1678. */
  1679. function ucwordbreaksCallbackMB( $matches ) {
  1680. return mb_strtoupper( $matches[0] );
  1681. }
  1682. /**
  1683. * @param $matches array
  1684. * @return string
  1685. */
  1686. function ucCallback( $matches ) {
  1687. list( $wikiUpperChars ) = self::getCaseMaps();
  1688. return st

Large files files are truncated, but you can click here to view the full file