00001 <?php
00022 class SpecialSupportedLanguages extends SpecialPage {
00024 protected $purge = false;
00025
00027 protected $period = 180;
00028
00029 public function __construct() {
00030 parent::__construct( 'SupportedLanguages' );
00031 }
00032
00033 public function execute( $par ) {
00034 $out = $this->getOutput();
00035 $lang = $this->getLanguage();
00036
00037 $this->purge = $this->getRequest()->getVal( 'action' ) === 'purge';
00038
00039 $this->setHeaders();
00040 $out->addModules( 'ext.translate.special.supportedlanguages' );
00041
00042
00043 $cache = wfGetCache( CACHE_ANYTHING );
00044 $cachekey = wfMemcKey( 'translate-supportedlanguages', $lang->getCode() );
00045 $data = $cache->get( $cachekey );
00046 if ( !$this->purge && is_string( $data ) ) {
00047 TranslateUtils::addSpecialHelpLink(
00048 $out,
00049 'Help:Extension:Translate/Statistics_and_reporting#List_of_languages_and_translators'
00050 );
00051 $out->addHtml( $data );
00052
00053 return;
00054 }
00055
00056 TranslateUtils::addSpecialHelpLink(
00057 $out,
00058 'Help:Extension:Translate/Statistics_and_reporting#List_of_languages_and_translators'
00059 );
00060
00061 $this->outputHeader();
00062 $dbr = wfGetDB( DB_SLAVE );
00063 if ( $dbr->getType() === 'sqlite' ) {
00064 $out->addWikiText( '<div class=errorbox>SQLite is not supported.</div>' );
00065
00066 return;
00067 }
00068
00069 $out->addWikiMsg( 'supportedlanguages-colorlegend', $this->getColorLegend() );
00070 $out->addWikiMsg( 'supportedlanguages-localsummary' );
00071
00072
00073 $cldrInstalled = class_exists( 'LanguageNames' );
00074
00075 $locals = array();
00076 if ( $cldrInstalled ) {
00077 $locals = LanguageNames::getNames( $lang->getCode(),
00078 LanguageNames::FALLBACK_NORMAL,
00079 LanguageNames::LIST_MW_AND_CLDR
00080 );
00081 }
00082
00083 $natives = Language::getLanguageNames( false );
00084 ksort( $natives );
00085
00086 $this->outputLanguageCloud( $natives );
00087
00088
00089 if ( !defined( 'NS_PORTAL' ) ) {
00090 $users = $this->fetchTranslatorsAuto();
00091 } else {
00092 $users = $this->fetchTranslatorsPortal( $natives );
00093 }
00094
00095 $this->preQueryUsers( $users );
00096
00097 list( $editcounts, $lastedits ) = $this->getUserStats();
00098
00099
00100 $linkInfo['rc']['title'] = SpecialPage::getTitleFor( 'Recentchanges' );
00101 $linkInfo['rc']['msg'] = $this->msg( 'supportedlanguages-recenttranslations' )->escaped();
00102 $linkInfo['stats']['title'] = SpecialPage::getTitleFor( 'LanguageStats' );
00103 $linkInfo['stats']['msg'] = $this->msg( 'languagestats' )->escaped();
00104
00105 foreach ( array_keys( $natives ) as $code ) {
00106 if ( !isset( $users[$code] ) ) {
00107 continue;
00108 }
00109
00110
00111 if ( $cldrInstalled ) {
00112 $headerText = $this->msg( 'supportedlanguages-portallink' )
00113 ->params( $code, $locals[$code], $natives[$code] )->escaped();
00114 } else {
00115
00116 $headerText = $this->msg( 'supportedlanguages-portallink-nocldr' )
00117 ->params( $code, $natives[$code] )->escaped();
00118 }
00119
00120 $headerText = htmlspecialchars( $headerText );
00121
00122 $out->addHtml( Html::openElement( 'h2', array( 'id' => $code ) ) );
00123 if ( defined( 'NS_PORTAL' ) ) {
00124 $portalTitle = Title::makeTitleSafe( NS_PORTAL, $code );
00125 $out->addHtml( Linker::linkKnown( $portalTitle, $headerText ) );
00126 } else {
00127 $out->addHtml( $headerText );
00128 }
00129
00130 $out->addHTML( "</h2>" );
00131
00132
00133 $links = array();
00134 $links[] = Linker::link(
00135 $linkInfo['stats']['title'],
00136 $linkInfo['stats']['msg'],
00137 array(),
00138 array(
00139 'code' => $code,
00140 'suppresscomplete' => '1'
00141 ),
00142 array( 'known', 'noclasses' )
00143 );
00144 $links[] = Linker::link(
00145 $linkInfo['rc']['title'],
00146 $linkInfo['rc']['msg'],
00147 array(),
00148 array(
00149 'translations' => 'only',
00150 'trailer' => "/" . $code
00151 ),
00152 array( 'known', 'noclasses' )
00153 );
00154 $linkList = $lang->listToText( $links );
00155
00156 $out->addHTML( "<p>" . $linkList . "</p>\n" );
00157 $this->makeUserList( $users[$code], $editcounts, $lastedits );
00158 }
00159
00160 $out->addHtml( Html::element( 'hr' ) );
00161 $out->addWikiMsg( 'supportedlanguages-count', $lang->formatNum( count( $users ) ) );
00162
00163 $cache->set( $cachekey, $out->getHTML(), 3600 );
00164 }
00165
00166 protected function languageCloud() {
00167 global $wgTranslateMessageNamespaces;
00168
00169 $cache = wfGetCache( CACHE_ANYTHING );
00170 $cachekey = wfMemcKey( 'translate-supportedlanguages-language-cloud' );
00171 $data = $cache->get( $cachekey );
00172 if ( !$this->purge && is_array( $data ) ) {
00173 return $data;
00174 }
00175
00176 $dbr = wfGetDB( DB_SLAVE );
00177 $tables = array( 'recentchanges' );
00178 $fields = array( 'substring_index(rc_title, \'/\', -1) as lang', 'count(*) as count' );
00179 $timestamp = $dbr->timestamp( TS_DB, wfTimeStamp( TS_UNIX ) - 60 * 60 * 24 * $this->period );
00180 $conds = array(
00181 'rc_title' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() ),
00182 'rc_namespace' => $wgTranslateMessageNamespaces,
00183 'rc_timestamp > ' . $timestamp,
00184 );
00185 $options = array( 'GROUP BY' => 'lang', 'HAVING' => 'count > 20' );
00186
00187 $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options );
00188
00189 $data = array();
00190 foreach ( $res as $row ) {
00191 $data[$row->lang] = $row->count;
00192 }
00193
00194 $cache->set( $cachekey, $data, 3600 );
00195
00196 return $data;
00197 }
00198
00199 protected function fetchTranslatorsAuto() {
00200 global $wgTranslateMessageNamespaces;
00201
00202 $cache = wfGetCache( CACHE_ANYTHING );
00203 $cachekey = wfMemcKey( 'translate-supportedlanguages-translator-list' );
00204 $data = $cache->get( $cachekey );
00205 if ( !$this->purge && is_array( $data ) ) {
00206 return $data;
00207 }
00208
00209 $dbr = wfGetDB( DB_SLAVE );
00210 $tables = array( 'page', 'revision' );
00211 $fields = array(
00212 'rev_user_text',
00213 'substring_index(page_title, \'/\', -1) as lang',
00214 'count(page_id) as count'
00215 );
00216 $conds = array(
00217 'page_title' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() ),
00218 'page_namespace' => $wgTranslateMessageNamespaces,
00219 'page_id=rev_page',
00220 );
00221 $options = array( 'GROUP BY' => 'rev_user_text, lang' );
00222
00223 $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options );
00224
00225 $data = array();
00226 foreach ( $res as $row ) {
00227 $data[$row->lang][$row->rev_user_text] = $row->count;
00228 }
00229
00230 $cache->set( $cachekey, $data, 3600 );
00231
00232 return $data;
00233 }
00234
00235 public function fetchTranslatorsPortal( $natives ) {
00236 $titles = array();
00237 foreach ( $natives as $code => $_ ) {
00238 $titles[] = Title::capitalize( $code, NS_PORTAL ) . '/translators';
00239 }
00240
00241 $dbr = wfGetDB( DB_SLAVE );
00242 $tables = array( 'page', 'revision', 'text' );
00243 $vars = array_merge(
00244 Revision::selectTextFields(),
00245 array( 'page_title', 'page_namespace' ),
00246 Revision::selectFields()
00247 );
00248 $conds = array(
00249 'page_latest = rev_id',
00250 'rev_text_id = old_id',
00251 'page_namespace' => NS_PORTAL,
00252 'page_title' => $titles,
00253 );
00254
00255 $res = $dbr->select( $tables, $vars, $conds, __METHOD__ );
00256
00257 $users = array();
00258 $lb = new LinkBatch;
00259
00260 foreach ( $res as $row ) {
00261 $rev = new Revision( $row );
00262 $text = $rev->getText();
00263 $code = strtolower( preg_replace( '!/translators$!', '', $row->page_title ) );
00264
00265 preg_match_all( '!{{[Uu]ser\|([^}|]+)!', $text, $matches, PREG_SET_ORDER );
00266 foreach ( $matches as $match ) {
00267 $user = Title::capitalize( $match[1], NS_USER );
00268 $lb->add( NS_USER, $user );
00269 $lb->add( NS_USER_TALK, $user );
00270 if ( !isset( $users[$code] ) ) {
00271 $users[$code] = array();
00272 }
00273 $users[$code][strtr( $user, '_', ' ' )] = -1;
00274 }
00275 }
00276
00277 $lb->execute();
00278
00279 return $users;
00280 }
00281
00282 protected function outputLanguageCloud( $names ) {
00283 $out = $this->getOutput();
00284
00285 $langs = $this->languageCloud();
00286 $out->addHtml( '<div class="tagcloud autonym">' );
00287 $langs = $this->shuffle_assoc( $langs );
00288 foreach ( $langs as $k => $v ) {
00289 $name = isset( $names[$k] ) ? $names[$k] : $k;
00290 $size = round( log( $v ) * 20 ) + 10;
00291
00292 $params = array(
00293 'href' => "#$k",
00294 'class' => 'tag',
00295 'style' => "font-size:$size%",
00296 'lang' => $k,
00297 );
00298
00299 $tag = Html::element( 'a', $params, $name );
00300 $out->addHtml( $tag . "\n" );
00301 }
00302 $out->addHtml( '</div>' );
00303 }
00304
00305 protected function makeUserList( $users, $editcounts, $lastedits ) {
00306 $day = 60 * 60 * 24;
00307
00308
00309
00310 $period = $this->period;
00311
00312 $links = array();
00313 $statsTable = new StatsTable();
00314
00315 foreach ( $users as $username => $count ) {
00316 $title = Title::makeTitleSafe( NS_USER, $username );
00317 $enc = htmlspecialchars( $username );
00318
00319 $attribs = array();
00320 $styles = array();
00321 if ( isset( $editcounts[$username] ) ) {
00322 if ( $count === -1 ) {
00323 $count = $editcounts[$username];
00324 }
00325
00326 $styles['font-size'] = round( log( $count, 10 ) * 30 ) + 70 . '%';
00327
00328 $last = wfTimestamp( TS_UNIX ) - $lastedits[$username];
00329 $last = round( $last / $day );
00330 $attribs['title'] = $this->msg( 'supportedlanguages-activity', $username )
00331 ->numParams( $count, $last )->text();
00332 $last = max( 1, min( $period, $last ) );
00333 $styles['border-bottom'] = '3px solid #' .
00334 $statsTable->getBackgroundColor( $period - $last, $period );
00335 } else {
00336 $enc = "<del>$enc</del>";
00337 }
00338
00339 $stylestr = $this->formatStyle( $styles );
00340 if ( $stylestr ) {
00341 $attribs['style'] = $stylestr;
00342 }
00343
00344 $links[] = Linker::link( $title, $enc, $attribs );
00345 }
00346
00347 $linkList = $this->getLanguage()->listToText( $links );
00348 $html = "<p class='mw-translate-spsl-translators'>";
00349 $html .= $this->msg(
00350 'supportedlanguages-translators',
00351 $linkList,
00352 count( $links )
00353 )->text();
00354 $html .= "</p>\n";
00355 $this->getOutput()->addHTML( $html );
00356 }
00357
00358 protected function getUserStats() {
00359 $cache = wfGetCache( CACHE_ANYTHING );
00360 $cachekey = wfMemcKey( 'translate-supportedlanguages-userstats' );
00361 $data = $cache->get( $cachekey );
00362 if ( !$this->purge && is_array( $data ) ) {
00363 return $data;
00364 }
00365
00366 $dbr = wfGetDB( DB_SLAVE );
00367 $editcounts = $lastedits = array();
00368 $tables = array( 'user', 'revision' );
00369 $fields = array( 'user_name', 'user_editcount', 'MAX(rev_timestamp) as lastedit' );
00370 $conds = array( 'user_id = rev_user' );
00371 $options = array( 'GROUP BY' => 'user_name' );
00372
00373 $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options );
00374 foreach ( $res as $row ) {
00375 $editcounts[$row->user_name] = $row->user_editcount;
00376 $lastedits[$row->user_name] = wfTimestamp( TS_UNIX, $row->lastedit );
00377 }
00378
00379 $data = array( $editcounts, $lastedits );
00380 $cache->set( $cachekey, $data, 3600 );
00381
00382 return $data;
00383 }
00384
00385 protected function formatStyle( $styles ) {
00386 $stylestr = '';
00387 foreach ( $styles as $key => $value ) {
00388 $stylestr .= "$key:$value;";
00389 }
00390
00391 return $stylestr;
00392 }
00393
00394 function shuffle_assoc( $list ) {
00395 if ( !is_array( $list ) ) {
00396 return $list;
00397 }
00398
00399 $keys = array_keys( $list );
00400 shuffle( $keys );
00401 $random = array();
00402 foreach ( $keys as $key )
00403 $random[$key] = $list[$key];
00404
00405 return $random;
00406 }
00407
00408 protected function preQueryUsers( $users ) {
00409 $lb = new LinkBatch;
00410 foreach ( $users as $translators ) {
00411 foreach ( $translators as $user => $count ) {
00412 $user = Title::capitalize( $user, NS_USER );
00413 $lb->add( NS_USER, $user );
00414 $lb->add( NS_USER_TALK, $user );
00415 }
00416 }
00417 $lb->execute();
00418 }
00419
00420 protected function getColorLegend() {
00421 $legend = '';
00422 $period = $this->period;
00423 $statsTable = new StatsTable();
00424
00425 for ( $i = 0; $i <= $period; $i += 30 ) {
00426 $iFormatted = htmlspecialchars( $this->getLanguage()->formatNum( $i ) );
00427 $legend .= '<span style="background-color:#' .
00428 $statsTable->getBackgroundColor( $period - $i, $period ) .
00429 "\"> $iFormatted</span>";
00430 }
00431
00432 return $legend;
00433 }
00434 }