Partly disable title conversion if variant == main language code
[lhc/web/wiklou.git] / languages / Language.php
index 416b1cf..84e7f37 100644 (file)
@@ -275,7 +275,7 @@ class Language {
                $alpha = '[a-z]';
                $digit = '[0-9]';
                $alphanum = '[a-z0-9]';
-               $x = 'x' ; # private use singleton
+               $x = 'x'; # private use singleton
                $singleton = '[a-wy-z]'; # other singleton
                $s = $lenient ? '[-_]' : '-';
 
@@ -326,13 +326,19 @@ class Language {
         * @return bool
         */
        public static function isValidCode( $code ) {
-               return
-                       // People think language codes are html safe, so enforce it.
-                       // Ideally we should only allow a-zA-Z0-9-
-                       // but, .+ and other chars are often used for {{int:}} hacks
-                       // see bugs 37564, 37587, 36938
+               static $cache = array();
+               if( isset( $cache[$code] ) ) {
+                       return $cache[$code];
+               }
+               // People think language codes are html safe, so enforce it.
+               // Ideally we should only allow a-zA-Z0-9-
+               // but, .+ and other chars are often used for {{int:}} hacks
+               // see bugs 37564, 37587, 36938
+               $return =
                        strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
                        && !preg_match( Title::getTitleInvalidRegex(), $code );
+               $cache[ $code ] = $return;
+               return $return;
        }
 
        /**
@@ -482,6 +488,9 @@ class Language {
        }
 
        /**
+        * Returns an array of localised namespaces indexed by their numbers. If the namespace is not
+        * available in localised form, it will be included in English.
+        *
         * @return array
         */
        public function getNamespaces() {
@@ -1075,6 +1084,7 @@ class Language {
         * @param $zone DateTimeZone: Timezone of $ts
         * @todo handling of "o" format character for Iranian, Hebrew, Hijri & Thai?
         *
+        * @throws MWException
         * @return string
         */
        function sprintfDate( $format, $ts, DateTimeZone $zone = null ) {
@@ -1090,6 +1100,15 @@ class Language {
                $thai = false;
                $minguo = false;
                $tenno = false;
+
+               if ( strlen( $ts ) !== 14 ) {
+                       throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
+               }
+
+               if ( !ctype_digit( $ts ) ) {
+                       throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
+               }
+
                for ( $p = 0; $p < strlen( $format ); $p++ ) {
                        $num = false;
                        $code = $format[$p];
@@ -1121,7 +1140,9 @@ class Language {
                                        $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
                                        break;
                                case 'xjx':
-                                       if ( !$hebrew ) $hebrew = self::tsToHebrew( $ts );
+                                       if ( !$hebrew ) {
+                                               $hebrew = self::tsToHebrew( $ts );
+                                       }
                                        $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
                                        break;
                                case 'd':
@@ -1811,13 +1832,13 @@ class Language {
                }
                if ( strlen( $s ) == 2 ) {
                        $str = $s . "'";
-               } else  {
+               } else {
                        $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
                        $str .= substr( $s, strlen( $s ) - 2, 2 );
                }
                $start = substr( $str, 0, strlen( $str ) - 2 );
                $end = substr( $str, strlen( $str ) - 2 );
-               switch( $end ) {
+               switch ( $end ) {
                        case 'כ':
                                $str = $start . 'ך';
                                break;
@@ -1900,12 +1921,12 @@ class Language {
                # will normalize out-of-range values so we don't have to split $minDiff
                # into hours and minutes.
                $t = mktime( (
-                 (int)substr( $ts, 8, 2 ) ), # Hours
-                 (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
-                 (int)substr( $ts, 12, 2 ), # Seconds
-                 (int)substr( $ts, 4, 2 ), # Month
-                 (int)substr( $ts, 6, 2 ), # Day
-                 (int)substr( $ts, 0, 4 ) ); # Year
+                       (int)substr( $ts, 8, 2 ) ), # Hours
+                       (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
+                       (int)substr( $ts, 12, 2 ), # Seconds
+                       (int)substr( $ts, 4, 2 ), # Month
+                       (int)substr( $ts, 6, 2 ), # Day
+                       (int)substr( $ts, 0, 4 ) ); # Year
 
                $date = date( 'YmdHis', $t );
                wfRestoreWarnings();
@@ -1956,6 +1977,8 @@ class Language {
         * @param $type string May be date, time or both
         * @param $pref string The format name as it appears in Messages*.php
         *
+        * @since 1.22 New type 'pretty' that provides a more readable timestamp format
+        *
         * @return string
         */
        function getDateFormatString( $type, $pref ) {
@@ -1965,7 +1988,12 @@ class Language {
                                $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
                        } else {
                                $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
-                               if ( is_null( $df ) ) {
+
+                               if ( $type === 'pretty' && $df === null ) {
+                                       $df = $this->getDateFormatString( 'date', $pref );
+                               }
+
+                               if ( $df === null ) {
                                        $pref = $this->getDefaultDateFormat();
                                        $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
                                }
@@ -2198,6 +2226,79 @@ class Language {
                return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
        }
 
+       /**
+        * Convert an MWTimestamp into a pretty human-readable timestamp using
+        * the given user preferences and relative base time.
+        *
+        * DO NOT USE THIS FUNCTION DIRECTLY. Instead, call MWTimestamp::getHumanTimestamp
+        * on your timestamp object, which will then call this function. Calling
+        * this function directly will cause hooks to be skipped over.
+        *
+        * @see MWTimestamp::getHumanTimestamp
+        * @param MWTimestamp $ts Timestamp to prettify
+        * @param MWTimestamp $relativeTo Base timestamp
+        * @param User $user User preferences to use
+        * @return string Human timestamp
+        * @since 1.21
+        */
+       public function getHumanTimestamp( MWTimestamp $ts, MWTimestamp $relativeTo, User $user ) {
+               $diff = $ts->diff( $relativeTo );
+               $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) - (int)$relativeTo->timestamp->format( 'w' ) );
+               $days = $diff->days ?: (int)$diffDay;
+               if ( $diff->invert || $days > 5 && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' ) ) {
+                       // Timestamps are in different years: use full timestamp
+                       // Also do full timestamp for future dates
+                       /**
+                        * @FIXME Add better handling of future timestamps.
+                        */
+                       $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
+                       $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
+               } elseif ( $days > 5 ) {
+                       // Timestamps are in same year,  but more than 5 days ago: show day and month only.
+                       $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
+                       $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
+               } elseif ( $days > 1 ) {
+                       // Timestamp within the past week: show the day of the week and time
+                       $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
+                       $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
+                       $ts = wfMessage( "$weekday-at" )
+                               ->inLanguage( $this )
+                               ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
+                               ->text();
+               } elseif ( $days == 1 ) {
+                       // Timestamp was yesterday: say 'yesterday' and the time.
+                       $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
+                       $ts = wfMessage( 'yesterday-at' )
+                               ->inLanguage( $this )
+                               ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
+                               ->text();
+               } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
+                       // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
+                       $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
+                       $ts = wfMessage( 'today-at' )
+                               ->inLanguage( $this )
+                               ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
+                               ->text();
+
+               // From here on in, the timestamp was soon enough ago so that we can simply say
+               // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
+               } elseif ( $diff->h == 1 ) {
+                       // Less than 90 minutes, but more than an hour ago.
+                       $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
+               } elseif ( $diff->i >= 1 ) {
+                       // A few minutes ago.
+                       $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
+               } elseif ( $diff->s >= 30 ) {
+                       // Less than a minute, but more than 30 sec ago.
+                       $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
+               } else {
+                       // Less than 30 seconds ago.
+                       $ts = wfMessage( 'just-now' )->text();
+               }
+
+               return $ts;
+       }
+
        /**
         * @param $key string
         * @return array|null
@@ -2781,7 +2882,9 @@ class Language {
         * @since 1.20
         */
        function getDirMarkEntity( $opposite = false ) {
-               if ( $opposite ) { return $this->isRTL() ? '&lrm;' : '&rlm;'; }
+               if ( $opposite ) {
+                       return $this->isRTL() ? '&lrm;' : '&rlm;';
+               }
                return $this->isRTL() ? '&rlm;' : '&lrm;';
        }
 
@@ -2798,7 +2901,9 @@ class Language {
        function getDirMark( $opposite = false ) {
                $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
                $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
-               if ( $opposite ) { return $this->isRTL() ? $lrm : $rlm; }
+               if ( $opposite ) {
+                       return $this->isRTL() ? $lrm : $rlm;
+               }
                return $this->isRTL() ? $rlm : $lrm;
        }
 
@@ -3030,20 +3135,20 @@ class Language {
                        $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
                        preg_match( "/\d+/", $number, $integerPart );
                        preg_match( "/\.\d*/", $number, $decimalPart );
-                       $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0]:"";
+                       $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
                        if ( $groupedNumber === $number ) {
                                // the string does not have any number part. Eg: .12345
                                return $sign . $groupedNumber;
                        }
                        $start = $end = strlen( $integerPart[0] );
                        while ( $start > 0 ) {
-                               $match = $matches[0][$numMatches -1] ;
+                               $match = $matches[0][$numMatches - 1];
                                $matchLen = strlen( $match );
                                $start = $end - $matchLen;
                                if ( $start < 0 ) {
                                        $start = 0;
                                }
-                               $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber ;
+                               $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber;
                                $end = $start;
                                if ( $numMatches > 1 ) {
                                        // use the last pattern for the rest of the number
@@ -3220,9 +3325,9 @@ class Language {
                                # We got the first byte only of a multibyte char; remove it.
                                $string = substr( $string, 0, -1 );
                        } elseif ( $char >= 0x80 &&
-                                 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
-                                                         '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) )
-                       {
+                               preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
+                                       '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m )
+                       {
                                # We chopped in the middle of a character; remove it
                                $string = $m[1];
                        }
@@ -3306,7 +3411,9 @@ class Language {
                                        break;
                                }
                        }
-                       if ( $pos >= $textLen ) break; // extra iteration just for above checks
+                       if ( $pos >= $textLen ) {
+                               break; // extra iteration just for above checks
+                       }
 
                        # Read the next char...
                        $ch = $text[$pos];
@@ -3439,7 +3546,7 @@ class Language {
        function getGrammarForms() {
                global $wgGrammarForms;
                if ( isset( $wgGrammarForms[$this->getCode()] ) && is_array( $wgGrammarForms[$this->getCode()] ) ) {
-                        return $wgGrammarForms[$this->getCode()];
+                       return $wgGrammarForms[$this->getCode()];
                }
                return array();
        }
@@ -4048,15 +4155,14 @@ class Language {
         * @since 1.18
         */
        public function formatExpiry( $expiry, $format = true ) {
-               static $infinity, $infinityMsg;
+               static $infinity;
                if ( $infinity === null ) {
-                       $infinityMsg = wfMessage( 'infiniteblock' );
                        $infinity = wfGetDB( DB_SLAVE )->getInfinity();
                }
 
                if ( $expiry == '' || $expiry == $infinity ) {
                        return $format === true
-                               ? $infinityMsg
+                               ? $this->getMessageFromDB( 'infiniteblock' )
                                : $infinity;
                } else {
                        return $format === true