r68580 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r68579‎ | r68580 | r68581 >
Date:18:48, 25 June 2010
Author:kaldari
Status:resolved (Comments)
Tags:
Comment:
CentralNotice: adding support for multiple languages per notice, Bug 20229
Modified paths:
  • /trunk/extensions/CentralNotice/CentralNotice.db.php (modified) (history)
  • /trunk/extensions/CentralNotice/SpecialCentralNotice.php (modified) (history)
  • /trunk/extensions/CentralNotice/SpecialNoticeText.php (modified) (history)

Diff [purge]

Index: trunk/extensions/CentralNotice/SpecialCentralNotice.php
@@ -178,22 +178,22 @@
179179 }
180180 }
181181
182 - // Handle adding
 182+ // Handle adding of notice
183183 $this->showAll = $wgRequest->getVal( 'showAll' );
184184 if ( $this->editable && $method == 'addNotice' ) {
185 - $noticeName = $wgRequest->getVal( 'noticeName' );
186 - $start = $wgRequest->getArray( 'start' );
187 - $project_name = $wgRequest->getVal( 'project_name' );
188 - $project_language = $wgRequest->getVal( 'wpUserLanguage' );
 185+ $noticeName = $wgRequest->getVal( 'noticeName' );
 186+ $start = $wgRequest->getArray( 'start' );
 187+ $project_name = $wgRequest->getVal( 'project_name' );
 188+ $project_languages = $wgRequest->getArray( 'project_languages' );
189189 if ( $noticeName == '' ) {
190190 $wgOut->addWikiMsg ( 'centralnotice-null-string' );
191191 }
192192 else {
193 - $this->addNotice( $noticeName, '0', $start, $project_name, $project_language );
 193+ $this->addNotice( $noticeName, '0', $start, $project_name, $project_languages );
194194 }
195195 }
196196
197 - // Handle removing
 197+ // Handle removing of notice
198198 if ( $this->editable && $method == 'removeNotice' ) {
199199 $noticeName = $wgRequest->getVal ( 'noticeName' );
200200 $this->removeNotice ( $noticeName );
@@ -370,7 +370,7 @@
371371
372372 // If all languages should be shown
373373 if ( isset( $this->showAll ) ) {
374 - // Get only notices for all languages
 374+ // Get notices for all languages
375375 $res = $dbr->select( 'cn_notices',
376376 array(
377377 'not_name',
@@ -379,7 +379,6 @@
380380 'not_enabled',
381381 'not_preferred',
382382 'not_project',
383 - 'not_language',
384383 'not_locked'
385384 ),
386385 null,
@@ -451,10 +450,16 @@
452451 // Project
453452 $fields[] = htmlspecialchars( $this->getProjectName( $row->not_project ) );
454453
455 - // Language
456 - if ( isset ( $this->showAll ) ) {
457 - $fields[] = htmlspecialchars( $row->not_language );
 454+ // Languages
 455+ $project_langs = array();
 456+ $project_langs = $this->getNoticeLanguages( $row->not_name );
 457+ $language_count = count( $project_langs );
 458+ if ( $language_count > 1 ) {
 459+ $languageList = "multiple ($language_count)";
 460+ } else {
 461+ $languageList = $project_langs[0];
458462 }
 463+ $fields[] = $languageList;
459464
460465 // Date and time calculations
461466 $start_timestamp = wfTimestamp( TS_MW, $row->not_start );
@@ -539,42 +544,39 @@
540545 $htmlOut .= Xml::hidden( 'method', 'addNotice' );
541546
542547 $htmlOut .= Xml::openElement( 'table', array ( 'cellpadding' => 9 ) );
 548+
 549+ // Name
 550+ $htmlOut .= Xml::openElement( 'tr' );
 551+ $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-notice-name' ) );
 552+ $htmlOut .= Xml::tags( 'td', array(), Xml::inputLabel( '', 'noticeName', 'noticeName', 25 ) );
 553+ $htmlOut .= Xml::closeElement( 'tr' );
 554+ // Start Date
 555+ $htmlOut .= Xml::openElement( 'tr' );
 556+ $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-start-date' ) );
 557+ $htmlOut .= Xml::tags( 'td', array(), $this->dateSelector( 'start' ) );
 558+ $htmlOut .= Xml::closeElement( 'tr' );
 559+ // Start Time
 560+ $htmlOut .= Xml::openElement( 'tr' );
 561+ $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-start-hour' ) . "(GMT)" );
 562+ $htmlOut .= Xml::tags( 'td', array(), $this->timeSelector( 'start' ) );
 563+ $htmlOut .= Xml::closeElement( 'tr' );
 564+ // Project
 565+ $htmlOut .= Xml::openElement( 'tr' );
 566+ $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-project-name' ) );
 567+ $htmlOut .= Xml::tags( 'td', array(), $this->projectDropDownList() );
 568+ $htmlOut .= Xml::closeElement( 'tr' );
 569+ // Languages
 570+ $htmlOut .= Xml::openElement( 'tr' );
 571+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ), wfMsgHtml( 'yourlanguage' ) );
 572+ $htmlOut .= Xml::tags( 'td', array(), $this->languageMultiSelector() );
 573+ $htmlOut .= Xml::closeElement( 'tr' );
 574+ // Submit
 575+ $htmlOut .= Xml::openElement( 'tr' );
 576+ $htmlOut .= Xml::tags( 'td', array( 'colspan' => '2' ), Xml::submitButton( wfMsg( 'centralnotice-modify' ) ) );
 577+ $htmlOut .= Xml::closeElement( 'tr' );
543578
544 - $table = array(
545 - // Name
546 - array(
547 - wfMsgHtml( 'centralnotice-notice-name' ),
548 - Xml::inputLabel( '', 'noticeName', 'noticeName', 25 ),
549 - ),
550 - // Start Date
551 - array(
552 - Xml::label( wfMsg( 'centralnotice-start-date' ), 'start-date' ),
553 - $this->dateSelector( 'start' ),
554 - ),
555 - // Start Time
556 - array(
557 - wfMsgHtml( 'centralnotice-start-hour' ) . "(GMT)",
558 - $this->timeSelector( 'start' ),
559 - ),
560 - // Project
561 - array(
562 - wfMsgHtml( 'centralnotice-project-name' ),
563 - $this->projectDropDownList(),
564 - ),
565 - // Languages + All
566 - $this->languageDropDownList( $wgUserLang ),
567 - // Submit
568 - array(
569 - Xml::submitButton( wfMsg( 'centralnotice-modify' ) ),
570 - ),
571 - );
572 -
573 - foreach ( $table as $cells ) {
574 - $htmlOut .= $this->tableRow( $cells );
575 - }
576 -
 579+ $htmlOut .= Xml::closeElement( 'table' );
577580 $htmlOut .= Xml::hidden( 'change', 'weight' );
578 - $htmlOut .= Xml::closeElement( 'table' );
579581 $htmlOut .= Xml::closeElement( 'fieldset' );
580582 $htmlOut .= Xml::closeElement( 'form' );
581583 }
@@ -585,7 +587,6 @@
586588
587589 function listNoticeDetail( $notice ) {
588590 global $wgOut, $wgRequest, $wgUser;
589 -
590591 if ( $wgRequest->wasPosted() ) {
591592 // Handle removing of templates
592593 $templateToRemove = $wgRequest->getArray( 'removeTemplates' );
@@ -601,10 +602,10 @@
602603 $this->updateProjectName ( $notice, $projectName );
603604 }
604605
605 - // Handle new user language
606 - $projectLang = $wgRequest->getVal( 'wpUserLanguage' );
607 - if ( isset( $projectLang ) ) {
608 - $this->updateProjectLanguage( $notice, $projectLang );
 606+ // Handle new project languages
 607+ $projectLangs = $wgRequest->getArray( 'project_languages' );
 608+ if ( isset( $projectLangs ) ) {
 609+ $this->updateProjectLanguages( $notice, $projectLangs );
609610 }
610611
611612 // Handle adding of templates
@@ -693,13 +694,20 @@
694695 'not_enabled',
695696 'not_preferred',
696697 'not_project',
697 - 'not_language',
698698 'not_locked'
699699 ),
700700 array( 'not_name' => $notice ),
701 - __METHOD__,
702 - array( 'ORDER BY' => 'not_id' )
 701+ __METHOD__
703702 );
 703+ $res = $dbr->select( 'cn_notice_languages',
 704+ 'not_language',
 705+ array( 'not_id' => $row->not_id ),
 706+ __METHOD__
 707+ );
 708+ $project_languages = array();
 709+ foreach ( $res as $langRow ) {
 710+ $project_languages[] = $langRow->not_language;
 711+ }
704712
705713 if ( $row ) {
706714 // Build Html
@@ -707,62 +715,53 @@
708716 $htmlOut .= Xml::openElement( 'table', array( 'cellpadding' => 9 ) );
709717
710718 // Rows
711 - $table = array(
712 - // Day
713 - array(
714 - wfMsgHtml( 'centralnotice-start-date' ),
715 - $this->dateSelector( "start", $row->not_start ),
716 - ),
717 - // Time of day
718 - array(
719 - wfMsgHtml( 'centralnotice-start-time' ),
720 - $this->timeSelector( "start", $row->not_start, "[$row->not_name]" ),
721 - ),
722 - // End
723 - array(
724 - wfMsgHtml( 'centralnotice-end-date' ),
725 - $this->dateSelector( "end", $row->not_end, "[$row->not_name]" ),
726 - ),
727 - // Project
728 - array(
729 - wfMsgHtml( 'centralnotice-project-name' ),
730 - $this->projectDropDownList( $row->not_project )
731 - ),
732 - // Language
733 - $this->languageDropDownList( $row->not_language ),
734 - // Enabled
735 - array(
736 - wfMsgHtml( 'centralnotice-enabled' ),
737 - Xml::check( 'enabled[]', ( $row->not_enabled == '1' ),
738 - wfArrayMerge( $readonly,
739 - array( 'value' => $row->not_name ) ) )
740 - ),
741 - // Preferred
742 - array(
743 - wfMsgHtml( 'centralnotice-preferred' ),
744 - Xml::check( 'preferred[]', ( $row->not_preferred == '1' ),
745 - wfArrayMerge( $readonly,
746 - array( 'value' => $row->not_name ) ) ),
747 - ),
 719+ // Start Date
 720+ $htmlOut .= Xml::openElement( 'tr' );
 721+ $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-start-date' ) );
 722+ $htmlOut .= Xml::tags( 'td', array(), $this->dateSelector( 'start', $row->not_start ) );
 723+ $htmlOut .= Xml::closeElement( 'tr' );
 724+ // Start Time
 725+ $htmlOut .= Xml::openElement( 'tr' );
 726+ $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-start-hour' ) . "(GMT)" );
 727+ $htmlOut .= Xml::tags( 'td', array(), $this->timeSelector( 'start', $row->not_start, "[$row->not_name]" ) );
 728+ $htmlOut .= Xml::closeElement( 'tr' );
 729+ // End Date
 730+ $htmlOut .= Xml::openElement( 'tr' );
 731+ $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-end-date' ) );
 732+ $htmlOut .= Xml::tags( 'td', array(), $this->dateSelector( 'end', $row->not_end, "[$row->not_name]" ) );
 733+ $htmlOut .= Xml::closeElement( 'tr' );
 734+ // Project
 735+ $htmlOut .= Xml::openElement( 'tr' );
 736+ $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-project-name' ) );
 737+ $htmlOut .= Xml::tags( 'td', array(), $this->projectDropDownList( $row->not_project ) );
 738+ $htmlOut .= Xml::closeElement( 'tr' );
 739+ // Languages
 740+ $htmlOut .= Xml::openElement( 'tr' );
 741+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ), wfMsgHtml( 'yourlanguage' ) );
 742+ $htmlOut .= Xml::tags( 'td', array(), $this->languageMultiSelector( $project_languages ) );
 743+ $htmlOut .= Xml::closeElement( 'tr' );
 744+ // Enabled
 745+ $htmlOut .= Xml::openElement( 'tr' );
 746+ $htmlOut .= Xml::tags( 'td', array(), Xml::label( wfMsgHtml( 'centralnotice-enabled' ), 'enabled[]' ) );
 747+ $htmlOut .= Xml::tags( 'td', array(), Xml::check( 'enabled[]', ( $row->not_enabled == '1' ), wfArrayMerge( $readonly, array( 'value' => $row->not_name, 'id' => 'enabled[]' ) ) ) );
 748+ $htmlOut .= Xml::closeElement( 'tr' );
 749+ // Preferred
 750+ $htmlOut .= Xml::openElement( 'tr' );
 751+ $htmlOut .= Xml::tags( 'td', array(), Xml::label( wfMsgHtml( 'centralnotice-preferred' ), 'preferred[]' ) );
 752+ $htmlOut .= Xml::tags( 'td', array(), Xml::check( 'preferred[]', ( $row->not_preferred == '1' ), wfArrayMerge( $readonly, array( 'value' => $row->not_name, 'id' => 'preferred[]' ) ) ) );
 753+ $htmlOut .= Xml::closeElement( 'tr' );
 754+ // Locked
 755+ $htmlOut .= Xml::openElement( 'tr' );
 756+ $htmlOut .= Xml::tags( 'td', array(), Xml::label( wfMsgHtml( 'centralnotice-locked' ), 'locked[]' ) );
 757+ $htmlOut .= Xml::tags( 'td', array(), Xml::check( 'locked[]', ( $row->not_locked == '1' ), wfArrayMerge( $readonly, array( 'value' => $row->not_name, 'id' => 'locked[]' ) ) ) );
 758+ $htmlOut .= Xml::closeElement( 'tr' );
 759+ if ( $this->editable ) {
748760 // Locked
749 - array(
750 - wfMsgHtml( 'centralnotice-locked' ),
751 - Xml::check( 'locked[]', ( $row->not_locked == '1' ),
752 - wfArrayMerge( $readonly,
753 - array( 'value' => $row->not_name ) ) ),
754 - ),
755 - );
756 - if ( $this->editable ) {
757 - // Remove
758 - $table[] = array(
759 - wfMsgHtml( 'centralnotice-remove' ),
760 - Xml::check( 'removeNotices[]', false,
761 - array( 'value' => $row->not_name ) )
762 - );
 761+ $htmlOut .= Xml::openElement( 'tr' );
 762+ $htmlOut .= Xml::tags( 'td', array(), Xml::label( wfMsgHtml( 'centralnotice-remove' ), 'removeNotices[]' ) );
 763+ $htmlOut .= Xml::tags( 'td', array(), Xml::check( 'removeNotices[]', false, array( 'value' => $row->not_name, 'id' => 'removeNotices[]' ) ) );
 764+ $htmlOut .= Xml::closeElement( 'tr' );
763765 }
764 - foreach ( $table as $cells ) {
765 - $htmlOut .= $this->tableRow( $cells );
766 - }
767766 $htmlOut .= Xml::closeElement( 'table' );
768767 $htmlOut .= Xml::closeElement( 'fieldset' ) ;
769768 return $htmlOut;
@@ -990,7 +989,7 @@
991990
992991 /**
993992 * Lookup function for active notice under a given language and project
994 - * Returns an id for the running notice
 993+ * Returns an array of running template names with associated weights
995994 */
996995 static function selectNoticeTemplates( $project, $language ) {
997996 $dbr = wfGetDB( DB_SLAVE );
@@ -998,8 +997,9 @@
999998 $res = $dbr->select(
1000999 array(
10011000 'cn_notices',
 1001+ 'cn_notice_languages',
10021002 'cn_assignments',
1003 - 'cn_templates',
 1003+ 'cn_templates'
10041004 ),
10051005 array(
10061006 'tmp_name',
@@ -1009,14 +1009,15 @@
10101010 "not_start <= $encTimestamp",
10111011 "not_end >= $encTimestamp",
10121012 "not_enabled = 1",
1013 - "not_language" => array( '', $language ),
 1013+ 'cn_notice_languages.not_id = cn_notices.not_id',
 1014+ "cn_notice_languages.not_language = '$language'",
10141015 "not_project" => array( '', $project ),
10151016 'cn_notices.not_id=cn_assignments.not_id',
1016 - 'cn_assignments.tmp_id=cn_templates.tmp_id',
 1017+ 'cn_assignments.tmp_id=cn_templates.tmp_id'
10171018 ),
10181019 __METHOD__,
10191020 array(
1020 - 'GROUP BY' => 'tmp_name',
 1021+ 'GROUP BY' => 'tmp_name'
10211022 )
10221023 );
10231024 $templateWeights = array();
@@ -1028,7 +1029,7 @@
10291030 return $templateWeights;
10301031 }
10311032
1032 - function addNotice( $noticeName, $enabled, $start, $project_name, $project_language ) {
 1033+ function addNotice( $noticeName, $enabled, $start, $project_name, $project_languages ) {
10331034 global $wgOut;
10341035
10351036 $dbr = wfGetDB( DB_SLAVE );
@@ -1059,10 +1060,17 @@
10601061 'not_enabled' => $enabled,
10611062 'not_start' => $dbr->timestamp( $startTs ),
10621063 'not_end' => $dbr->timestamp( $endTs ),
1063 - 'not_project' => $project_name,
1064 - 'not_language' => $project_language
 1064+ 'not_project' => $project_name
10651065 )
10661066 );
 1067+ $not_id = $dbw->insertId();
 1068+ foreach( $project_languages as $code ) {
 1069+ $res = $dbw->insert( 'cn_notice_languages',
 1070+ array( 'not_id' => $not_id,
 1071+ 'not_language' => $code
 1072+ )
 1073+ );
 1074+ }
10671075 $dbw->commit();
10681076 return;
10691077 }
@@ -1089,6 +1097,7 @@
10901098 $noticeId = htmlspecialchars( $this->getNoticeId( $noticeName ) );
10911099 $res = $dbw->delete( 'cn_assignments', array ( 'not_id' => $noticeId ) );
10921100 $res = $dbw->delete( 'cn_notices', array ( 'not_name' => $noticeName ) );
 1101+ $res = $dbw->delete( 'cn_notice_languages', array ( 'not_id' => $noticeId ) );
10931102 $dbw->commit();
10941103 return;
10951104 }
@@ -1133,12 +1142,16 @@
11341143 return $row->not_id;
11351144 }
11361145
1137 - function getNoticeLanguage ( $noticeName ) {
1138 - $dbr = wfGetDB( DB_SLAVE );
1139 - $eNoticeName = htmlspecialchars( $noticeName );
1140 - $res = $dbr->select( 'cn_notices', 'not_language', array( 'not_name' => $eNoticeName ) );
1141 - $row = $dbr->fetchObject( $res );
1142 - return $row->not_language;
 1146+ function getNoticeLanguages ( $noticeName ) {
 1147+ $dbr = wfGetDB( DB_SLAVE );
 1148+ $eNoticeName = htmlspecialchars( $noticeName );
 1149+ $row = $dbr->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $eNoticeName ) );
 1150+ $res = $dbr->select( 'cn_notice_languages', 'not_language', array( 'not_id' => $row->not_id ) );
 1151+ $languages = array();
 1152+ foreach ( $res as $langRow ) {
 1153+ $languages[] = $langRow->not_language;
 1154+ }
 1155+ return $languages;
11431156 }
11441157
11451158 function getNoticeProjectName ( $noticeName ) {
@@ -1172,8 +1185,6 @@
11731186 global $wgOut;
11741187
11751188 $dbr = wfGetDB( DB_SLAVE );
1176 - $project_name = $this->getNoticeProjectname( $noticeName );
1177 - $project_language = $this->getNoticeLanguage( $noticeName );
11781189
11791190 // Start / end dont line up
11801191 if ( $start > $end || $end < $start ) {
@@ -1257,50 +1268,71 @@
12581269 }
12591270 }
12601271 }
1261 -
1262 - function languageDropDownList( $selected = '' ) {
1263 - // Language
1264 - list( $lsLabel, $lsSelect ) = Xml::languageSelector( $selected );
1265 -
1266 - if ( $this->editable ) {
1267 - /*
1268 - * Dirty hack to add our very own "All" option
1269 - */
1270 - // Strip selected flag
1271 - if ( $selected == '' ) {
1272 - $lsSelect = str_replace( ' selected="selected"', '', $lsSelect );
1273 - }
12741272
1275 - // Find the first select tag
1276 - $insertPoint = stripos( $lsSelect , '<option' );
1277 -
1278 - // Create our own option
1279 - $option = Xml::option( 'All languages', '', ( $selected == '' ) );
1280 -
1281 - // Insert our option
1282 - $lsSelect = substr( $lsSelect, 0, $insertPoint ) . $option . substr( $lsSelect, $insertPoint );
1283 -
1284 - return array( $lsLabel, $lsSelect );
1285 - } else {
1286 - if ( $selected == '' ) {
1287 - $lang = 'All languages';
1288 - } else {
1289 - global $wgLang;
1290 - $name = $wgLang->getLanguageName( $selected );
1291 - $lang = htmlspecialchars( "$selected - $name" );
1292 - }
1293 - return array( $lsLabel, $lang );
 1273+ /**
 1274+ * Generates a multiple select list of all languages.
 1275+ * @param $selected The language codes of the selected languages
 1276+ * @param $customisedOnly If true only languages which have some content are listed
 1277+ * @return multiple select list
 1278+ */
 1279+ function languageMultiSelector( $selected = array(), $customisedOnly = true ) {
 1280+ global $wgContLanguageCode;
 1281+ global $wgScriptPath;
 1282+ $scriptPath = "$wgScriptPath/extensions/CentralNotice";
 1283+ /**
 1284+ * Make sure the site language is in the list; a custom language code
 1285+ * might not have a defined name...
 1286+ */
 1287+ $languages = Language::getLanguageNames( $customisedOnly );
 1288+ if( !array_key_exists( $wgContLanguageCode, $languages ) ) {
 1289+ $languages[$wgContLanguageCode] = $wgContLanguageCode;
12941290 }
 1291+ ksort( $languages );
 1292+
 1293+ $options = "\n";
 1294+ foreach( $languages as $code => $name ) {
 1295+ $options .= Xml::option( "$code - $name", $code, in_array( $code, $selected ) ) . "\n";
 1296+ }
 1297+ $htmlOut = "
 1298+<script type=\"text/javascript\">\n
 1299+function selectLanguages(selectAll) {\n
 1300+ var selectBox = document.getElementById(\"project_languages[]\");\n
 1301+ var firstSelect = selectBox.options.length - 1;\n
 1302+ for (var i = firstSelect; i >= 0; i--) {\n
 1303+ selectBox.options[i].selected = selectAll;\n
 1304+ }\n
 1305+}\n
 1306+function top10Languages() {\n
 1307+ var selectBox = document.getElementById(\"project_languages[]\");\n
 1308+ var top10 = new Array('en','de','fr','it','pt','ja','es','pl','ru','nl');\n
 1309+ for (var i = 0; i < selectBox.options.length; i++) {\n
 1310+ var lang = selectBox.options[i].value;\n
 1311+ if (top10.toString().indexOf(lang)!==-1) {\n
 1312+ selectBox.options[i].selected = true;\n
 1313+ }\n
 1314+ }\n
 1315+}\n
 1316+</script>";
 1317+ $htmlOut .=
 1318+ Xml::tags( 'select',
 1319+ array( 'multiple' => 'multiple', 'size' => 4, 'id' => 'project_languages[]', 'name' => 'project_languages[]' ),
 1320+ $options
 1321+ ).
 1322+ Xml::tags( 'div',
 1323+ array( 'style' => 'margin-top: 0.2em;' ),
 1324+ '<img src="'.$scriptPath.'/arrow.png" style="vertical-align:baseline;"/>Select: <a href="#" onclick="selectLanguages(true);return false;">All</a>, <a href="#" onclick="selectLanguages(false);return false;">None</a>, <a href="#" onclick="top10Languages();return false;">Top 10 Languages</a>'
 1325+ );
 1326+ return $htmlOut;
12951327 }
1296 -
 1328+
12971329 function getProjectName( $value ) {
12981330 return $value; // @fixme -- use wfMsg()
12991331 }
13001332
13011333 function updateProjectName( $notice, $projectName ) {
1302 - $dbw = wfGetDB( DB_MASTER );
1303 - $dbw->begin();
1304 - $res = $dbw->update( 'cn_notices',
 1334+ $dbw = wfGetDB( DB_MASTER );
 1335+ $dbw->begin();
 1336+ $res = $dbw->update( 'cn_notices',
13051337 array ( 'not_project' => $projectName ),
13061338 array(
13071339 'not_name' => $notice
@@ -1309,15 +1341,33 @@
13101342 $dbw->commit();
13111343 }
13121344
1313 - function updateProjectLanguage( $notice, $language ) {
1314 - $dbw = wfGetDB( DB_MASTER );
1315 - $dbw->begin();
1316 - $res = $dbw->update( 'cn_notices',
1317 - array ( 'not_language' => $language ),
1318 - array(
1319 - 'not_name' => $notice
1320 - )
1321 - );
 1345+ function updateProjectLanguages( $notice, $newLanguages ) {
 1346+ $dbw = wfGetDB( DB_MASTER );
 1347+ $dbw->begin();
 1348+
 1349+ // Get the previously assigned languages
 1350+ $oldLanguages = array();
 1351+ $oldLanguages = $this->getNoticeLanguages( $notice );
 1352+
 1353+ // Get the notice id
 1354+ $row = $dbw->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $notice ) );
 1355+
 1356+ // Add newly assigned languages
 1357+ $addLanguages = array_diff($newLanguages, $oldLanguages);
 1358+ foreach( $addLanguages as $code ) {
 1359+ $res = $dbw->insert( 'cn_notice_languages',
 1360+ array( 'not_id' => $row->not_id, 'not_language' => $code )
 1361+ );
 1362+ }
 1363+
 1364+ // Remove disassociated languages
 1365+ $removeLanguages = array_diff($oldLanguages, $newLanguages);
 1366+ foreach( $removeLanguages as $code ) {
 1367+ $res = $dbw->delete( 'cn_notice_languages',
 1368+ array( 'not_id' => $row->not_id, 'not_language' => $code )
 1369+ );
 1370+ }
 1371+
13221372 $dbw->commit();
13231373 }
13241374
Index: trunk/extensions/CentralNotice/CentralNotice.db.php
@@ -21,19 +21,29 @@
2222 * Return notices in the system within given constraints
2323 * Optional: return both enabled and disabled notices
2424 */
25 - public function getNotices( $project = false, $language = false , $date = false , $enabled = true, $preferred = false ) {
 25+ public function getNotices( $project = false, $language = false, $date = false, $enabled = true, $preferred = false ) {
2626 // Database setup
2727 $dbr = wfGetDB( DB_SLAVE );
 28+
 29+ $tables[] = "cn_notices";
 30+ if ( $language ) {
 31+ $tables[] = "cn_notice_languages";
 32+ }
2833
2934 // Use whatever conditional arguments got passed in
30 - if ( $project )
 35+ if ( $project ) {
3136 $conds[] = "not_project =" . $dbr->addQuotes( $project );
32 - if ( $language )
33 - $conds[] = "not_language =" . $dbr->addQuotes( $language );
34 - if ( $preferred )
 37+ }
 38+ if ( $language ) {
 39+ $conds[] = "cn_notice_languages.not_id = cn_notices.not_id";
 40+ $conds[] = "cn_notice_languages.not_language =" . $dbr->addQuotes( $language );
 41+ }
 42+ if ( $preferred ) {
3543 $conds[] = "not_preferred = 1";
36 - if ( !$date )
 44+ }
 45+ if ( !$date ) {
3746 $date = $dbr->timestamp();
 47+ }
3848
3949 $conds[] = ( $date ) ? "not_start <= " . $dbr->addQuotes( $date ) : "not_start <= " . $dbr->addQuotes( $dbr->timestamp( $date ) );
4050 $conds[] = ( $date ) ? "not_end >= " . $dbr->addQuotes( $date ) : "not_end >= " . $dbr->addQuotes( $dbr->timestamp( $date ) );
@@ -41,21 +51,19 @@
4252
4353 // Pull db data
4454 $res = $dbr->select(
 55+ $tables,
4556 array(
46 - 'cn_notices',
47 - ),
48 - array(
4957 'not_name',
5058 'not_project',
51 - 'not_language',
5259 'not_locked',
5360 'not_enabled',
54 - 'not_preferred',
 61+ 'not_preferred'
5562 ),
5663 $conds,
5764 __METHOD__
5865 );
5966
 67+ // If no matching notices, return NULL
6068 if ( $dbr->numRows( $res ) < 1 ) {
6169 return;
6270 }
@@ -65,7 +73,6 @@
6674 while ( $row = $dbr->fetchObject( $res ) ) {
6775 $notice = $row->not_name;
6876 $notices[$notice]['project'] = $row->not_project;
69 - $notices[$notice]['language'] = $row->not_language;
7077 $notices[$notice]['preferred'] = $row->not_preferred;
7178 $notices[$notice]['locked'] = $row->not_locked;
7279 $notices[$notice]['enabled'] = $row->not_enabled;
Index: trunk/extensions/CentralNotice/SpecialNoticeText.php
@@ -19,6 +19,8 @@
2020 }
2121
2222 function getJsOutput( $par ) {
 23+
 24+ // Break $par into separate parameters and assign to $this->project and $this->language
2325 $this->setLanguage( $par );
2426
2527 // Quick short circuit to be able to show preferred notices
@@ -41,16 +43,13 @@
4244 }
4345
4446 if ( !$templates && $this->project == 'wikipedia' ) {
45 - $notices = CentralNoticeDB::getNotices( 'wikipedia', '', '', '', 1 );
46 - if ( $notices && is_array( $notices ) ) {
47 - foreach ( $notices as $notice => $val ) {
48 - if ( $val['language'] == '' ||
49 - $val['language'] == $this->language ) {
50 - $templates = CentralNoticeDB::selectTemplatesAssigned( $notice );
51 - break;
52 - }
53 - }
 47+ $notices = CentralNoticeDB::getNotices( 'wikipedia', $this->language, '', '', 1 );
 48+ if ( $notices && is_array( $notices ) ) {
 49+ foreach ( $notices as $notice => $val ) {
 50+ $templates = CentralNoticeDB::selectTemplatesAssigned( $notice );
 51+ break;
5452 }
 53+ }
5554 }
5655
5756 // Didn't find any preferred matches so do an old style lookup

Follow-up revisions

RevisionCommit summaryAuthorDate
r70901fixing CentralNotice issues from r68580kaldari18:08, 11 August 2010
r71382fixing table field names per r68666, fixing inserts and deletes per r68580, r...kaldari22:34, 20 August 2010

Comments

#Comment by Ryan Kaldari (WMF) (talk | contribs)   23:27, 5 August 2010

Forgot to localize "Select: " in the multi-language selection interface in SpecialCentralNotice.php. Fixed in r70529.

#Comment by Ryan Kaldari (WMF) (talk | contribs)   23:50, 5 August 2010

Eh forgot a few more. Fixed in r70531.

#Comment by Catrope (talk | contribs)   08:13, 11 August 2010

This commit would have been a less confusing read if you had separated the migration from array-based to Xml:: function-based form/table building and various indentation changes out into separate commits (so that's 3 commits: multilang feature, form building migration and indent fixing). Please do separate unrelated changes like these in the future.

I see unlocalized strings like "multiple" and "(GMT)". The latter can probably just be folded into the message preceding it.

-			array( 'ORDER BY' => 'not_id' )

Are you sure you meant to drop the ordering by ID? If so, why?

+				"cn_notice_languages.not_language = '$language'",

Use array( 'cn_notice_languages.not_language' => $language ) so $language will be escaped. This is a potential security vulnerability (XSS).

+			$options .= Xml::option( "$code - $name", $code, in_array( $code, $selected ) ) . "\n";

Even trivial-looking things like "$code - $name" should be made localizable: other languages may not want to use a dash, may want to swap the order, or what have you.

+		$htmlOut = "
+<script type=\"text/javascript\">\n
+function selectLanguages(selectAll) {\n
+	var selectBox = document.getElementById(\"project_languages[]\");\n
+	var firstSelect = selectBox.options.length - 1;\n
+	for (var i = firstSelect; i >= 0; i--) {\n
+		selectBox.options[i].selected = selectAll;\n
+	}\n
+}\n
+function top10Languages() {\n
+	var selectBox = document.getElementById(\"project_languages[]\");\n
+	var top10 = new Array('en','de','fr','it','pt','ja','es','pl','ru','nl');\n
+	for (var i = 0; i < selectBox.options.length; i++) {\n
+		var lang = selectBox.options[i].value;\n
+		if (top10.toString().indexOf(lang)!==-1) {\n
+			selectBox.options[i].selected = true;\n
+		}\n
+	}\n
+}\n
+</script>";

You could make this look less ugly by using heredoc syntax.

+		foreach( $addLanguages as $code ) {
+			$res = $dbw->insert( 'cn_notice_languages',
+				array( 'not_id' => $row->not_id, 'not_language' => $code )
+			);
+		}

You should use a multi-row insert() call by passing in an array of rows instead of just one row. This will do a multi-row insert on DBMSes that support it.

Issues I noticed that were not introduced in this commit:

+			$htmlOut .= Xml::tags( 'td', array(), Xml::inputLabel( '', 'noticeName',  'noticeName', 25 ) );

So you want an <input> with a <label>, but want the label to be empty? Don't you just want to use Xml::input() here? Looks like this may have been present in the original

+			$htmlOut .= Xml::tags( 'td', array(), $this->timeSelector( 'start', $row->not_start, "[$row->not_name]" ) );

ID-based indexing is safer. You can't rely on notice names not containing evil characters that mess this up, such as ].

+		$eNoticeName = htmlspecialchars( $noticeName );
+		$row = $dbr->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $eNoticeName ) );

Why the heck is this code storing HTML-escaped notice names in the database? This is considered bad style, and I think the Xml:: functions will just escape them a second time, creating double-escaping bugs.

#Comment by Kaldari (talk | contribs)   18:27, 11 August 2010

Should be fixed now. See my comment below.

#Comment by Tim Starling (talk | contribs)   09:07, 20 August 2010

The '$language' thing was SQL injection, not XSS.

#Comment by Kaldari (talk | contribs)   18:25, 11 August 2010

Thanks for the detailed feedback. It was very helpful!

The following issues have been fixed in r70901:

  • Unlocalized strings localized
  • $language is escaped
  • switched to doing a multi-row insert() for the languages
  • inputLabel switched to input

The Javascript code was moved to an included js file in r70609.

Regarding why I dropped the ordering by ID: It doesn't make sense to order the results of a selectRow() since the function only returns a single row.

Regarding the last two comments: I have no idea why these were done this way, but I'm reluctant to change them until I have time to review the existing code more deeply.


#Comment by Tim Starling (talk | contribs)   09:38, 20 August 2010

updateProjectLanguages() now uses a multi-row insert, but addNotice() still needs fixing. Also, updateProjectLanguages() uses multiple delete queries to delete the rows, it should use a multi-row delete instead:

$dbw->delete( 'cn_notice_languages', 
   array( 'not_id' => $row->not_id, 'not_language' => $removeLanguages ),
   __METHOD__ );

getNoticeLanguages() and noticeDetailForm() should fail gracefully when the cn_notices row is missing, instead of spewing errors and making unintended database queries.

All the fields in the cn_notice_languages table have to be renamed, as I said on r68666.

#Comment by Kaldari (talk | contribs)   23:23, 20 August 2010

All issues should be fixed in r71382.

Status & tagging log