PageRenderTime 47ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/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
  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 strtr( $matches[1], $wikiUpperChars );
  1689. }
  1690. /**
  1691. * @param $matches array
  1692. * @return string
  1693. */
  1694. function lcCallback( $matches ) {
  1695. list( , $wikiLowerChars ) = self::getCaseMaps();
  1696. return strtr( $matches[1], $wikiLowerChars );
  1697. }
  1698. /**
  1699. * @param $matches array
  1700. * @return string
  1701. */
  1702. function ucwordsCallbackMB( $matches ) {
  1703. return mb_strtoupper( $matches[0] );
  1704. }
  1705. /**
  1706. * @param $matches array
  1707. * @return string
  1708. */
  1709. function ucwordsCallbackWiki( $matches ) {
  1710. list( $wikiUpperChars ) = self::getCaseMaps();
  1711. return strtr( $matches[0], $wikiUpperChars );
  1712. }
  1713. /**
  1714. * Make a string's first character uppercase
  1715. *
  1716. * @param $str string
  1717. *
  1718. * @return string
  1719. */
  1720. function ucfirst( $str ) {
  1721. $o = ord( $str );
  1722. if ( $o < 96 ) { // if already uppercase...
  1723. return $str;
  1724. } elseif ( $o < 128 ) {
  1725. return ucfirst( $str ); // use PHP's ucfirst()
  1726. } else {
  1727. // fall back to more complex logic in case of multibyte strings
  1728. return $this->uc( $str, true );
  1729. }
  1730. }
  1731. /**
  1732. * Convert a string to uppercase
  1733. *
  1734. * @param $str string
  1735. * @param $first bool
  1736. *
  1737. * @return string
  1738. */
  1739. function uc( $str, $first = false ) {
  1740. if ( function_exists( 'mb_strtoupper' ) ) {
  1741. if ( $first ) {
  1742. if ( $this->isMultibyte( $str ) ) {
  1743. return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
  1744. } else {
  1745. return ucfirst( $str );
  1746. }
  1747. } else {
  1748. return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
  1749. }
  1750. } else {
  1751. if ( $this->isMultibyte( $str ) ) {
  1752. $x = $first ? '^' : '';
  1753. return preg_replace_callback(
  1754. "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
  1755. array( $this, 'ucCallback' ),
  1756. $str
  1757. );
  1758. } else {
  1759. return $first ? ucfirst( $str ) : strtoupper( $str );
  1760. }
  1761. }
  1762. }
  1763. /**
  1764. * @param $str string
  1765. * @return mixed|string
  1766. */
  1767. function lcfirst( $str ) {
  1768. $o = ord( $str );
  1769. if ( !$o ) {
  1770. return strval( $str );
  1771. } elseif ( $o >= 128 ) {
  1772. return $this->lc( $str, true );
  1773. } elseif ( $o > 96 ) {
  1774. return $str;
  1775. } else {
  1776. $str[0] = strtolower( $str[0] );
  1777. return $str;
  1778. }
  1779. }
  1780. /**
  1781. * @param $str string
  1782. * @param $first bool
  1783. * @return mixed|string
  1784. */
  1785. function lc( $str, $first = false ) {
  1786. if ( function_exists( 'mb_strtolower' ) ) {
  1787. if ( $first ) {
  1788. if ( $this->isMultibyte( $str ) ) {
  1789. return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
  1790. } else {
  1791. return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
  1792. }
  1793. } else {
  1794. return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
  1795. }
  1796. } else {
  1797. if ( $this->isMultibyte( $str ) ) {
  1798. $x = $first ? '^' : '';
  1799. return preg_replace_callback(
  1800. "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
  1801. array( $this, 'lcCallback' ),
  1802. $str
  1803. );
  1804. } else {
  1805. return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
  1806. }
  1807. }
  1808. }
  1809. /**
  1810. * @param $str string
  1811. * @return bool
  1812. */
  1813. function isMultibyte( $str ) {
  1814. return (bool)preg_match( '/[\x80-\xff]/', $str );
  1815. }
  1816. /**
  1817. * @param $str string
  1818. * @return mixed|string
  1819. */
  1820. function ucwords( $str ) {
  1821. if ( $this->isMultibyte( $str ) ) {
  1822. $str = $this->lc( $str );
  1823. // regexp to find first letter in each word (i.e. after each space)
  1824. $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
  1825. // function to use to capitalize a single char
  1826. if ( function_exists( 'mb_strtoupper' ) ) {
  1827. return preg_replace_callback(
  1828. $replaceRegexp,
  1829. array( $this, 'ucwordsCallbackMB' ),
  1830. $str
  1831. );
  1832. } else {
  1833. return preg_replace_callback(
  1834. $replaceRegexp,
  1835. array( $this, 'ucwordsCallbackWiki' ),
  1836. $str
  1837. );
  1838. }
  1839. } else {
  1840. return ucwords( strtolower( $str ) );
  1841. }
  1842. }
  1843. /**
  1844. * capitalize words at word breaks
  1845. *
  1846. * @param $str string
  1847. * @return mixed
  1848. */
  1849. function ucwordbreaks( $str ) {
  1850. if ( $this->isMultibyte( $str ) ) {
  1851. $str = $this->lc( $str );
  1852. // since \b doesn't work for UTF-8, we explicitely define word break chars
  1853. $breaks = "[ \-\(\)\}\{\.,\?!]";
  1854. // find first letter after word break
  1855. $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
  1856. if ( function_exists( 'mb_strtoupper' ) ) {
  1857. return preg_replace_callback(
  1858. $replaceRegexp,
  1859. array( $this, 'ucwordbreaksCallbackMB' ),
  1860. $str
  1861. );
  1862. } else {
  1863. return preg_replace_callback(
  1864. $replaceRegexp,
  1865. array( $this, 'ucwordsCallbackWiki' ),
  1866. $str
  1867. );
  1868. }
  1869. } else {
  1870. return preg_replace_callback(
  1871. '/\b([\w\x80-\xff]+)\b/',
  1872. array( $this, 'ucwordbreaksCallbackAscii' ),
  1873. $str
  1874. );
  1875. }
  1876. }
  1877. /**
  1878. * Return a case-folded representation of $s
  1879. *
  1880. * This is a representation such that caseFold($s1)==caseFold($s2) if $s1
  1881. * and $s2 are the same except for the case of their characters. It is not
  1882. * necessary for the value returned to make sense when displayed.
  1883. *
  1884. * Do *not* perform any other normalisation in this function. If a caller
  1885. * uses this function when it should be using a more general normalisation
  1886. * function, then fix the caller.
  1887. *
  1888. * @param $s string
  1889. *
  1890. * @return string
  1891. */
  1892. function caseFold( $s ) {
  1893. return $this->uc( $s );
  1894. }
  1895. /**
  1896. * @param $s string
  1897. * @return string
  1898. */
  1899. function checkTitleEncoding( $s ) {
  1900. if ( is_array( $s ) ) {
  1901. wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
  1902. }
  1903. # Check for non-UTF-8 URLs
  1904. $ishigh = preg_match( '/[\x80-\xff]/', $s );
  1905. if ( !$ishigh ) {
  1906. return $s;
  1907. }
  1908. $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
  1909. '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
  1910. if ( $isutf8 ) {
  1911. return $s;
  1912. }
  1913. return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
  1914. }
  1915. /**
  1916. * @return array
  1917. */
  1918. function fallback8bitEncoding() {
  1919. return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
  1920. }
  1921. /**
  1922. * Most writing systems use whitespace to break up words.
  1923. * Some languages such as Chinese don't conventionally do this,
  1924. * which requires special handling when breaking up words for
  1925. * searching etc.
  1926. *
  1927. * @return bool
  1928. */
  1929. function hasWordBreaks() {
  1930. return true;
  1931. }
  1932. /**
  1933. * Some languages such as Chinese require word segmentation,
  1934. * Specify such segmentation when overridden in derived class.
  1935. *
  1936. * @param $string String
  1937. * @return String
  1938. */
  1939. function segmentByWord( $string ) {
  1940. return $string;
  1941. }
  1942. /**
  1943. * Some languages have special punctuation need to be normalized.
  1944. * Make such changes here.
  1945. *
  1946. * @param $string String
  1947. * @return String
  1948. */
  1949. function normalizeForSearch( $string ) {
  1950. return self::convertDoubleWidth( $string );
  1951. }
  1952. /**
  1953. * convert double-width roman characters to single-width.
  1954. * range: ff00-ff5f ~= 0020-007f
  1955. *
  1956. * @param $string string
  1957. *
  1958. * @return string
  1959. */
  1960. protected static function convertDoubleWidth( $string ) {
  1961. static $full = null;
  1962. static $half = null;
  1963. if ( $full === null ) {
  1964. $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
  1965. $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
  1966. $full = str_split( $fullWidth, 3 );
  1967. $half = str_split( $halfWidth );
  1968. }
  1969. $string = str_replace( $full, $half, $string );
  1970. return $string;
  1971. }
  1972. /**
  1973. * @param $string string
  1974. * @param $pattern string
  1975. * @return string
  1976. */
  1977. protected static function insertSpace( $string, $pattern ) {
  1978. $string = preg_replace( $pattern, " $1 ", $string );
  1979. $string = preg_replace( '/ +/', ' ', $string );
  1980. return $string;
  1981. }
  1982. /**
  1983. * @param $termsArray array
  1984. * @return array
  1985. */
  1986. function convertForSearchResult( $termsArray ) {
  1987. # some languages, e.g. Chinese, need to do a conversion
  1988. # in order for search results to be displayed correctly
  1989. return $termsArray;
  1990. }
  1991. /**
  1992. * Get the first character of a string.
  1993. *
  1994. * @param $s string
  1995. * @return string
  1996. */
  1997. function firstChar( $s ) {
  1998. $matches = array();
  1999. preg_match(
  2000. '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
  2001. '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
  2002. $s,
  2003. $matches
  2004. );
  2005. if ( isset( $matches[1] ) ) {
  2006. if ( strlen( $matches[1] ) != 3 ) {
  2007. return $matches[1];
  2008. }
  2009. // Break down Hangul syllables to grab the first jamo
  2010. $code = utf8ToCodepoint( $matches[1] );
  2011. if ( $code < 0xac00 || 0xd7a4 <= $code ) {
  2012. return $matches[1];
  2013. } elseif ( $code < 0xb098 ) {
  2014. return "\xe3\x84\xb1";
  2015. } elseif ( $code < 0xb2e4 ) {
  2016. return "\xe3\x84\xb4";
  2017. } elseif ( $code < 0xb77c ) {
  2018. return "\xe3\x84\xb7";
  2019. } elseif ( $code < 0xb9c8 ) {
  2020. return "\xe3\x84\xb9";
  2021. } elseif ( $code < 0xbc14 ) {
  2022. return "\xe3\x85\x81";
  2023. } elseif ( $code < 0xc0ac ) {
  2024. return "\xe3\x85\x82";
  2025. } elseif ( $code < 0xc544 ) {
  2026. return "\xe3\x85\x85";
  2027. } elseif ( $code < 0xc790 ) {
  2028. return "\xe3\x85\x87";
  2029. } elseif ( $code < 0xcc28 ) {
  2030. return "\xe3\x85\x88";
  2031. } elseif ( $code < 0xce74 ) {
  2032. return "\xe3\x85\x8a";
  2033. } elseif ( $code < 0xd0c0 ) {
  2034. return "\xe3\x85\x8b";
  2035. } elseif ( $code < 0xd30c ) {
  2036. return "\xe3\x85\x8c";
  2037. } elseif ( $code < 0xd558 ) {
  2038. return "\xe3\x85\x8d";
  2039. } else {
  2040. return "\xe3\x85\x8e";
  2041. }
  2042. } else {
  2043. return '';
  2044. }
  2045. }
  2046. function initEncoding() {
  2047. # Some languages may have an alternate char encoding option
  2048. # (Esperanto X-coding, Japanese furigana conversion, etc)
  2049. # If this language is used as the primary content language,
  2050. # an override to the defaults can be set here on startup.
  2051. }
  2052. /**
  2053. * @param $s string
  2054. * @return string
  2055. */
  2056. function recodeForEdit( $s ) {
  2057. # For some languages we'll want to explicitly specify
  2058. # which characters make it into the edit box raw
  2059. # or are converted in some way or another.
  2060. global $wgEditEncoding;
  2061. if ( $wgEditEncoding == '' || $wgEditEncoding == 'UTF-8' ) {
  2062. return $s;
  2063. } else {
  2064. return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
  2065. }
  2066. }
  2067. /**
  2068. * @param $s string
  2069. * @return string
  2070. */
  2071. function recodeInput( $s ) {
  2072. # Take the previous into account.
  2073. global $wgEditEncoding;
  2074. if ( $wgEditEncoding != '' ) {
  2075. $enc = $wgEditEncoding;
  2076. } else {
  2077. $enc = 'UTF-8';
  2078. }
  2079. if ( $enc == 'UTF-8' ) {
  2080. return $s;
  2081. } else {
  2082. return $this->iconv( $enc, 'UTF-8', $s );
  2083. }
  2084. }
  2085. /**
  2086. * Convert a UTF-8 string to normal form C. In Malayalam and Arabic, this
  2087. * also cleans up certain backwards-compatible sequences, converting them
  2088. * to the modern Unicode equivalent.
  2089. *
  2090. * This is language-specific for performance reasons only.
  2091. *
  2092. * @param $s string
  2093. *
  2094. * @return string
  2095. */
  2096. function normalize( $s ) {
  2097. global $wgAllUnicodeFixes;
  2098. $s = UtfNormal::cleanUp( $s );
  2099. if ( $wgAllUnicodeFixes ) {
  2100. $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s );
  2101. $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s );
  2102. }
  2103. return $s;
  2104. }
  2105. /**
  2106. * Transform a string using serialized data stored in the given file (which
  2107. * must be in the serialized subdirectory of $IP). The file contains pairs
  2108. * mapping source characters to destination characters.
  2109. *
  2110. * The data is cached in process memory. This will go faster if you have the
  2111. * FastStringSearch extension.
  2112. *
  2113. * @param $file string
  2114. * @param $string string
  2115. *
  2116. * @return string
  2117. */
  2118. function transformUsingPairFile( $file, $string ) {
  2119. if ( !isset( $this->transformData[$file] ) ) {
  2120. $data = wfGetPrecompiledData( $file );
  2121. if ( $data === false ) {
  2122. throw new MWException( __METHOD__ . ": The transformation file $file is missing" );
  2123. }
  2124. $this->transformData[$file] = new ReplacementArray( $data );
  2125. }
  2126. return $this->transformData[$file]->replace( $string );
  2127. }
  2128. /**
  2129. * For right-to-left language support
  2130. *
  2131. * @return bool
  2132. */
  2133. function isRTL() {
  2134. return self::$dataCache->getItem( $this->mCode, 'rtl' );
  2135. }
  2136. /**
  2137. * Return the correct HTML 'dir' attribute value for this language.
  2138. * @return String
  2139. */
  2140. function getDir() {
  2141. return $this->isRTL() ? 'rtl' : 'ltr';
  2142. }
  2143. /**
  2144. * Return 'left' or 'right' as appropriate alignment for line-start
  2145. * for this language's text direction.
  2146. *
  2147. * Should be equivalent to CSS3 'start' text-align value....
  2148. *
  2149. * @return String
  2150. */
  2151. function alignStart() {
  2152. return $this->isRTL() ? 'right' : 'left';
  2153. }
  2154. /**
  2155. * Return 'right' or 'left' as appropriate alignment for line-end
  2156. * for this language's text direction.
  2157. *
  2158. * Should be equivalent to CSS3 'end' text-align value....
  2159. *
  2160. * @return String
  2161. */
  2162. function alignEnd() {
  2163. return $this->isRTL() ? 'left' : 'right';
  2164. }
  2165. /**
  2166. * A hidden direction mark (LRM or RLM), depending on the language direction
  2167. *
  2168. * @return string
  2169. */
  2170. function getDirMark() {
  2171. return $this->isRTL() ? "\xE2\x80\x8F" : "\xE2\x80\x8E";
  2172. }
  2173. /**
  2174. * @return array
  2175. */
  2176. function capitalizeAllNouns() {
  2177. return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
  2178. }
  2179. /**
  2180. * An arrow, depending on the language direction
  2181. *
  2182. * @return string
  2183. */
  2184. function getArrow() {
  2185. return $this->isRTL() ? '←' : '→';
  2186. }
  2187. /**
  2188. * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
  2189. *
  2190. * @return bool
  2191. */
  2192. function linkPrefixExtension() {
  2193. return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
  2194. }
  2195. /**
  2196. * @return array
  2197. */
  2198. function getMagicWords() {
  2199. return self::$dataCache->getItem( $this->mCode, 'magicWords' );
  2200. }
  2201. protected function doMagicHook() {
  2202. if ( $this->mMagicHookDone ) {
  2203. return;
  2204. }
  2205. $this->mMagicHookDone = true;
  2206. wfProfileIn( 'LanguageGetMagic' );
  2207. wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions, $this->getCode() ) );
  2208. wfProfileOut( 'LanguageGetMagic' );
  2209. }
  2210. /**
  2211. * Fill a MagicWord object with data from here
  2212. *
  2213. * @param $mw
  2214. */
  2215. function getMagic( $mw ) {
  2216. $this->doMagicHook();
  2217. if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
  2218. $rawEntry = $this->mMagicExtensions[$mw->mId];
  2219. } else {
  2220. $magicWords = $this->getMagicWords();
  2221. if ( isset( $magicWords[$mw->mId] ) ) {
  2222. $rawEntry = $magicWords[$mw->mId];
  2223. } else {
  2224. $rawEntry = false;
  2225. }
  2226. }
  2227. if ( !is_array( $rawEntry ) ) {
  2228. error_log( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
  2229. } else {
  2230. $mw->mCaseSensitive = $rawEntry[0];
  2231. $mw->mSynonyms = array_slice( $rawEntry, 1 );
  2232. }
  2233. }
  2234. /**
  2235. * Add magic words to the extension array
  2236. *
  2237. * @param $newWords array
  2238. */
  2239. function addMagicWordsByLang( $newWords ) {
  2240. $code = $this->getCode();
  2241. $fallbackChain = array();
  2242. while ( $code && !in_array( $code, $fallbackChain ) ) {
  2243. $fallbackChain[] = $code;
  2244. $code = self::getFallbackFor( $code );
  2245. }
  2246. if ( !in_array( 'en', $fallbackChain ) ) {
  2247. $fallbackChain[] = 'en';
  2248. }
  2249. $fallbackChain = array_reverse( $fallbackChain );
  2250. foreach ( $fallbackChain as $code ) {
  2251. if ( isset( $newWords[$code] ) ) {
  2252. $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
  2253. }
  2254. }
  2255. }
  2256. /**
  2257. * Get special page names, as an associative array
  2258. * case folded alias => real name
  2259. */
  2260. function getSpecialPageAliases() {
  2261. // Cache aliases because it may be slow to load them
  2262. if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
  2263. // Initialise array
  2264. $this->mExtendedSpecialPageAliases =
  2265. self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
  2266. wfRunHooks( 'LanguageGetSpecialPageAliases',
  2267. array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) );
  2268. }
  2269. return $this->mExtendedSpecialPageAliases;
  2270. }
  2271. /**
  2272. * Italic is unsuitable for some languages
  2273. *
  2274. * @param $text String: the text to be emphasized.
  2275. * @return string
  2276. */
  2277. function emphasize( $text ) {
  2278. return "<em>$text</em>";
  2279. }
  2280. /**
  2281. * Normally we output all numbers in plain en_US style, that is
  2282. * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
  2283. * point twohundredthirtyfive. However this is not sutable for all
  2284. * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
  2285. * Icelandic just want to use commas instead of dots, and dots instead
  2286. * of commas like "293.291,235".
  2287. *
  2288. * An example of this function being called:
  2289. * <code>
  2290. * wfMsg( 'message', $wgLang->formatNum( $num ) )
  2291. * </code>
  2292. *
  2293. * See LanguageGu.php for the Gujarati implementation and
  2294. * $separatorTransformTable on MessageIs.php for
  2295. * the , => . and . => , implementation.
  2296. *
  2297. * @todo check if it's viable to use localeconv() for the decimal
  2298. * separator thing.
  2299. * @param $number Mixed: the string to be formatted, should be an integer
  2300. * or a floating point number.
  2301. * @param $nocommafy Bool: set to true for special numbers like dates
  2302. * @return string
  2303. */
  2304. function formatNum( $number, $nocommafy = false ) {
  2305. global $wgTranslateNumerals;
  2306. if ( !$nocommafy ) {
  2307. $number = $this->commafy( $number );
  2308. $s = $this->separatorTransformTable();
  2309. if ( $s ) {
  2310. $number = strtr( $number, $s );
  2311. }
  2312. }
  2313. if ( $wgTranslateNumerals ) {
  2314. $s = $this->digitTransformTable();
  2315. if ( $s ) {
  2316. $number = strtr( $number, $s );
  2317. }
  2318. }
  2319. return $number;
  2320. }
  2321. /**
  2322. * @param $number string
  2323. * @return string
  2324. */
  2325. function parseFormattedNumber( $number ) {
  2326. $s = $this->digitTransformTable();
  2327. if ( $s ) {
  2328. $number = strtr( $number, array_flip( $s ) );
  2329. }
  2330. $s = $this->separatorTransformTable();
  2331. if ( $s ) {
  2332. $number = strtr( $number, array_flip( $s ) );
  2333. }
  2334. $number = strtr( $number, array( ',' => '' ) );
  2335. return $number;
  2336. }
  2337. /**
  2338. * Adds commas to a given number
  2339. *
  2340. * @param $_ mixed
  2341. * @return string
  2342. */
  2343. function commafy( $_ ) {
  2344. return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $_ ) ) );
  2345. }
  2346. /**
  2347. * @return array
  2348. */
  2349. function digitTransformTable() {
  2350. return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
  2351. }
  2352. /**
  2353. * @return array
  2354. */
  2355. function separatorTransformTable() {
  2356. return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
  2357. }
  2358. /**
  2359. * Take a list of strings and build a locale-friendly comma-separated
  2360. * list, using the local comma-separator message.
  2361. * The last two strings are chained with an "and".
  2362. *
  2363. * @param $l Array
  2364. * @return string
  2365. */
  2366. function listToText( $l ) {
  2367. $s = '';
  2368. $m = count( $l ) - 1;
  2369. if ( $m == 1 ) {
  2370. return $l[0] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $l[1];
  2371. } else {
  2372. for ( $i = $m; $i >= 0; $i-- ) {
  2373. if ( $i == $m ) {
  2374. $s = $l[$i];
  2375. } elseif ( $i == $m - 1 ) {
  2376. $s = $l[$i] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $s;
  2377. } else {
  2378. $s = $l[$i] . $this->getMessageFromDB( 'comma-separator' ) . $s;
  2379. }
  2380. }
  2381. return $s;
  2382. }
  2383. }
  2384. /**
  2385. * Take a list of strings and build a locale-friendly comma-separated
  2386. * list, using the local comma-separator message.
  2387. * @param $list array of strings to put in a comma list
  2388. * @return string
  2389. */
  2390. function commaList( $list ) {
  2391. return implode(
  2392. $list,
  2393. wfMsgExt(
  2394. 'comma-separator',
  2395. array( 'parsemag', 'escapenoentities', 'language' => $this )
  2396. )
  2397. );
  2398. }
  2399. /**
  2400. * Take a list of strings and build a locale-friendly semicolon-separated
  2401. * list, using the local semicolon-separator message.
  2402. * @param $list array of strings to put in a semicolon list
  2403. * @return string
  2404. */
  2405. function semicolonList( $list ) {
  2406. return implode(
  2407. $list,
  2408. wfMsgExt(
  2409. 'semicolon-separator',
  2410. array( 'parsemag', 'escapenoentities', 'language' => $this )
  2411. )
  2412. );
  2413. }
  2414. /**
  2415. * Same as commaList, but separate it with the pipe instead.
  2416. * @param $list array of strings to put in a pipe list
  2417. * @return string
  2418. */
  2419. function pipeList( $list ) {
  2420. return implode(
  2421. $list,
  2422. wfMsgExt(
  2423. 'pipe-separator',
  2424. array( 'escapenoentities', 'language' => $this )
  2425. )
  2426. );
  2427. }
  2428. /**
  2429. * Truncate a string to a specified length in bytes, appending an optional
  2430. * string (e.g. for ellipses)
  2431. *
  2432. * The database offers limited byte lengths for some columns in the database;
  2433. * multi-byte character sets mean we need to ensure that only whole characters
  2434. * are included, otherwise broken characters can be passed to the user
  2435. *
  2436. * If $length is negative, the string will be truncated from the beginning
  2437. *
  2438. * @param $string String to truncate
  2439. * @param $length Int: maximum length (including ellipses)
  2440. * @param $ellipsis String to append to the truncated text
  2441. * @param $adjustLength Boolean: Subtract length of ellipsis from $length.
  2442. * $adjustLength was introduced in 1.18, before that behaved as if false.
  2443. * @return string
  2444. */
  2445. function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
  2446. # Use the localized ellipsis character
  2447. if ( $ellipsis == '...' ) {
  2448. $ellipsis = wfMsgExt( 'ellipsis', array( 'escapenoentities', 'language' => $this ) );
  2449. }
  2450. # Check if there is no need to truncate
  2451. if ( $length == 0 ) {
  2452. return $ellipsis; // convention
  2453. } elseif ( strlen( $string ) <= abs( $length ) ) {
  2454. return $string; // no need to truncate
  2455. }
  2456. $stringOriginal = $string;
  2457. # If ellipsis length is >= $length then we can't apply $adjustLength
  2458. if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) {
  2459. $string = $ellipsis; // this can be slightly unexpected
  2460. # Otherwise, truncate and add ellipsis...
  2461. } else {
  2462. $eLength = $adjustLength ? strlen( $ellipsis ) : 0;
  2463. if ( $length > 0 ) {
  2464. $length -= $eLength;
  2465. $string = substr( $string, 0, $length ); // xyz...
  2466. $string = $this->removeBadCharLast( $string );
  2467. $string = $string . $ellipsis;
  2468. } else {
  2469. $length += $eLength;
  2470. $string = substr( $string, $length ); // ...xyz
  2471. $string = $this->removeBadCharFirst( $string );
  2472. $string = $ellipsis . $string;
  2473. }
  2474. }
  2475. # Do not truncate if the ellipsis makes the string longer/equal (bug 22181).
  2476. # This check is *not* redundant if $adjustLength, due to the single case where
  2477. # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
  2478. if ( strlen( $string ) < strlen( $stringOriginal ) ) {
  2479. return $string;
  2480. } else {
  2481. return $stringOriginal;
  2482. }
  2483. }
  2484. /**
  2485. * Remove bytes that represent an incomplete Unicode character
  2486. * at the end of string (e.g. bytes of the char are missing)
  2487. *
  2488. * @param $string String
  2489. * @return string
  2490. */
  2491. protected function removeBadCharLast( $string ) {
  2492. if ( $string != '' ) {
  2493. $char = ord( $string[strlen( $string ) - 1] );
  2494. $m = array();
  2495. if ( $char >= 0xc0 ) {
  2496. # We got the first byte only of a multibyte char; remove it.
  2497. $string = substr( $string, 0, -1 );
  2498. } elseif ( $char >= 0x80 &&
  2499. preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
  2500. '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) )
  2501. {
  2502. # We chopped in the middle of a character; remove it
  2503. $string = $m[1];
  2504. }
  2505. }
  2506. return $string;
  2507. }
  2508. /**
  2509. * Remove bytes that represent an incomplete Unicode character
  2510. * at the start of string (e.g. bytes of the char are missing)
  2511. *
  2512. * @param $string String
  2513. * @return string
  2514. */
  2515. protected function removeBadCharFirst( $string ) {
  2516. if ( $string != '' ) {
  2517. $char = ord( $string[0] );
  2518. if ( $char >= 0x80 && $char < 0xc0 ) {
  2519. # We chopped in the middle of a character; remove the whole thing
  2520. $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
  2521. }
  2522. }
  2523. return $string;
  2524. }
  2525. /**
  2526. * Truncate a string of valid HTML to a specified length in bytes,
  2527. * appending an optional string (e.g. for ellipses), and return valid HTML
  2528. *
  2529. * This is only intended for styled/linked text, such as HTML with
  2530. * tags like <span> and <a>, were the tags are self-contained (valid HTML).
  2531. * Also, this will not detect things like "display:none" CSS.
  2532. *
  2533. * Note: since 1.18 you do not need to leave extra room in $length for ellipses.
  2534. *
  2535. * @param string $text HTML string to truncate
  2536. * @param int $length (zero/positive) Maximum length (including ellipses)
  2537. * @param string $ellipsis String to append to the truncated text
  2538. * @return string
  2539. */
  2540. function truncateHtml( $text, $length, $ellipsis = '...' ) {
  2541. # Use the localized ellipsis character
  2542. if ( $ellipsis == '...' ) {
  2543. $ellipsis = wfMsgExt( 'ellipsis', array( 'escapenoentities', 'language' => $this ) );
  2544. }
  2545. # Check if there is clearly no need to truncate
  2546. if ( $length <= 0 ) {
  2547. return $ellipsis; // no text shown, nothing to format (convention)
  2548. } elseif ( strlen( $text ) <= $length ) {
  2549. return $text; // string short enough even *with* HTML (short-circuit)
  2550. }
  2551. $displayLen = 0; // innerHTML legth so far
  2552. $testingEllipsis = false; // checking if ellipses will make string longer/equal?
  2553. $tagType = 0; // 0-open, 1-close
  2554. $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
  2555. $entityState = 0; // 0-not entity, 1-entity
  2556. $tag = $ret = $pRet = ''; // accumulated tag name, accumulated result string
  2557. $openTags = array(); // open tag stack
  2558. $pOpenTags = array();
  2559. $textLen = strlen( $text );
  2560. $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
  2561. for ( $pos = 0; true; ++$pos ) {
  2562. # Consider truncation once the display length has reached the maximim.
  2563. # Check that we're not in the middle of a bracket/entity...
  2564. if ( $displayLen >= $neLength && $bracketState == 0 && $entityState == 0 ) {
  2565. if ( !$testingEllipsis ) {
  2566. $testingEllipsis = true;
  2567. # Save where we are; we will truncate here unless there turn out to
  2568. # be so few remaining characters that truncation is not necessary.
  2569. $pOpenTags = $openTags; // save state
  2570. $pRet = $ret; // save state
  2571. } elseif ( $displayLen > $length && $displayLen > strlen( $ellipsis ) ) {
  2572. # String in fact does need truncation, the truncation point was OK.
  2573. $openTags = $pOpenTags; // reload state
  2574. $ret = $this->removeBadCharLast( $pRet ); // reload state, multi-byte char fix
  2575. $ret .= $ellipsis; // add ellipsis
  2576. break;
  2577. }
  2578. }
  2579. if ( $pos >= $textLen ) break; // extra iteration just for above checks
  2580. # Read the next char...
  2581. $ch = $text[$pos];
  2582. $lastCh = $pos ? $text[$pos - 1] : '';
  2583. $ret .= $ch; // add to result string
  2584. if ( $ch == '<' ) {
  2585. $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
  2586. $entityState = 0; // for bad HTML
  2587. $bracketState = 1; // tag started (checking for backslash)
  2588. } elseif ( $ch == '>' ) {
  2589. $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
  2590. $entityState = 0; // for bad HTML
  2591. $bracketState = 0; // out of brackets
  2592. } elseif ( $bracketState == 1 ) {
  2593. if ( $ch == '/' ) {
  2594. $tagType = 1; // close tag (e.g. "</span>")
  2595. } else {
  2596. $tagType = 0; // open tag (e.g. "<span>")
  2597. $tag .= $ch;
  2598. }
  2599. $bracketState = 2; // building tag name
  2600. } elseif ( $bracketState == 2 ) {
  2601. if ( $ch != ' ' ) {
  2602. $tag .= $ch;
  2603. } else {
  2604. // Name found (e.g. "<a href=..."), add on tag attributes...
  2605. $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
  2606. }
  2607. } elseif ( $bracketState == 0 ) {
  2608. if ( $entityState ) {
  2609. if ( $ch == ';' ) {
  2610. $entityState = 0;
  2611. $displayLen++; // entity is one displayed char
  2612. }
  2613. } else {
  2614. if ( $ch == '&' ) {
  2615. $entityState = 1; // entity found, (e.g. "&#160;")
  2616. } else {
  2617. $displayLen++; // this char is displayed
  2618. // Add the next $max display text chars after this in one swoop...
  2619. $max = ( $testingEllipsis ? $length : $neLength ) - $displayLen;
  2620. $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
  2621. $displayLen += $skipped;
  2622. $pos += $skipped;
  2623. }
  2624. }
  2625. }
  2626. }
  2627. if ( $displayLen == 0 ) {
  2628. return ''; // no text shown, nothing to format
  2629. }
  2630. // Close the last tag if left unclosed by bad HTML
  2631. $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
  2632. while ( count( $openTags ) > 0 ) {
  2633. $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
  2634. }
  2635. return $ret;
  2636. }
  2637. /**
  2638. * truncateHtml() helper function
  2639. * like strcspn() but adds the skipped chars to $ret
  2640. *
  2641. * @param $ret
  2642. * @param $text
  2643. * @param $search
  2644. * @param $start
  2645. * @param $len
  2646. * @return int
  2647. */
  2648. private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
  2649. if ( $len === null ) {
  2650. $len = -1; // -1 means "no limit" for strcspn
  2651. } elseif ( $len < 0 ) {
  2652. $len = 0; // sanity
  2653. }
  2654. $skipCount = 0;
  2655. if ( $start < strlen( $text ) ) {
  2656. $skipCount = strcspn( $text, $search, $start, $len );
  2657. $ret .= substr( $text, $start, $skipCount );
  2658. }
  2659. return $skipCount;
  2660. }
  2661. /**
  2662. * truncateHtml() helper function
  2663. * (a) push or pop $tag from $openTags as needed
  2664. * (b) clear $tag value
  2665. * @param String &$tag Current HTML tag name we are looking at
  2666. * @param int $tagType (0-open tag, 1-close tag)
  2667. * @param char $lastCh Character before the '>' that ended this tag
  2668. * @param array &$openTags Open tag stack (not accounting for $tag)
  2669. */
  2670. private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
  2671. $tag = ltrim( $tag );
  2672. if ( $tag != '' ) {
  2673. if ( $tagType == 0 && $lastCh != '/' ) {
  2674. $openTags[] = $tag; // tag opened (didn't close itself)
  2675. } elseif ( $tagType == 1 ) {
  2676. if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
  2677. array_pop( $openTags ); // tag closed
  2678. }
  2679. }
  2680. $tag = '';
  2681. }
  2682. }
  2683. /**
  2684. * Grammatical transformations, needed for inflected languages
  2685. * Invoked by putting {{grammar:case|word}} in a message
  2686. *
  2687. * @param $word string
  2688. * @param $case string
  2689. * @return string
  2690. */
  2691. function convertGrammar( $word, $case ) {
  2692. global $wgGrammarForms;
  2693. if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
  2694. return $wgGrammarForms[$this->getCode()][$case][$word];
  2695. }
  2696. return $word;
  2697. }
  2698. /**
  2699. * Provides an alternative text depending on specified gender.
  2700. * Usage {{gender:username|masculine|feminine|neutral}}.
  2701. * username is optional, in which case the gender of current user is used,
  2702. * but only in (some) interface messages; otherwise default gender is used.
  2703. * If second or third parameter are not specified, masculine is used.
  2704. * These details may be overriden per language.
  2705. *
  2706. * @param $gender string
  2707. * @param $forms array
  2708. *
  2709. * @return string
  2710. */
  2711. function gender( $gender, $forms ) {
  2712. if ( !count( $forms ) ) {
  2713. return '';
  2714. }
  2715. $forms = $this->preConvertPlural( $forms, 2 );
  2716. if ( $gender === 'male' ) {
  2717. return $forms[0];
  2718. }
  2719. if ( $gender === 'female' ) {
  2720. return $forms[1];
  2721. }
  2722. return isset( $forms[2] ) ? $forms[2] : $forms[0];
  2723. }
  2724. /**
  2725. * Plural form transformations, needed for some languages.
  2726. * For example, there are 3 form of plural in Russian and Polish,
  2727. * depending on "count mod 10". See [[w:Plural]]
  2728. * For English it is pretty simple.
  2729. *
  2730. * Invoked by putting {{plural:count|wordform1|wordform2}}
  2731. * or {{plural:count|wordform1|wordform2|wordform3}}
  2732. *
  2733. * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
  2734. *
  2735. * @param $count Integer: non-localized number
  2736. * @param $forms Array: different plural forms
  2737. * @return string Correct form of plural for $count in this language
  2738. */
  2739. function convertPlural( $count, $forms ) {
  2740. if ( !count( $forms ) ) {
  2741. return '';
  2742. }
  2743. $forms = $this->preConvertPlural( $forms, 2 );
  2744. return ( $count == 1 ) ? $forms[0] : $forms[1];
  2745. }
  2746. /**
  2747. * Checks that convertPlural was given an array and pads it to requested
  2748. * amount of forms by copying the last one.
  2749. *
  2750. * @param $count Integer: How many forms should there be at least
  2751. * @param $forms Array of forms given to convertPlural
  2752. * @return array Padded array of forms or an exception if not an array
  2753. */
  2754. protected function preConvertPlural( /* Array */ $forms, $count ) {
  2755. while ( count( $forms ) < $count ) {
  2756. $forms[] = $forms[count( $forms ) - 1];
  2757. }
  2758. return $forms;
  2759. }
  2760. /**
  2761. * Maybe translate block durations. Note that this function is somewhat misnamed: it
  2762. * deals with translating the *duration* ("1 week", "4 days", etc), not the expiry time
  2763. * (which is an absolute timestamp).
  2764. * @param $str String: the validated block duration in English
  2765. * @return Somehow translated block duration
  2766. * @see LanguageFi.php for example implementation
  2767. */
  2768. function translateBlockExpiry( $str ) {
  2769. $duration = SpecialBlock::getSuggestedDurations( $this );
  2770. foreach( $duration as $show => $value ){
  2771. if ( strcmp( $str, $value ) == 0 ) {
  2772. return htmlspecialchars( trim( $show ) );
  2773. }
  2774. }
  2775. // Since usually only infinite or indefinite is only on list, so try
  2776. // equivalents if still here.
  2777. $indefs = array( 'infinite', 'infinity', 'indefinite' );
  2778. if ( in_array( $str, $indefs ) ) {
  2779. foreach( $indefs as $val ) {
  2780. $show = array_search( $val, $duration, true );
  2781. if ( $show !== false ) {
  2782. return htmlspecialchars( trim( $show ) );
  2783. }
  2784. }
  2785. }
  2786. // If all else fails, return the original string.
  2787. return $str;
  2788. }
  2789. /**
  2790. * languages like Chinese need to be segmented in order for the diff
  2791. * to be of any use
  2792. *
  2793. * @param $text String
  2794. * @return String
  2795. */
  2796. function segmentForDiff( $text ) {
  2797. return $text;
  2798. }
  2799. /**
  2800. * and unsegment to show the result
  2801. *
  2802. * @param $text String
  2803. * @return String
  2804. */
  2805. function unsegmentForDiff( $text ) {
  2806. return $text;
  2807. }
  2808. /**
  2809. * convert text to all supported variants
  2810. *
  2811. * @param $text string
  2812. * @return array
  2813. */
  2814. function autoConvertToAllVariants( $text ) {
  2815. return $this->mConverter->autoConvertToAllVariants( $text );
  2816. }
  2817. /**
  2818. * convert text to different variants of a language.
  2819. *
  2820. * @param $text string
  2821. * @return string
  2822. */
  2823. function convert( $text ) {
  2824. return $this->mConverter->convert( $text );
  2825. }
  2826. /**
  2827. * Convert a Title object to a string in the preferred variant
  2828. *
  2829. * @param $title Title
  2830. * @return string
  2831. */
  2832. function convertTitle( $title ) {
  2833. return $this->mConverter->convertTitle( $title );
  2834. }
  2835. /**
  2836. * Check if this is a language with variants
  2837. *
  2838. * @return bool
  2839. */
  2840. function hasVariants() {
  2841. return sizeof( $this->getVariants() ) > 1;
  2842. }
  2843. /**
  2844. * Put custom tags (e.g. -{ }-) around math to prevent conversion
  2845. *
  2846. * @param $text string
  2847. * @return string
  2848. */
  2849. function armourMath( $text ) {
  2850. return $this->mConverter->armourMath( $text );
  2851. }
  2852. /**
  2853. * Perform output conversion on a string, and encode for safe HTML output.
  2854. * @param $text String text to be converted
  2855. * @param $isTitle Bool whether this conversion is for the article title
  2856. * @return string
  2857. * @todo this should get integrated somewhere sane
  2858. */
  2859. function convertHtml( $text, $isTitle = false ) {
  2860. return htmlspecialchars( $this->convert( $text, $isTitle ) );
  2861. }
  2862. /**
  2863. * @param $key string
  2864. * @return string
  2865. */
  2866. function convertCategoryKey( $key ) {
  2867. return $this->mConverter->convertCategoryKey( $key );
  2868. }
  2869. /**
  2870. * Get the list of variants supported by this language
  2871. * see sample implementation in LanguageZh.php
  2872. *
  2873. * @return array an array of language codes
  2874. */
  2875. function getVariants() {
  2876. return $this->mConverter->getVariants();
  2877. }
  2878. /**
  2879. * @return string
  2880. */
  2881. function getPreferredVariant() {
  2882. return $this->mConverter->getPreferredVariant();
  2883. }
  2884. /**
  2885. * @return string
  2886. */
  2887. function getDefaultVariant() {
  2888. return $this->mConverter->getDefaultVariant();
  2889. }
  2890. /**
  2891. * @return string
  2892. */
  2893. function getURLVariant() {
  2894. return $this->mConverter->getURLVariant();
  2895. }
  2896. /**
  2897. * If a language supports multiple variants, it is
  2898. * possible that non-existing link in one variant
  2899. * actually exists in another variant. this function
  2900. * tries to find it. See e.g. LanguageZh.php
  2901. *
  2902. * @param $link String: the name of the link
  2903. * @param $nt Mixed: the title object of the link
  2904. * @param $ignoreOtherCond Boolean: to disable other conditions when
  2905. * we need to transclude a template or update a category's link
  2906. * @return null the input parameters may be modified upon return
  2907. */
  2908. function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
  2909. $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
  2910. }
  2911. /**
  2912. * If a language supports multiple variants, converts text
  2913. * into an array of all possible variants of the text:
  2914. * 'variant' => text in that variant
  2915. *
  2916. * @deprecated since 1.17 Use autoConvertToAllVariants()
  2917. *
  2918. * @param $text string
  2919. *
  2920. * @return string
  2921. */
  2922. function convertLinkToAllVariants( $text ) {
  2923. return $this->mConverter->convertLinkToAllVariants( $text );
  2924. }
  2925. /**
  2926. * returns language specific options used by User::getPageRenderHash()
  2927. * for example, the preferred language variant
  2928. *
  2929. * @return string
  2930. */
  2931. function getExtraHashOptions() {
  2932. return $this->mConverter->getExtraHashOptions();
  2933. }
  2934. /**
  2935. * For languages that support multiple variants, the title of an
  2936. * article may be displayed differently in different variants. this
  2937. * function returns the apporiate title defined in the body of the article.
  2938. *
  2939. * @return string
  2940. */
  2941. function getParsedTitle() {
  2942. return $this->mConverter->getParsedTitle();
  2943. }
  2944. /**
  2945. * Enclose a string with the "no conversion" tag. This is used by
  2946. * various functions in the Parser
  2947. *
  2948. * @param $text String: text to be tagged for no conversion
  2949. * @param $noParse bool
  2950. * @return string the tagged text
  2951. */
  2952. function markNoConversion( $text, $noParse = false ) {
  2953. return $this->mConverter->markNoConversion( $text, $noParse );
  2954. }
  2955. /**
  2956. * A regular expression to match legal word-trailing characters
  2957. * which should be merged onto a link of the form [[foo]]bar.
  2958. *
  2959. * @return string
  2960. */
  2961. function linkTrail() {
  2962. return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
  2963. }
  2964. /**
  2965. * @return Language
  2966. */
  2967. function getLangObj() {
  2968. return $this;
  2969. }
  2970. /**
  2971. * Get the RFC 3066 code for this language object
  2972. *
  2973. * @return string
  2974. */
  2975. function getCode() {
  2976. return $this->mCode;
  2977. }
  2978. /**
  2979. * @param $code string
  2980. */
  2981. function setCode( $code ) {
  2982. $this->mCode = $code;
  2983. }
  2984. /**
  2985. * Get the name of a file for a certain language code
  2986. * @param $prefix string Prepend this to the filename
  2987. * @param $code string Language code
  2988. * @param $suffix string Append this to the filename
  2989. * @return string $prefix . $mangledCode . $suffix
  2990. */
  2991. static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
  2992. // Protect against path traversal
  2993. if ( !Language::isValidCode( $code )
  2994. || strcspn( $code, ":/\\\000" ) !== strlen( $code ) )
  2995. {
  2996. throw new MWException( "Invalid language code \"$code\"" );
  2997. }
  2998. return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
  2999. }
  3000. /**
  3001. * Get the language code from a file name. Inverse of getFileName()
  3002. * @param $filename string $prefix . $languageCode . $suffix
  3003. * @param $prefix string Prefix before the language code
  3004. * @param $suffix string Suffix after the language code
  3005. * @return string Language code, or false if $prefix or $suffix isn't found
  3006. */
  3007. static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
  3008. $m = null;
  3009. preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
  3010. preg_quote( $suffix, '/' ) . '/', $filename, $m );
  3011. if ( !count( $m ) ) {
  3012. return false;
  3013. }
  3014. return str_replace( '_', '-', strtolower( $m[1] ) );
  3015. }
  3016. /**
  3017. * @param $code string
  3018. * @return string
  3019. */
  3020. static function getMessagesFileName( $code ) {
  3021. global $IP;
  3022. return self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
  3023. }
  3024. /**
  3025. * @param $code string
  3026. * @return string
  3027. */
  3028. static function getClassFileName( $code ) {
  3029. global $IP;
  3030. return self::getFileName( "$IP/languages/classes/Language", $code, '.php' );
  3031. }
  3032. /**
  3033. * Get the fallback for a given language
  3034. *
  3035. * @param $code string
  3036. *
  3037. * @return false|string
  3038. */
  3039. static function getFallbackFor( $code ) {
  3040. global $I18N;
  3041. if ( $code === 'en' ) {
  3042. // Shortcut
  3043. return false;
  3044. } else {
  3045. return $I18N->getLangFallback( $code );
  3046. //return self::getLocalisationCache()->getItem( $code, 'fallback' );
  3047. }
  3048. }
  3049. /**
  3050. * Get all messages for a given language
  3051. * WARNING: this may take a long time
  3052. *
  3053. * @param $code string
  3054. *
  3055. * @return array
  3056. */
  3057. static function getMessagesFor( $code ) {
  3058. return self::getLocalisationCache()->getItem( $code, 'messages' );
  3059. }
  3060. /**
  3061. * Get a message for a given language
  3062. *
  3063. * @param $key string
  3064. * @param $code string
  3065. *
  3066. * @return string
  3067. */
  3068. static function getMessageFor( $key, $code ) {
  3069. return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
  3070. }
  3071. /**
  3072. * @param $talk
  3073. * @return mixed
  3074. */
  3075. function fixVariableInNamespace( $talk ) {
  3076. if ( strpos( $talk, '$1' ) === false ) {
  3077. return $talk;
  3078. }
  3079. global $wgMetaNamespace;
  3080. $talk = str_replace( '$1', $wgMetaNamespace, $talk );
  3081. # Allow grammar transformations
  3082. # Allowing full message-style parsing would make simple requests
  3083. # such as action=raw much more expensive than they need to be.
  3084. # This will hopefully cover most cases.
  3085. $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
  3086. array( &$this, 'replaceGrammarInNamespace' ), $talk );
  3087. return str_replace( ' ', '_', $talk );
  3088. }
  3089. /**
  3090. * @param $m string
  3091. * @return string
  3092. */
  3093. function replaceGrammarInNamespace( $m ) {
  3094. return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
  3095. }
  3096. /**
  3097. * @throws MWException
  3098. * @return array
  3099. */
  3100. static function getCaseMaps() {
  3101. static $wikiUpperChars, $wikiLowerChars;
  3102. if ( isset( $wikiUpperChars ) ) {
  3103. return array( $wikiUpperChars, $wikiLowerChars );
  3104. }
  3105. wfProfileIn( __METHOD__ );
  3106. $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
  3107. if ( $arr === false ) {
  3108. throw new MWException(
  3109. "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
  3110. }
  3111. $wikiUpperChars = $arr['wikiUpperChars'];
  3112. $wikiLowerChars = $arr['wikiLowerChars'];
  3113. wfProfileOut( __METHOD__ );
  3114. return array( $wikiUpperChars, $wikiLowerChars );
  3115. }
  3116. /**
  3117. * Decode an expiry (block, protection, etc) which has come from the DB
  3118. *
  3119. * @param $expiry String: Database expiry String
  3120. * @param $format Bool|Int true to process using language functions, or TS_ constant
  3121. * to return the expiry in a given timestamp
  3122. * @return String
  3123. */
  3124. public function formatExpiry( $expiry, $format = true ) {
  3125. static $infinity, $infinityMsg;
  3126. if( $infinity === null ){
  3127. $infinityMsg = wfMessage( 'infiniteblock' );
  3128. $infinity = wfGetDB( DB_SLAVE )->getInfinity();
  3129. }
  3130. if ( $expiry == '' || $expiry == $infinity ) {
  3131. return $format === true
  3132. ? $infinityMsg
  3133. : $infinity;
  3134. } else {
  3135. return $format === true
  3136. ? $this->timeanddate( $expiry )
  3137. : wfTimestamp( $format, $expiry );
  3138. }
  3139. }
  3140. /**
  3141. * @todo Document
  3142. * @param $seconds int|float
  3143. * @param $format String Optional, one of ("avoidseconds","avoidminutes"):
  3144. * "avoidseconds" - don't mention seconds if $seconds >= 1 hour
  3145. * "avoidminutes" - don't mention seconds/minutes if $seconds > 2 days
  3146. * @return string
  3147. */
  3148. function formatTimePeriod( $seconds, $format = false ) {
  3149. if ( round( $seconds * 10 ) < 100 ) {
  3150. return $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) ) .
  3151. $this->getMessageFromDB( 'seconds-abbrev' );
  3152. } elseif ( round( $seconds ) < 60 ) {
  3153. return $this->formatNum( round( $seconds ) ) .
  3154. $this->getMessageFromDB( 'seconds-abbrev' );
  3155. } elseif ( round( $seconds ) < 3600 ) {
  3156. $minutes = floor( $seconds / 60 );
  3157. $secondsPart = round( fmod( $seconds, 60 ) );
  3158. if ( $secondsPart == 60 ) {
  3159. $secondsPart = 0;
  3160. $minutes++;
  3161. }
  3162. return $this->formatNum( $minutes ) . $this->getMessageFromDB( 'minutes-abbrev' ) .
  3163. ' ' .
  3164. $this->formatNum( $secondsPart ) . $this->getMessageFromDB( 'seconds-abbrev' );
  3165. } elseif ( round( $seconds ) <= 2*86400 ) {
  3166. $hours = floor( $seconds / 3600 );
  3167. $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
  3168. $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
  3169. if ( $secondsPart == 60 ) {
  3170. $secondsPart = 0;
  3171. $minutes++;
  3172. }
  3173. if ( $minutes == 60 ) {
  3174. $minutes = 0;
  3175. $hours++;
  3176. }
  3177. $s = $this->formatNum( $hours ) . $this->getMessageFromDB( 'hours-abbrev' ) .
  3178. ' ' .
  3179. $this->formatNum( $minutes ) . $this->getMessageFromDB( 'minutes-abbrev' );
  3180. if ( $format !== 'avoidseconds' ) {
  3181. $s .= ' ' . $this->formatNum( $secondsPart ) .
  3182. $this->getMessageFromDB( 'seconds-abbrev' );
  3183. }
  3184. return $s;
  3185. } else {
  3186. $days = floor( $seconds / 86400 );
  3187. $s = $this->formatNum( $days ) . $this->getMessageFromDB( 'days-abbrev' ) . ' ';
  3188. if ( $format === 'avoidminutes' ) {
  3189. $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
  3190. $s .= $this->formatNum( $hours ) . $this->getMessageFromDB( 'hours-abbrev' );
  3191. } else {
  3192. $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
  3193. }
  3194. return $s;
  3195. }
  3196. }
  3197. /**
  3198. * @param $bps int
  3199. * @return string
  3200. */
  3201. function formatBitrate( $bps ) {
  3202. $units = array( 'bps', 'kbps', 'Mbps', 'Gbps' );
  3203. if ( $bps <= 0 ) {
  3204. return $this->formatNum( $bps ) . $units[0];
  3205. }
  3206. $unitIndex = floor( log10( $bps ) / 3 );
  3207. $mantissa = $bps / pow( 1000, $unitIndex );
  3208. if ( $mantissa < 10 ) {
  3209. $mantissa = round( $mantissa, 1 );
  3210. } else {
  3211. $mantissa = round( $mantissa );
  3212. }
  3213. return $this->formatNum( $mantissa ) . $units[$unitIndex];
  3214. }
  3215. /**
  3216. * Format a size in bytes for output, using an appropriate
  3217. * unit (B, KB, MB or GB) according to the magnitude in question
  3218. *
  3219. * @param $size Size to format
  3220. * @return string Plain text (not HTML)
  3221. */
  3222. function formatSize( $size ) {
  3223. // For small sizes no decimal places necessary
  3224. $round = 0;
  3225. if ( $size > 1024 ) {
  3226. $size = $size / 1024;
  3227. if ( $size > 1024 ) {
  3228. $size = $size / 1024;
  3229. // For MB and bigger two decimal places are smarter
  3230. $round = 2;
  3231. if ( $size > 1024 ) {
  3232. $size = $size / 1024;
  3233. $msg = 'size-gigabytes';
  3234. } else {
  3235. $msg = 'size-megabytes';
  3236. }
  3237. } else {
  3238. $msg = 'size-kilobytes';
  3239. }
  3240. } else {
  3241. $msg = 'size-bytes';
  3242. }
  3243. $size = round( $size, $round );
  3244. $text = $this->getMessageFromDB( $msg );
  3245. return str_replace( '$1', $this->formatNum( $size ), $text );
  3246. }
  3247. /**
  3248. * Get the conversion rule title, if any.
  3249. *
  3250. * @return string
  3251. */
  3252. function getConvRuleTitle() {
  3253. return $this->mConverter->getConvRuleTitle();
  3254. }
  3255. }