r90920 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r90919‎ | r90920 | r90921 >
Date:23:04, 27 June 2011
Author:reedy
Status:ok
Tags:
Comment:
Move special files to special folder
Modified paths:
  • /trunk/extensions/CentralNotice/CentralNotice.php (modified) (history)
  • /trunk/extensions/CentralNotice/SpecialBannerAllocation.php (deleted) (history)
  • /trunk/extensions/CentralNotice/SpecialBannerController.php (deleted) (history)
  • /trunk/extensions/CentralNotice/SpecialBannerListLoader.php (deleted) (history)
  • /trunk/extensions/CentralNotice/SpecialBannerLoader.php (deleted) (history)
  • /trunk/extensions/CentralNotice/SpecialCentralNotice.php (deleted) (history)
  • /trunk/extensions/CentralNotice/SpecialCentralNoticeLogs.php (deleted) (history)
  • /trunk/extensions/CentralNotice/SpecialHideBanners.php (deleted) (history)
  • /trunk/extensions/CentralNotice/SpecialNoticeTemplate.php (deleted) (history)
  • /trunk/extensions/CentralNotice/special (added) (history)
  • /trunk/extensions/CentralNotice/special/SpecialBannerAllocation.php (added) (history)
  • /trunk/extensions/CentralNotice/special/SpecialBannerController.php (added) (history)
  • /trunk/extensions/CentralNotice/special/SpecialBannerListLoader.php (added) (history)
  • /trunk/extensions/CentralNotice/special/SpecialBannerLoader.php (added) (history)
  • /trunk/extensions/CentralNotice/special/SpecialCentralNotice.php (added) (history)
  • /trunk/extensions/CentralNotice/special/SpecialCentralNoticeLogs.php (added) (history)
  • /trunk/extensions/CentralNotice/special/SpecialHideBanners.php (added) (history)
  • /trunk/extensions/CentralNotice/special/SpecialNoticeTemplate.php (added) (history)

Diff [purge]

Index: trunk/extensions/CentralNotice/SpecialBannerListLoader.php
@@ -1,79 +0,0 @@
2 -<?php
3 -
4 -/**
5 - * Generates JSON files listing all the banners for a particular site
6 - */
7 -class SpecialBannerListLoader extends UnlistedSpecialPage {
8 - public $project; // Project name
9 - public $language; // Project language
10 - public $location; // User country
11 - protected $sharedMaxAge = 300; // Cache for 5 minutes on the server side
12 - protected $maxAge = 300; // Cache for 5 minutes on the client side
13 -
14 - function __construct() {
15 - // Register special page
16 - parent::__construct( "BannerListLoader" );
17 - }
18 -
19 - function execute( $par ) {
20 - global $wgOut, $wgRequest;
21 -
22 - $wgOut->disable();
23 - $this->sendHeaders();
24 -
25 - // Get project language from the query string
26 - $this->language = $wgRequest->getText( 'language', 'en' );
27 -
28 - // Get project name from the query string
29 - $this->project = $wgRequest->getText( 'project', 'wikipedia' );
30 -
31 - // Get location from the query string
32 - $this->location = $wgRequest->getText( 'country' );
33 -
34 - if ( $this->project && $this->language ) {
35 - $content = $this->getJsonList();
36 - if ( strlen( $content ) == 0 ) {
37 - // Hack for IE/Mac 0-length keepalive problem, see RawPage.php
38 - echo "/* Empty */";
39 - } else {
40 - echo $content;
41 - }
42 - } else {
43 - echo "/* No site specified */";
44 - }
45 - }
46 -
47 - /**
48 - * Generate the HTTP response headers for the banner file
49 - */
50 - function sendHeaders() {
51 - global $wgJsMimeType;
52 - header( "Content-type: $wgJsMimeType; charset=utf-8" );
53 - header( "Cache-Control: public, s-maxage=$this->sharedMaxAge, max-age=$this->maxAge" );
54 - }
55 -
56 - /**
57 - * Generate JSON for the specified site
58 - */
59 - function getJsonList() {
60 - $banners = array();
61 -
62 - // See if we have any preferred campaigns for this language and project
63 - $campaigns = CentralNoticeDB::getCampaigns( $this->project, $this->language, null, 1, 1, $this->location );
64 -
65 - // Quick short circuit to show preferred campaigns
66 - if ( $campaigns ) {
67 - // Pull banners
68 - $banners = CentralNoticeDB::selectBannersAssigned( $campaigns );
69 - }
70 -
71 - // Didn't find any preferred banners so do an old style lookup
72 - if ( !$banners ) {
73 - $banners = CentralNotice::selectNoticeTemplates(
74 - $this->project, $this->language, $this->location );
75 - }
76 -
77 - return FormatJson::encode( $banners );
78 - }
79 -
80 -}
Index: trunk/extensions/CentralNotice/SpecialCentralNotice.php
@@ -1,1950 +0,0 @@
2 -<?php
3 -
4 -if ( !defined( 'MEDIAWIKI' ) ) {
5 - echo "CentralNotice extension\n";
6 - exit( 1 );
7 -}
8 -
9 -class CentralNotice extends SpecialPage {
10 - var $editable, $centralNoticeError;
11 -
12 - function __construct() {
13 - // Register special page
14 - parent::__construct( 'CentralNotice' );
15 - }
16 -
17 - /**
18 - * Handle different types of page requests
19 - */
20 - function execute( $sub ) {
21 - global $wgOut, $wgUser, $wgRequest, $wgExtensionAssetsPath;
22 -
23 - // Begin output
24 - $this->setHeaders();
25 - $this->outputHeader();
26 -
27 - // Add style file to the output headers
28 - $wgOut->addExtensionStyle( "$wgExtensionAssetsPath/CentralNotice/centralnotice.css" );
29 -
30 - // Add script file to the output headers
31 - $wgOut->addScriptFile( "$wgExtensionAssetsPath/CentralNotice/centralnotice.js" );
32 -
33 - // Check permissions
34 - $this->editable = $wgUser->isAllowed( 'centralnotice-admin' );
35 -
36 - // Initialize error variable
37 - $this->centralNoticeError = false;
38 -
39 - // Show header
40 - $this->printHeader( $sub );
41 -
42 - // Begin Campaigns tab content
43 - $wgOut->addHTML( Xml::openElement( 'div', array( 'id' => 'preferences' ) ) );
44 -
45 - $method = $wgRequest->getVal( 'method' );
46 -
47 - // Switch to campaign detail interface if requested
48 - if ( $method == 'listNoticeDetail' ) {
49 - $notice = $wgRequest->getVal ( 'notice' );
50 - $this->listNoticeDetail( $notice );
51 - $wgOut->addHTML( Xml::closeElement( 'div' ) );
52 - return;
53 - }
54 -
55 - // Handle form submissions from "Manage campaigns" or "Add a campaign" interface
56 - if ( $this->editable && $wgRequest->wasPosted() ) {
57 -
58 - // Check authentication token
59 - if ( $wgUser->matchEditToken( $wgRequest->getVal( 'authtoken' ) ) ) {
60 -
61 - // Handle removing campaigns
62 - $toRemove = $wgRequest->getArray( 'removeNotices' );
63 - if ( $toRemove ) {
64 - // Remove campaigns in list
65 - foreach ( $toRemove as $notice ) {
66 - $this->removeNotice( $notice );
67 - }
68 -
69 - // Skip subsequent form handling and show list of campaigns
70 - $this->listNotices();
71 - $wgOut->addHTML( Xml::closeElement( 'div' ) );
72 - return;
73 - }
74 -
75 - // Handle locking/unlocking campaigns
76 - $lockedNotices = $wgRequest->getArray( 'locked' );
77 - if ( $lockedNotices ) {
78 - // Build list of campaigns to lock
79 - $unlockedNotices = array_diff( $this->getAllCampaignNames(), $lockedNotices );
80 -
81 - // Set locked/unlocked flag accordingly
82 - foreach ( $lockedNotices as $notice ) {
83 - $this->updateLock( $notice, '1' );
84 - }
85 - foreach ( $unlockedNotices as $notice ) {
86 - $this->updateLock( $notice, '0' );
87 - }
88 - // Handle updates if no post content came through (all checkboxes unchecked)
89 - } elseif ( $method !== 'addNotice' ) {
90 - $allNotices = $this->getAllCampaignNames();
91 - foreach ( $allNotices as $notice ) {
92 - $this->updateLock( $notice, '0' );
93 - }
94 - }
95 -
96 - // Handle enabling/disabling campaigns
97 - $enabledNotices = $wgRequest->getArray( 'enabled' );
98 - if ( $enabledNotices ) {
99 - // Build list of campaigns to disable
100 - $disabledNotices = array_diff( $this->getAllCampaignNames(), $enabledNotices );
101 -
102 - // Set enabled/disabled flag accordingly
103 - foreach ( $enabledNotices as $notice ) {
104 - $this->updateEnabled( $notice, '1' );
105 - }
106 - foreach ( $disabledNotices as $notice ) {
107 - $this->updateEnabled( $notice, '0' );
108 - }
109 - // Handle updates if no post content came through (all checkboxes unchecked)
110 - } elseif ( $method !== 'addNotice' ) {
111 - $allNotices = $this->getAllCampaignNames();
112 - foreach ( $allNotices as $notice ) {
113 - $this->updateEnabled( $notice, '0' );
114 - }
115 - }
116 -
117 - // Handle setting preferred campaigns
118 - $preferredNotices = $wgRequest->getArray( 'preferred' );
119 - if ( $preferredNotices ) {
120 - // Build list of campaigns to unset
121 - $unsetNotices = array_diff( $this->getAllCampaignNames(), $preferredNotices );
122 -
123 - // Set flag accordingly
124 - foreach ( $preferredNotices as $notice ) {
125 - $this->updatePreferred( $notice, '1' );
126 - }
127 - foreach ( $unsetNotices as $notice ) {
128 - $this->updatePreferred( $notice, '0' );
129 - }
130 - // Handle updates if no post content came through (all checkboxes unchecked)
131 - } elseif ( $method !== 'addNotice' ) {
132 - $allNotices = $this->getAllCampaignNames();
133 - foreach ( $allNotices as $notice ) {
134 - $this->updatePreferred( $notice, '0' );
135 - }
136 - }
137 -
138 - // Handle adding of campaign
139 - if ( $method == 'addNotice' ) {
140 - $noticeName = $wgRequest->getVal( 'noticeName' );
141 - $start = $wgRequest->getArray( 'start' );
142 - $projects = $wgRequest->getArray( 'projects' );
143 - $project_languages = $wgRequest->getArray( 'project_languages' );
144 - $geotargeted = $wgRequest->getCheck( 'geotargeted' );
145 - $geo_countries = $wgRequest->getArray( 'geo_countries' );
146 - if ( $noticeName == '' ) {
147 - $this->showError( 'centralnotice-null-string' );
148 - } else {
149 - $this->addNotice( $noticeName, '0', $start, $projects,
150 - $project_languages, $geotargeted, $geo_countries );
151 - }
152 - }
153 -
154 - // If there were no errors, reload the page to prevent duplicate form submission
155 - if ( !$this->centralNoticeError ) {
156 - $wgOut->redirect( $this->getTitle()->getLocalUrl() );
157 - return;
158 - }
159 -
160 - } else {
161 - $this->showError( 'sessionfailure' );
162 - }
163 -
164 - }
165 -
166 - // Show list of campaigns
167 - $this->listNotices();
168 -
169 - // End Campaigns tab content
170 - $wgOut->addHTML( Xml::closeElement( 'div' ) );
171 - }
172 -
173 - /**
174 - * Output the tabs for the different CentralNotice interfaces (Allocation, Logs, etc.)
175 - */
176 - public static function printHeader() {
177 - global $wgOut, $wgTitle, $wgUser;
178 - $sk = $wgUser->getSkin();
179 -
180 - $pages = array(
181 - 'CentralNotice' => wfMsg( 'centralnotice-notices' ),
182 - 'NoticeTemplate' => wfMsg ( 'centralnotice-templates' ),
183 - 'BannerAllocation' => wfMsg ( 'centralnotice-allocation' ),
184 - 'CentralNoticeLogs' => wfMsg ( 'centralnotice-logs' )
185 - );
186 - $htmlOut = Xml::openElement( 'ul', array( 'id' => 'preftoc' ) );
187 - foreach ( $pages as $page => $msg ) {
188 - $title = SpecialPage::getTitleFor( $page );
189 - $attribs = array();
190 - if ( $wgTitle->equals( $title ) ) {
191 - $attribs['class'] = 'selected';
192 - }
193 - $htmlOut .= Xml::tags( 'li', $attribs,
194 - $sk->makeLinkObj( $title, htmlspecialchars( $msg ) )
195 - );
196 - }
197 - $htmlOut .= Xml::closeElement( 'ul' );
198 -
199 - $wgOut->addHTML( $htmlOut );
200 - }
201 -
202 - /**
203 - * Get all the campaigns in the database
204 - * @return an array of campaign names
205 - */
206 - function getAllCampaignNames() {
207 - $dbr = wfGetDB( DB_SLAVE );
208 - $res = $dbr->select( 'cn_notices', 'not_name', null, __METHOD__ );
209 - $notices = array();
210 - foreach ( $res as $row ) {
211 - $notices[] = $row->not_name;
212 - }
213 - return $notices;
214 - }
215 -
216 - /**
217 - * Build a table row. Needed since Xml::buildTableRow escapes all HTML.
218 - */
219 - function tableRow( $fields, $element = 'td', $attribs = array() ) {
220 - $cells = array();
221 - foreach ( $fields as $field ) {
222 - $cells[] = Xml::tags( $element, array(), $field );
223 - }
224 - return Xml::tags( 'tr', $attribs, implode( "\n", $cells ) ) . "\n";
225 - }
226 -
227 - function dateSelector( $prefix, $timestamp = null ) {
228 - if ( $this->editable ) {
229 - // Default ranges...
230 - $years = range( 2008, 2014 );
231 - $months = range( 1, 12 );
232 - $months = array_map( array( $this, 'addZero' ), $months );
233 - $days = range( 1 , 31 );
234 - $days = array_map( array( $this, 'addZero' ), $days );
235 -
236 - // Normalize timestamp format. If no timestamp passed, defaults to now.
237 - $ts = wfTimestamp( TS_MW, $timestamp );
238 -
239 - $fields = array(
240 - array( "month", "centralnotice-month", $months, substr( $ts, 4, 2 ) ),
241 - array( "day", "centralnotice-day", $days, substr( $ts, 6, 2 ) ),
242 - array( "year", "centralnotice-year", $years, substr( $ts, 0, 4 ) ),
243 - );
244 -
245 - return $this->createSelector( $prefix, $fields );
246 - } else {
247 - global $wgLang;
248 - return $wgLang->date( $timestamp );
249 - }
250 - }
251 -
252 - function timeSelector( $prefix, $timestamp = null ) {
253 - if ( $this->editable ) {
254 - // Default ranges...
255 - $minutes = range( 0, 59 ); // formerly in 15-minute increments
256 - $minutes = array_map( array( $this, 'addZero' ), $minutes );
257 - $hours = range( 0 , 23 );
258 - $hours = array_map( array( $this, 'addZero' ), $hours );
259 -
260 - // Normalize timestamp format...
261 - $ts = wfTimestamp( TS_MW, $timestamp );
262 -
263 - $fields = array(
264 - array( "hour", "centralnotice-hours", $hours, substr( $ts, 8, 2 ) ),
265 - array( "min", "centralnotice-min", $minutes, substr( $ts, 10, 2 ) ),
266 - );
267 -
268 - return $this->createSelector( $prefix, $fields );
269 - } else {
270 - global $wgLang;
271 - return $wgLang->time( $timestamp );
272 - }
273 - }
274 -
275 - /**
276 - * Build a set of select lists. Used by dateSelector and timeSelector.
277 - * @param $prefix string
278 - * @param $fields array
279 - */
280 - private function createSelector( $prefix, $fields ) {
281 - $out = '';
282 - foreach ( $fields as $data ) {
283 - list( $field, $label, $set, $current ) = $data;
284 - $out .= Xml::listDropDown( "{$prefix}[{$field}]",
285 - $this->dropDownList( wfMsg( $label ), $set ),
286 - '',
287 - $current );
288 - }
289 - return $out;
290 - }
291 -
292 - /**
293 - * Show all campaigns found in the database, show "Add a campaign" form
294 - */
295 - function listNotices() {
296 - global $wgOut, $wgUser, $wgLang, $wgRequest, $wgNoticeProjects;
297 -
298 - // Get connection
299 - $dbr = wfGetDB( DB_SLAVE );
300 - $sk = $wgUser->getSkin();
301 - if ( $this->editable ) {
302 - $readonly = array();
303 - } else {
304 - $readonly = array( 'disabled' => 'disabled' );
305 - }
306 -
307 - // Get all campaigns from the database
308 - $res = $dbr->select( 'cn_notices',
309 - array(
310 - 'not_name',
311 - 'not_start',
312 - 'not_end',
313 - 'not_enabled',
314 - 'not_preferred',
315 - 'not_locked'
316 - ),
317 - null,
318 - __METHOD__,
319 - array( 'ORDER BY' => 'not_id DESC' )
320 - );
321 -
322 - // Begin building HTML
323 - $htmlOut = '';
324 -
325 - // Begin Manage campaigns fieldset
326 - $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
327 -
328 - // If there are campaigns to show...
329 - if ( $dbr->numRows( $res ) >= 1 ) {
330 - if ( $this->editable ) {
331 - $htmlOut .= Xml::openElement( 'form', array( 'method' => 'post' ) );
332 - }
333 - $htmlOut .= Xml::element( 'h2', null, wfMsg( 'centralnotice-manage' ) );
334 -
335 - // Begin table of campaigns
336 - $htmlOut .= Xml::openElement( 'table',
337 - array(
338 - 'cellpadding' => 9,
339 - 'width' => '100%',
340 - 'class' => 'wikitable sortable'
341 - )
342 - );
343 -
344 - // Table headers
345 - $headers = array(
346 - wfMsgHtml( 'centralnotice-notice-name' ),
347 - wfMsgHtml( 'centralnotice-projects' ),
348 - wfMsgHtml( 'centralnotice-languages' ),
349 - wfMsgHtml( 'centralnotice-start-date' ),
350 - wfMsgHtml( 'centralnotice-end-date' ),
351 - wfMsgHtml( 'centralnotice-enabled' ),
352 - wfMsgHtml( 'centralnotice-preferred' ),
353 - wfMsgHtml( 'centralnotice-locked' ),
354 - );
355 - if ( $this->editable ) {
356 - $headers[] = wfMsgHtml( 'centralnotice-remove' );
357 - }
358 - $htmlOut .= $this->tableRow( $headers, 'th' );
359 -
360 - // Table rows
361 - foreach ( $res as $row ) {
362 - $fields = array();
363 -
364 - // Name
365 - $fields[] = $sk->makeLinkObj( $this->getTitle(),
366 - htmlspecialchars( $row->not_name ),
367 - 'method=listNoticeDetail&notice=' . urlencode( $row->not_name ) );
368 -
369 - // Projects
370 - $projects = $this->getNoticeProjects( $row->not_name );
371 - $project_count = count( $projects );
372 - $projectList = '';
373 - if ( $project_count > 1 ) {
374 - $allProjects = true;
375 - foreach ( $wgNoticeProjects as $project ) {
376 - if ( !in_array( $project, $projects ) ) {
377 - $allProjects = false;
378 - break;
379 - }
380 - }
381 - if ( $allProjects ) {
382 - $projectList = wfMsg ( 'centralnotice-all-projects' );
383 - } else {
384 - $projectList = wfMsg ( 'centralnotice-multiple-projects', $project_count );
385 - }
386 - } elseif ( $project_count == 1 ) {
387 - $projectList = htmlspecialchars( $projects[0] );
388 - }
389 - $fields[] = $projectList;
390 -
391 - // Languages
392 - $project_langs = $this->getNoticeLanguages( $row->not_name );
393 - $language_count = count( $project_langs );
394 - $languageList = '';
395 - if ( $language_count > 3 ) {
396 - $languageList = wfMsg ( 'centralnotice-multiple-languages', $language_count );
397 - } elseif ( $language_count > 0 ) {
398 - $languageList = $wgLang->commaList( $project_langs );
399 - }
400 - $fields[] = $languageList;
401 -
402 - // Date and time calculations
403 - $start_timestamp = wfTimestamp( TS_MW, $row->not_start );
404 - $start_year = substr( $start_timestamp, 0 , 4 );
405 - $start_month = substr( $start_timestamp, 4, 2 );
406 - $start_day = substr( $start_timestamp, 6, 2 );
407 - $start_hour = substr( $start_timestamp, 8, 2 );
408 - $start_min = substr( $start_timestamp, 10, 2 );
409 - $end_timestamp = wfTimestamp( TS_MW, $row->not_end );
410 - $end_year = substr( $end_timestamp, 0 , 4 );
411 - $end_month = substr( $end_timestamp, 4, 2 );
412 - $end_day = substr( $end_timestamp, 6, 2 );
413 - $end_hour = substr( $end_timestamp, 8, 2 );
414 - $end_min = substr( $end_timestamp, 10, 2 );
415 -
416 - // Start
417 - $fields[] = "{$start_year}/{$start_month}/{$start_day} {$start_hour}:{$start_min}";
418 -
419 - // End
420 - $fields[] = "{$end_year}/{$end_month}/{$end_day} {$end_hour}:{$end_min}";
421 -
422 - // Enabled
423 - $fields[] =
424 - Xml::check( 'enabled[]', ( $row->not_enabled == '1' ),
425 - wfArrayMerge( $readonly,
426 - array( 'value' => $row->not_name, 'class' => 'noshiftselect' ) ) );
427 -
428 - // Preferred
429 - $fields[] =
430 - Xml::check( 'preferred[]', ( $row->not_preferred == '1' ),
431 - wfArrayMerge( $readonly,
432 - array( 'value' => $row->not_name, 'class' => 'noshiftselect' ) ) );
433 -
434 - // Locked
435 - $fields[] =
436 - Xml::check( 'locked[]', ( $row->not_locked == '1' ),
437 - wfArrayMerge( $readonly,
438 - array( 'value' => $row->not_name, 'class' => 'noshiftselect' ) ) );
439 -
440 - if ( $this->editable ) {
441 - // Remove
442 - $fields[] = Xml::check( 'removeNotices[]', false,
443 - array( 'value' => $row->not_name, 'class' => 'noshiftselect' ) );
444 - }
445 -
446 - // If campaign is currently active, set special class on table row.
447 - $attribs = array();
448 - if ( wfTimestamp() > wfTimestamp( TS_UNIX , $row->not_start )
449 - && wfTimestamp() < wfTimestamp( TS_UNIX , $row->not_end )
450 - && $row->not_enabled == '1' )
451 - {
452 - $attribs = array( 'class' => 'cn-active-campaign' );
453 - }
454 -
455 - $htmlOut .= $this->tableRow( $fields, 'td', $attribs );
456 - }
457 - // End table of campaigns
458 - $htmlOut .= Xml::closeElement( 'table' );
459 -
460 - if ( $this->editable ) {
461 - $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
462 - $htmlOut .= Xml::openElement( 'div', array( 'class' => 'cn-buttons' ) );
463 - $htmlOut .= Xml::submitButton( wfMsg( 'centralnotice-modify' ),
464 - array(
465 - 'id' => 'centralnoticesubmit',
466 - 'name' => 'centralnoticesubmit'
467 - )
468 - );
469 - $htmlOut .= Xml::closeElement( 'div' );
470 - $htmlOut .= Xml::closeElement( 'form' );
471 - }
472 -
473 - // No campaigns to show
474 - } else {
475 - $htmlOut .= wfMsg( 'centralnotice-no-notices-exist' );
476 - }
477 -
478 - // End Manage Campaigns fieldset
479 - $htmlOut .= Xml::closeElement( 'fieldset' );
480 -
481 - if ( $this->editable ) {
482 -
483 - // If there was an error, we'll need to restore the state of the form
484 - if ( $wgRequest->wasPosted() && ( $wgRequest->getVal( 'method' ) == 'addNotice' ) ) {
485 - $startArray = $wgRequest->getArray( 'start' );
486 - $startTimestamp = $startArray['year'] .
487 - $startArray['month'] .
488 - $startArray['day'] .
489 - $startArray['hour'] .
490 - $startArray['min'] . '00'
491 - ;
492 - $noticeProjects = $wgRequest->getArray( 'projects', array() );
493 - $noticeLanguages = $wgRequest->getArray( 'project_languages', array() );
494 - } else { // Defaults
495 - $startTimestamp = null;
496 - $noticeProjects = array();
497 - $noticeLanguages = array();
498 - }
499 -
500 - // Begin Add a campaign fieldset
501 - $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
502 -
503 - // Form for adding a campaign
504 - $htmlOut .= Xml::openElement( 'form', array( 'method' => 'post' ) );
505 - $htmlOut .= Xml::element( 'h2', null, wfMsg( 'centralnotice-add-notice' ) );
506 - $htmlOut .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
507 - $htmlOut .= Html::hidden( 'method', 'addNotice' );
508 -
509 - $htmlOut .= Xml::openElement( 'table', array ( 'cellpadding' => 9 ) );
510 -
511 - // Name
512 - $htmlOut .= Xml::openElement( 'tr' );
513 - $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-notice-name' ) );
514 - $htmlOut .= Xml::tags( 'td', array(),
515 - Xml::input( 'noticeName', 25, $wgRequest->getVal( 'noticeName' ) ) );
516 - $htmlOut .= Xml::closeElement( 'tr' );
517 - // Start Date
518 - $htmlOut .= Xml::openElement( 'tr' );
519 - $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-start-date' ) );
520 - $htmlOut .= Xml::tags( 'td', array(), $this->dateSelector( 'start', $startTimestamp ) );
521 - $htmlOut .= Xml::closeElement( 'tr' );
522 - // Start Time
523 - $htmlOut .= Xml::openElement( 'tr' );
524 - $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-start-time' ) );
525 - $htmlOut .= Xml::tags( 'td', array(), $this->timeSelector( 'start', $startTimestamp ) );
526 - $htmlOut .= Xml::closeElement( 'tr' );
527 - // Project
528 - $htmlOut .= Xml::openElement( 'tr' );
529 - $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
530 - wfMsgHtml( 'centralnotice-projects' ) );
531 - $htmlOut .= Xml::tags( 'td', array(), $this->projectMultiSelector( $noticeProjects ) );
532 - $htmlOut .= Xml::closeElement( 'tr' );
533 - // Languages
534 - $htmlOut .= Xml::openElement( 'tr' );
535 - $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
536 - wfMsgHtml( 'centralnotice-languages' ) );
537 - $htmlOut .= Xml::tags( 'td', array(),
538 - $this->languageMultiSelector( $noticeLanguages ) );
539 - $htmlOut .= Xml::closeElement( 'tr' );
540 - // Countries
541 - $htmlOut .= Xml::openElement( 'tr' );
542 - $htmlOut .= Xml::tags( 'td', array(),
543 - Xml::label( wfMsg( 'centralnotice-geotargeted' ), 'geotargeted' ) );
544 - $htmlOut .= Xml::tags( 'td', array(),
545 - Xml::check( 'geotargeted', false,
546 - wfArrayMerge( $readonly, array( 'value' => 1, 'id' => 'geotargeted' ) ) ) );
547 - $htmlOut .= Xml::closeElement( 'tr' );
548 - $htmlOut .= Xml::openElement( 'tr',
549 - array( 'id'=>'geoMultiSelector', 'style'=>'display:none;' ) );
550 - $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
551 - wfMsgHtml( 'centralnotice-countries' ) );
552 - $htmlOut .= Xml::tags( 'td', array(), $this->geoMultiSelector() );
553 - $htmlOut .= Xml::closeElement( 'tr' );
554 -
555 - $htmlOut .= Xml::closeElement( 'table' );
556 - $htmlOut .= Html::hidden( 'change', 'weight' );
557 - $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
558 -
559 - // Submit button
560 - $htmlOut .= Xml::tags( 'div',
561 - array( 'class' => 'cn-buttons' ),
562 - Xml::submitButton( wfMsg( 'centralnotice-modify' ) )
563 - );
564 -
565 - $htmlOut .= Xml::closeElement( 'form' );
566 -
567 - // End Add a campaign fieldset
568 - $htmlOut .= Xml::closeElement( 'fieldset' );
569 - }
570 -
571 - // Output HTML
572 - $wgOut->addHTML( $htmlOut );
573 - }
574 -
575 - /**
576 - * Show the interface for viewing/editing an individual campaign
577 - * @param $notice The name of the campaign to view
578 - */
579 - function listNoticeDetail( $notice ) {
580 - global $wgOut, $wgRequest, $wgUser;
581 -
582 - // Make sure notice exists
583 - if ( !$this->noticeExists( $notice ) ) {
584 - $this->showError( 'centralnotice-notice-doesnt-exist' );
585 - } else {
586 -
587 - // Handle form submissions from campaign detail interface
588 - if ( $this->editable && $wgRequest->wasPosted() ) {
589 -
590 - // Check authentication token
591 - if ( $wgUser->matchEditToken( $wgRequest->getVal( 'authtoken' ) ) ) {
592 -
593 - // Handle removing campaign
594 - if ( $wgRequest->getVal( 'remove' ) ) {
595 - $this->removeNotice( $notice );
596 - if ( !$this->centralNoticeError ) {
597 - // Leave campaign detail interface
598 - $wgOut->redirect( $this->getTitle()->getLocalUrl() );
599 - return;
600 - }
601 - }
602 -
603 - // Handle locking/unlocking campaign
604 - if ( $wgRequest->getCheck( 'locked' ) ) {
605 - $this->updateLock( $notice, '1' );
606 - } else {
607 - $this->updateLock( $notice, 0 );
608 - }
609 -
610 - // Handle enabling/disabling campaign
611 - if ( $wgRequest->getCheck( 'enabled' ) ) {
612 - $this->updateEnabled( $notice, '1' );
613 - } else {
614 - $this->updateEnabled( $notice, 0 );
615 - }
616 -
617 - // Handle setting campaign to preferred/not preferred
618 - if ( $wgRequest->getCheck( 'preferred' ) ) {
619 - $this->updatePreferred( $notice, '1' );
620 - } else {
621 - $this->updatePreferred( $notice, 0 );
622 - }
623 -
624 - // Handle updating geotargeting
625 - if ( $wgRequest->getCheck( 'geotargeted' ) ) {
626 - $this->updateGeotargeted( $notice, 1 );
627 - $countries = $wgRequest->getArray( 'geo_countries' );
628 - if ( $countries ) {
629 - $this->updateCountries( $notice, $countries );
630 - }
631 - } else {
632 - $this->updateGeotargeted( $notice, 0 );
633 - }
634 -
635 - // Handle updating the start and end settings
636 - $start = $wgRequest->getArray( 'start' );
637 - $end = $wgRequest->getArray( 'end' );
638 - if ( $start && $end ) {
639 - $updatedStart = sprintf( "%04d%02d%02d%02d%02d00",
640 - $start['year'],
641 - $start['month'],
642 - $start['day'],
643 - $start['hour'],
644 - $start['min']
645 - );
646 - $updatedEnd = sprintf( "%04d%02d%02d%02d%02d00",
647 - $end['year'],
648 - $end['month'],
649 - $end['day'],
650 - $end['hour'],
651 - $end['min']
652 - );
653 -
654 - $this->updateNoticeDate( $notice, $updatedStart, $updatedEnd );
655 - }
656 -
657 - // Handle adding of banners to the campaign
658 - $templatesToAdd = $wgRequest->getArray( 'addTemplates' );
659 - if ( $templatesToAdd ) {
660 - $weight = $wgRequest->getArray( 'weight' );
661 - foreach ( $templatesToAdd as $templateName ) {
662 - $templateId = $this->getTemplateId( $templateName );
663 - $this->addTemplateTo( $notice, $templateName, $weight[$templateId] );
664 - }
665 - }
666 -
667 - // Handle removing of banners from the campaign
668 - $templateToRemove = $wgRequest->getArray( 'removeTemplates' );
669 - if ( $templateToRemove ) {
670 - foreach ( $templateToRemove as $template ) {
671 - $this->removeTemplateFor( $notice, $template );
672 - }
673 - }
674 -
675 - // Handle weight changes
676 - $updatedWeights = $wgRequest->getArray( 'weight' );
677 - if ( $updatedWeights ) {
678 - foreach ( $updatedWeights as $templateId => $weight ) {
679 - $this->updateWeight( $notice, $templateId, $weight );
680 - }
681 - }
682 -
683 - // Handle new projects
684 - $projects = $wgRequest->getArray( 'projects' );
685 - if ( $projects ) {
686 - $this->updateProjects( $notice, $projects );
687 - }
688 -
689 - // Handle new project languages
690 - $projectLangs = $wgRequest->getArray( 'project_languages' );
691 - if ( $projectLangs ) {
692 - $this->updateProjectLanguages( $notice, $projectLangs );
693 - }
694 -
695 - // If there were no errors, reload the page to prevent duplicate form submission
696 - if ( !$this->centralNoticeError ) {
697 - $wgOut->redirect( $this->getTitle()->getLocalUrl(
698 - "method=listNoticeDetail&notice=$notice" ) );
699 - return;
700 - }
701 - } else {
702 - $this->showError( 'sessionfailure' );
703 - }
704 -
705 - }
706 -
707 - $htmlOut = '';
708 -
709 - // Begin Campaign detail fieldset
710 - $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
711 -
712 - if ( $this->editable ) {
713 - $htmlOut .= Xml::openElement( 'form',
714 - array(
715 - 'method' => 'post',
716 - 'action' => $this->getTitle()->getLocalUrl(
717 - "method=listNoticeDetail&notice=$notice" )
718 - )
719 - );
720 - }
721 -
722 - $output_detail = $this->noticeDetailForm( $notice );
723 - $output_assigned = $this->assignedTemplatesForm( $notice );
724 - $output_templates = $this->addTemplatesForm( $notice );
725 -
726 - $htmlOut .= $output_detail;
727 -
728 - // Catch for no banners so that we don't double message
729 - if ( $output_assigned == '' && $output_templates == '' ) {
730 - $htmlOut .= wfMsg( 'centralnotice-no-templates' );
731 - $htmlOut .= Xml::element( 'p' );
732 - $newPage = $this->getTitleFor( 'NoticeTemplate', 'add' );
733 - $sk = $wgUser->getSkin();
734 - $htmlOut .= $sk->makeLinkObj( $newPage, wfMsgHtml( 'centralnotice-add-template' ) );
735 - $htmlOut .= Xml::element( 'p' );
736 - } elseif ( $output_assigned == '' ) {
737 - $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-assigned-templates' ) );
738 - $htmlOut .= wfMsg( 'centralnotice-no-templates-assigned' );
739 - $htmlOut .= Xml::closeElement( 'fieldset' );
740 - if ( $this->editable ) {
741 - $htmlOut .= $output_templates;
742 - }
743 - } else {
744 - $htmlOut .= $output_assigned;
745 - if ( $this->editable ) {
746 - $htmlOut .= $output_templates;
747 - }
748 - }
749 - if ( $this->editable ) {
750 - $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
751 -
752 - // Submit button
753 - $htmlOut .= Xml::tags( 'div',
754 - array( 'class' => 'cn-buttons' ),
755 - Xml::submitButton( wfMsg( 'centralnotice-modify' ) )
756 - );
757 - }
758 -
759 - if ( $this->editable ) {
760 - $htmlOut .= Xml::closeElement( 'form' );
761 - }
762 - $htmlOut .= Xml::closeElement( 'fieldset' );
763 - $wgOut->addHTML( $htmlOut );
764 - }
765 - }
766 -
767 - /**
768 - * Create form for managing campaign settings (start date, end date, languages, etc.)
769 - */
770 - function noticeDetailForm( $notice ) {
771 - global $wgRequest;
772 -
773 - if ( $this->editable ) {
774 - $readonly = array();
775 - } else {
776 - $readonly = array( 'disabled' => 'disabled' );
777 - }
778 - $dbr = wfGetDB( DB_SLAVE );
779 -
780 - // Get campaign info from database
781 - $row = $dbr->selectRow( 'cn_notices',
782 - array(
783 - 'not_id',
784 - 'not_name',
785 - 'not_start',
786 - 'not_end',
787 - 'not_enabled',
788 - 'not_preferred',
789 - 'not_locked',
790 - 'not_geo'
791 - ),
792 - array( 'not_name' => $notice ),
793 - __METHOD__
794 - );
795 -
796 - if ( $row ) {
797 -
798 - // If there was an error, we'll need to restore the state of the form
799 - if ( $wgRequest->wasPosted() ) {
800 - $startArray = $wgRequest->getArray( 'start' );
801 - $startTimestamp = $startArray['year'] .
802 - $startArray['month'] .
803 - $startArray['day'] .
804 - $startArray['hour'] .
805 - $startArray['min'] . '00'
806 - ;
807 - $endArray = $wgRequest->getArray( 'end' );
808 - $endTimestamp = $endArray['year'] .
809 - $endArray['month'] .
810 - $endArray['day'] .
811 - $endArray['hour'] .
812 - $endArray['min'] .'00'
813 - ;
814 - $isEnabled = $wgRequest->getCheck( 'enabled' );
815 - $isPreferred = $wgRequest->getCheck( 'preferred' );
816 - $isLocked = $wgRequest->getCheck( 'locked' );
817 - $noticeProjects = $wgRequest->getArray( 'projects', array() );
818 - $noticeLanguages = $wgRequest->getArray( 'project_languages', array() );
819 - $isGeotargeted = $wgRequest->getCheck( 'geotargeted' );
820 - $countries = $wgRequest->getArray( 'geo_countries', array() );
821 - } else { // Defaults
822 - $startTimestamp = $row->not_start;
823 - $endTimestamp = $row->not_end;
824 - $isEnabled = ( $row->not_enabled == '1' );
825 - $isPreferred = ( $row->not_preferred == '1' );
826 - $isLocked = ( $row->not_locked == '1' );
827 - $noticeProjects = $this->getNoticeProjects( $notice );
828 - $noticeLanguages = $this->getNoticeLanguages( $notice );
829 - $isGeotargeted = ( $row->not_geo == '1' );
830 - $countries = $this->getNoticeCountries( $notice );
831 - }
832 -
833 - // Build Html
834 - $htmlOut = '';
835 - $htmlOut .= Xml::tags( 'h2', null, wfMsg( 'centralnotice-notice-heading', $notice ) );
836 - $htmlOut .= Xml::openElement( 'table', array( 'cellpadding' => 9 ) );
837 -
838 - // Rows
839 - // Start Date
840 - $htmlOut .= Xml::openElement( 'tr' );
841 - $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-start-date' ) );
842 - $htmlOut .= Xml::tags( 'td', array(), $this->dateSelector( 'start', $startTimestamp ) );
843 - $htmlOut .= Xml::closeElement( 'tr' );
844 - // Start Time
845 - $htmlOut .= Xml::openElement( 'tr' );
846 - $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-start-time' ) );
847 - $htmlOut .= Xml::tags( 'td', array(), $this->timeSelector( 'start', $startTimestamp ) );
848 - $htmlOut .= Xml::closeElement( 'tr' );
849 - // End Date
850 - $htmlOut .= Xml::openElement( 'tr' );
851 - $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-end-date' ) );
852 - $htmlOut .= Xml::tags( 'td', array(), $this->dateSelector( 'end', $endTimestamp ) );
853 - $htmlOut .= Xml::closeElement( 'tr' );
854 - // End Time
855 - $htmlOut .= Xml::openElement( 'tr' );
856 - $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-end-time' ) );
857 - $htmlOut .= Xml::tags( 'td', array(), $this->timeSelector( 'end', $endTimestamp ) );
858 - $htmlOut .= Xml::closeElement( 'tr' );
859 - // Project
860 - $htmlOut .= Xml::openElement( 'tr' );
861 - $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
862 - wfMsgHtml( 'centralnotice-projects' ) );
863 - $htmlOut .= Xml::tags( 'td', array(),
864 - $this->projectMultiSelector( $noticeProjects ) );
865 - $htmlOut .= Xml::closeElement( 'tr' );
866 - // Languages
867 - $htmlOut .= Xml::openElement( 'tr' );
868 - $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
869 - wfMsgHtml( 'centralnotice-languages' ) );
870 - $htmlOut .= Xml::tags( 'td', array(),
871 - $this->languageMultiSelector( $noticeLanguages ) );
872 - $htmlOut .= Xml::closeElement( 'tr' );
873 - // Countries
874 - $htmlOut .= Xml::openElement( 'tr' );
875 - $htmlOut .= Xml::tags( 'td', array(),
876 - Xml::label( wfMsg( 'centralnotice-geotargeted' ), 'geotargeted' ) );
877 - $htmlOut .= Xml::tags( 'td', array(),
878 - Xml::check( 'geotargeted', $isGeotargeted,
879 - wfArrayMerge(
880 - $readonly,
881 - array( 'value' => $row->not_name, 'id' => 'geotargeted' ) ) ) );
882 - $htmlOut .= Xml::closeElement( 'tr' );
883 - if ( $isGeotargeted ) {
884 - $htmlOut .= Xml::openElement( 'tr', array( 'id'=>'geoMultiSelector' ) );
885 - } else {
886 - $htmlOut .= Xml::openElement( 'tr',
887 - array( 'id'=>'geoMultiSelector', 'style'=>'display:none;' ) );
888 - }
889 - $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
890 - wfMsgHtml( 'centralnotice-countries' ) );
891 - $htmlOut .= Xml::tags( 'td', array(), $this->geoMultiSelector( $countries ) );
892 - $htmlOut .= Xml::closeElement( 'tr' );
893 - // Enabled
894 - $htmlOut .= Xml::openElement( 'tr' );
895 - $htmlOut .= Xml::tags( 'td', array(),
896 - Xml::label( wfMsg( 'centralnotice-enabled' ), 'enabled' ) );
897 - $htmlOut .= Xml::tags( 'td', array(),
898 - Xml::check( 'enabled', $isEnabled,
899 - wfArrayMerge( $readonly,
900 - array( 'value' => $row->not_name, 'id' => 'enabled' ) ) ) );
901 - $htmlOut .= Xml::closeElement( 'tr' );
902 - // Preferred
903 - $htmlOut .= Xml::openElement( 'tr' );
904 - $htmlOut .= Xml::tags( 'td', array(),
905 - Xml::label( wfMsg( 'centralnotice-preferred' ), 'preferred' ) );
906 - $htmlOut .= Xml::tags( 'td', array(),
907 - Xml::check( 'preferred', $isPreferred,
908 - wfArrayMerge( $readonly,
909 - array( 'value' => $row->not_name, 'id' => 'preferred' ) ) ) );
910 - $htmlOut .= Xml::closeElement( 'tr' );
911 - // Locked
912 - $htmlOut .= Xml::openElement( 'tr' );
913 - $htmlOut .= Xml::tags( 'td', array(),
914 - Xml::label( wfMsg( 'centralnotice-locked' ), 'locked' ) );
915 - $htmlOut .= Xml::tags( 'td', array(),
916 - Xml::check( 'locked', $isLocked,
917 - wfArrayMerge( $readonly,
918 - array( 'value' => $row->not_name, 'id' => 'locked' ) ) ) );
919 - $htmlOut .= Xml::closeElement( 'tr' );
920 - if ( $this->editable ) {
921 - // Locked
922 - $htmlOut .= Xml::openElement( 'tr' );
923 - $htmlOut .= Xml::tags( 'td', array(),
924 - Xml::label( wfMsg( 'centralnotice-remove' ), 'remove' ) );
925 - $htmlOut .= Xml::tags( 'td', array(),
926 - Xml::check( 'remove', false,
927 - array( 'value' => $row->not_name, 'id' => 'remove' ) ) );
928 - $htmlOut .= Xml::closeElement( 'tr' );
929 - }
930 - $htmlOut .= Xml::closeElement( 'table' );
931 - return $htmlOut;
932 - } else {
933 - return '';
934 - }
935 - }
936 -
937 - /**
938 - * Create form for managing banners assigned to a campaign
939 - */
940 - function assignedTemplatesForm( $notice ) {
941 - global $wgUser;
942 - $sk = $wgUser->getSkin();
943 -
944 - $dbr = wfGetDB( DB_SLAVE );
945 - $res = $dbr->select(
946 - array(
947 - 'cn_notices',
948 - 'cn_assignments',
949 - 'cn_templates'
950 - ),
951 - array(
952 - 'cn_templates.tmp_id',
953 - 'cn_templates.tmp_name',
954 - 'cn_assignments.tmp_weight'
955 - ),
956 - array(
957 - 'cn_notices.not_name' => $notice,
958 - 'cn_notices.not_id = cn_assignments.not_id',
959 - 'cn_assignments.tmp_id = cn_templates.tmp_id'
960 - ),
961 - __METHOD__,
962 - array( 'ORDER BY' => 'cn_notices.not_id' )
963 - );
964 -
965 - // No banners found
966 - if ( $dbr->numRows( $res ) < 1 ) {
967 - return;
968 - }
969 -
970 - // Build Assigned banners HTML
971 - $htmlOut = Html::hidden( 'change', 'weight' );
972 - $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-assigned-templates' ) );
973 - $htmlOut .= Xml::openElement( 'table',
974 - array(
975 - 'cellpadding' => 9,
976 - 'width' => '100%'
977 - )
978 - );
979 - if ( $this->editable ) {
980 - $htmlOut .= Xml::element( 'th', array( 'align' => 'left', 'width' => '5%' ),
981 - wfMsg ( "centralnotice-remove" ) );
982 - }
983 - $htmlOut .= Xml::element( 'th', array( 'align' => 'left', 'width' => '5%' ),
984 - wfMsg ( "centralnotice-weight" ) );
985 - $htmlOut .= Xml::element( 'th', array( 'align' => 'left', 'width' => '70%' ),
986 - wfMsg ( "centralnotice-templates" ) );
987 -
988 - // Table rows
989 - foreach( $res as $row ) {
990 -
991 - $htmlOut .= Xml::openElement( 'tr' );
992 -
993 - if ( $this->editable ) {
994 - // Remove
995 - $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
996 - Xml::check( 'removeTemplates[]', false, array( 'value' => $row->tmp_name ) )
997 - );
998 - }
999 -
1000 - // Weight
1001 - $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
1002 - $this->weightDropDown( "weight[$row->tmp_id]", $row->tmp_weight )
1003 - );
1004 -
1005 - $viewPage = $this->getTitleFor( 'NoticeTemplate', 'view' );
1006 -
1007 - /* XXX this code is duplicated in the CentralNoticePager::formatRow */
1008 - $render = new SpecialBannerLoader();
1009 - $render->siteName = 'Wikipedia';
1010 - global $wgRequest;
1011 - $render->language = $wgRequest->getVal( 'wpUserLanguage' );
1012 - try {
1013 - $preview = $render->getHtmlNotice( $row->tmp_name );
1014 - } catch ( SpecialBannerLoaderException $e ) {
1015 - $preview = wfMsg( 'centralnotice-nopreview' );
1016 - }
1017 - $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
1018 - $sk->makeLinkObj( $viewPage,
1019 - htmlspecialchars( $row->tmp_name ),
1020 - 'template=' . urlencode( $row->tmp_name ) ) .
1021 - Xml::fieldset( wfMsg( 'centralnotice-preview' ),
1022 - $preview,
1023 - array( 'class' => 'cn-bannerpreview')
1024 - )
1025 - );
1026 -
1027 - $htmlOut .= Xml::closeElement( 'tr' );
1028 - }
1029 - $htmlOut .= XMl::closeElement( 'table' );
1030 - $htmlOut .= Xml::closeElement( 'fieldset' );
1031 - return $htmlOut;
1032 -
1033 - }
1034 -
1035 - function weightDropDown( $name, $selected ) {
1036 - if ( $this->editable ) {
1037 - return Xml::listDropDown( $name,
1038 - $this->dropDownList( wfMsg( 'centralnotice-weight' ),
1039 - range ( 0, 100, 5 ) ),
1040 - '',
1041 - $selected,
1042 - '',
1043 - 1 );
1044 - } else {
1045 - return htmlspecialchars( $selected );
1046 - }
1047 - }
1048 -
1049 - /**
1050 - * Create form for adding banners to a campaign
1051 - */
1052 - function addTemplatesForm( $notice ) {
1053 - $pager = new CentralNoticePager( $this );
1054 - $dbr = wfGetDB( DB_SLAVE );
1055 - $res = $dbr->select( 'cn_templates', 'tmp_name', '', '', array( 'ORDER BY' => 'tmp_id' ) );
1056 -
1057 - if ( $dbr->numRows( $res ) > 0 ) {
1058 - // Build HTML
1059 - $htmlOut = Xml::fieldset( wfMsg( "centralnotice-available-templates" ) );
1060 -
1061 - // Show paginated list of banners
1062 - $htmlOut .= Xml::tags( 'div',
1063 - array( 'class' => 'cn-pager' ),
1064 - $pager->getNavigationBar() );
1065 - $htmlOut .= $pager->getBody();
1066 - $htmlOut .= Xml::tags( 'div',
1067 - array( 'class' => 'cn-pager' ),
1068 - $pager->getNavigationBar() );
1069 -
1070 - $htmlOut .= Xml::closeElement( 'fieldset' );
1071 - } else {
1072 - // Nothing found
1073 - return;
1074 - }
1075 - return $htmlOut;
1076 - }
1077 -
1078 - /**
1079 - * Lookup function for active banners under a given language/project/location. This function is
1080 - * called by SpecialBannerListLoader::getJsonList() in order to build the banner list JSON for
1081 - * each project.
1082 - * @return a 2D array of running banners with associated weights and settings
1083 - */
1084 - static function selectNoticeTemplates( $project, $language, $location = null ) {
1085 - global $wgCentralDBname;
1086 -
1087 - $campaigns = array();
1088 - $dbr = wfGetDB( DB_SLAVE, array(), $wgCentralDBname );
1089 - $encTimestamp = $dbr->addQuotes( $dbr->timestamp() );
1090 -
1091 - // Pull non-geotargeted campaigns
1092 - $campaignResults1 = $dbr->select(
1093 - array(
1094 - 'cn_notices',
1095 - 'cn_notice_projects',
1096 - 'cn_notice_languages'
1097 - ),
1098 - array(
1099 - 'not_id'
1100 - ),
1101 - array(
1102 - "not_start <= $encTimestamp",
1103 - "not_end >= $encTimestamp",
1104 - 'not_enabled = 1', // enabled
1105 - 'not_geo = 0', // not geotargeted
1106 - 'np_notice_id = cn_notices.not_id',
1107 - 'np_project' => $project,
1108 - 'nl_notice_id = cn_notices.not_id',
1109 - 'nl_language' => $language
1110 - ),
1111 - __METHOD__
1112 - );
1113 - foreach ( $campaignResults1 as $row ) {
1114 - $campaigns[] = $row->not_id;
1115 - }
1116 - if ( $location ) {
1117 -
1118 - // Normalize location parameter (should be an uppercase 2-letter country code)
1119 - preg_match( '/[a-zA-Z][a-zA-Z]/', $location, $matches );
1120 - if ( $matches ) {
1121 - $location = strtoupper( $matches[0] );
1122 -
1123 - // Pull geotargeted campaigns
1124 - $campaignResults2 = $dbr->select(
1125 - array(
1126 - 'cn_notices',
1127 - 'cn_notice_projects',
1128 - 'cn_notice_languages',
1129 - 'cn_notice_countries'
1130 - ),
1131 - array(
1132 - 'not_id'
1133 - ),
1134 - array(
1135 - "not_start <= $encTimestamp",
1136 - "not_end >= $encTimestamp",
1137 - 'not_enabled = 1', // enabled
1138 - 'not_geo = 1', // geotargeted
1139 - 'nc_notice_id = cn_notices.not_id',
1140 - 'nc_country' => $location,
1141 - 'np_notice_id = cn_notices.not_id',
1142 - 'np_project' => $project,
1143 - 'nl_notice_id = cn_notices.not_id',
1144 - 'nl_language' => $language
1145 - ),
1146 - __METHOD__
1147 - );
1148 - foreach ( $campaignResults2 as $row ) {
1149 - $campaigns[] = $row->not_id;
1150 - }
1151 - }
1152 - }
1153 -
1154 - $templates = array();
1155 - if ( $campaigns ) {
1156 - // Pull all banners assigned to the campaigns
1157 - $templates = CentralNoticeDB::selectBannersAssigned( $campaigns );
1158 - }
1159 - return $templates;
1160 - }
1161 -
1162 - function addNotice( $noticeName, $enabled, $start, $projects,
1163 - $project_languages, $geotargeted, $geo_countries )
1164 - {
1165 - if ( $this->noticeExists( $noticeName ) ) {
1166 - $this->showError( 'centralnotice-notice-exists' );
1167 - return;
1168 - } elseif ( empty( $projects ) ) {
1169 - $this->showError( 'centralnotice-no-project' );
1170 - return;
1171 - } elseif ( empty( $project_languages ) ) {
1172 - $this->showError( 'centralnotice-no-language' );
1173 - return;
1174 - } else {
1175 - $dbw = wfGetDB( DB_MASTER );
1176 - $dbw->begin();
1177 - $start['hour'] = substr( $start['hour'], 0 , 2 );
1178 - $start['min'] = substr( $start['min'], 0 , 2 );
1179 - if ( $start['month'] == 12 ) {
1180 - $end['month'] = '01';
1181 - $end['year'] = ( $start['year'] + 1 );
1182 - } elseif ( $start['month'] == '09' ) {
1183 - $end['month'] = '10';
1184 - $end['year'] = $start['year'];
1185 - } else {
1186 - $end['month'] =
1187 - ( substr( $start['month'], 0, 1 ) ) == 0
1188 - ? 0 . ( intval( $start['month'] ) + 1 )
1189 - : ( $start['month'] + 1 );
1190 - $end['year'] = $start['year'];
1191 - }
1192 -
1193 - $startTs = wfTimeStamp( TS_MW, "{$start['year']}:{$start['month']}:{$start['day']} " .
1194 - "{$start['hour']}:{$start['min']}:00" );
1195 - $endTs = wfTimeStamp( TS_MW, "{$end['year']}:{$end['month']}:{$start['day']} " .
1196 - "{$start['hour']}:{$start['min']}:00" );
1197 -
1198 - $res = $dbw->insert( 'cn_notices',
1199 - array( 'not_name' => $noticeName,
1200 - 'not_enabled' => $enabled,
1201 - 'not_start' => $dbw->timestamp( $startTs ),
1202 - 'not_end' => $dbw->timestamp( $endTs ),
1203 - 'not_geo' => $geotargeted
1204 - )
1205 - );
1206 - $not_id = $dbw->insertId();
1207 -
1208 - // Do multi-row insert for campaign projects
1209 - $insertArray = array();
1210 - foreach( $projects as $project ) {
1211 - $insertArray[] = array( 'np_notice_id' => $not_id, 'np_project' => $project );
1212 - }
1213 - $res = $dbw->insert( 'cn_notice_projects', $insertArray,
1214 - __METHOD__, array( 'IGNORE' ) );
1215 -
1216 - // Do multi-row insert for campaign languages
1217 - $insertArray = array();
1218 - foreach( $project_languages as $code ) {
1219 - $insertArray[] = array( 'nl_notice_id' => $not_id, 'nl_language' => $code );
1220 - }
1221 - $res = $dbw->insert( 'cn_notice_languages', $insertArray,
1222 - __METHOD__, array( 'IGNORE' ) );
1223 -
1224 - if ( $geotargeted && $geo_countries ) {
1225 - // Do multi-row insert for campaign countries
1226 - $insertArray = array();
1227 - foreach( $geo_countries as $code ) {
1228 - $insertArray[] = array( 'nc_notice_id' => $not_id, 'nc_country' => $code );
1229 - }
1230 - $res = $dbw->insert( 'cn_notice_countries', $insertArray,
1231 - __METHOD__, array( 'IGNORE' ) );
1232 - }
1233 -
1234 - $dbw->commit();
1235 -
1236 - // Log the creation of the campaign
1237 - $beginSettings = array();
1238 - $endSettings = array(
1239 - 'notlog_end_name' => $noticeName,
1240 - 'notlog_end_projects' => implode( ", ", $projects ),
1241 - 'notlog_end_languages' => implode( ", ", $project_languages ),
1242 - 'notlog_end_countries' => implode( ", ", $geo_countries ),
1243 - 'notlog_end_start' => $dbw->timestamp( $startTs ),
1244 - 'notlog_end_end' => $dbw->timestamp( $endTs ),
1245 - 'notlog_end_enabled' => $enabled,
1246 - 'notlog_end_preferred' => 0,
1247 - 'notlog_end_locked' => 0,
1248 - 'notlog_end_geo' => $geotargeted
1249 - );
1250 - $this->logCampaignChange( 'created', $not_id, $beginSettings, $endSettings );
1251 -
1252 - return;
1253 - }
1254 - }
1255 -
1256 - function removeNotice( $noticeName ) {
1257 - $dbr = wfGetDB( DB_SLAVE );
1258 -
1259 - $res = $dbr->select( 'cn_notices', 'not_name, not_locked',
1260 - array( 'not_name' => $noticeName )
1261 - );
1262 - if ( $dbr->numRows( $res ) < 1 ) {
1263 - $this->showError( 'centralnotice-remove-notice-doesnt-exist' );
1264 - return;
1265 - }
1266 - $row = $dbr->fetchObject( $res );
1267 - if ( $row->not_locked == '1' ) {
1268 - $this->showError( 'centralnotice-notice-is-locked' );
1269 - return;
1270 - } else {
1271 - $dbw = wfGetDB( DB_MASTER );
1272 - $dbw->begin();
1273 - $noticeId = htmlspecialchars( $this->getNoticeId( $noticeName ) );
1274 - $res = $dbw->delete( 'cn_assignments', array ( 'not_id' => $noticeId ) );
1275 - $res = $dbw->delete( 'cn_notices', array ( 'not_name' => $noticeName ) );
1276 - $res = $dbw->delete( 'cn_notice_languages', array ( 'nl_notice_id' => $noticeId ) );
1277 - $dbw->commit();
1278 -
1279 - // Log the removal of the campaign
1280 - $this->logCampaignChange( 'removed', $noticeId );
1281 -
1282 - return;
1283 - }
1284 - }
1285 -
1286 - function addTemplateTo( $noticeName, $templateName, $weight ) {
1287 - $dbr = wfGetDB( DB_SLAVE );
1288 -
1289 - $eNoticeName = htmlspecialchars ( $noticeName );
1290 - $noticeId = $this->getNoticeId( $eNoticeName );
1291 - $templateId = $this->getTemplateId( $templateName );
1292 - $res = $dbr->select( 'cn_assignments', 'asn_id',
1293 - array(
1294 - 'tmp_id' => $templateId,
1295 - 'not_id' => $noticeId
1296 - )
1297 - );
1298 - if ( $dbr->numRows( $res ) > 0 ) {
1299 - $this->showError( 'centralnotice-template-already-exists' );
1300 - } else {
1301 - $dbw = wfGetDB( DB_MASTER );
1302 - $dbw->begin();
1303 - $noticeId = $this->getNoticeId( $eNoticeName );
1304 - $res = $dbw->insert( 'cn_assignments',
1305 - array(
1306 - 'tmp_id' => $templateId,
1307 - 'tmp_weight' => $weight,
1308 - 'not_id' => $noticeId
1309 - )
1310 - );
1311 - $dbw->commit();
1312 - }
1313 - }
1314 -
1315 - /**
1316 - * Lookup the ID for a campaign based on the campaign name
1317 - */
1318 - public static function getNoticeId( $noticeName ) {
1319 - $dbr = wfGetDB( DB_SLAVE );
1320 - $eNoticeName = htmlspecialchars( $noticeName );
1321 - $row = $dbr->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $eNoticeName ) );
1322 - if ( $row ) {
1323 - return $row->not_id;
1324 - } else {
1325 - return null;
1326 - }
1327 - }
1328 -
1329 - /*
1330 - * Lookup the name of a campaign based on the campaign ID
1331 - */
1332 - public static function getNoticeName( $noticeId ) {
1333 - $dbr = wfGetDB( DB_SLAVE );
1334 - if ( is_numeric( $noticeId ) ) {
1335 - $row = $dbr->selectRow( 'cn_notices', 'not_name', array( 'not_id' => $noticeId ) );
1336 - if ( $row ) {
1337 - return $row->not_name;
1338 - }
1339 - }
1340 - return null;
1341 - }
1342 -
1343 - function getNoticeProjects( $noticeName ) {
1344 - $dbr = wfGetDB( DB_SLAVE );
1345 - $eNoticeName = htmlspecialchars( $noticeName );
1346 - $row = $dbr->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $eNoticeName ) );
1347 - $projects = array();
1348 - if ( $row ) {
1349 - $res = $dbr->select( 'cn_notice_projects', 'np_project',
1350 - array( 'np_notice_id' => $row->not_id ) );
1351 - foreach ( $res as $projectRow ) {
1352 - $projects[] = $projectRow->np_project;
1353 - }
1354 - }
1355 - return $projects;
1356 - }
1357 -
1358 - function getNoticeLanguages( $noticeName ) {
1359 - $dbr = wfGetDB( DB_SLAVE );
1360 - $eNoticeName = htmlspecialchars( $noticeName );
1361 - $row = $dbr->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $eNoticeName ) );
1362 - $languages = array();
1363 - if ( $row ) {
1364 - $res = $dbr->select( 'cn_notice_languages', 'nl_language',
1365 - array( 'nl_notice_id' => $row->not_id ) );
1366 - foreach ( $res as $langRow ) {
1367 - $languages[] = $langRow->nl_language;
1368 - }
1369 - }
1370 - return $languages;
1371 - }
1372 -
1373 - function getNoticeCountries( $noticeName ) {
1374 - $dbr = wfGetDB( DB_SLAVE );
1375 - $eNoticeName = htmlspecialchars( $noticeName );
1376 - $row = $dbr->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $eNoticeName ) );
1377 - $countries = array();
1378 - if ( $row ) {
1379 - $res = $dbr->select( 'cn_notice_countries', 'nc_country',
1380 - array( 'nc_notice_id' => $row->not_id ) );
1381 - foreach ( $res as $countryRow ) {
1382 - $countries[] = $countryRow->nc_country;
1383 - }
1384 - }
1385 - return $countries;
1386 - }
1387 -
1388 - function getNoticeProjectName( $noticeName ) {
1389 - $dbr = wfGetDB( DB_SLAVE );
1390 - $eNoticeName = htmlspecialchars( $noticeName );
1391 - $res = $dbr->select( 'cn_notices', 'not_project', array( 'not_name' => $eNoticeName ) );
1392 - $row = $dbr->fetchObject( $res );
1393 - return $row->not_project;
1394 - }
1395 -
1396 - function getTemplateId( $templateName ) {
1397 - $dbr = wfGetDB( DB_SLAVE );
1398 - $templateName = htmlspecialchars ( $templateName );
1399 - $res = $dbr->select( 'cn_templates', 'tmp_id', array( 'tmp_name' => $templateName ) );
1400 - $row = $dbr->fetchObject( $res );
1401 - return $row->tmp_id;
1402 - }
1403 -
1404 - function removeTemplateFor( $noticeName, $templateName ) {
1405 - $dbw = wfGetDB( DB_MASTER );
1406 - $dbw->begin();
1407 - $noticeId = $this->getNoticeId( $noticeName );
1408 - $templateId = $this->getTemplateId( $templateName );
1409 - $dbw->delete( 'cn_assignments', array ( 'tmp_id' => $templateId, 'not_id' => $noticeId ) );
1410 - $dbw->commit();
1411 - }
1412 -
1413 - function updateNoticeDate( $noticeName, $start, $end ) {
1414 - $dbr = wfGetDB( DB_SLAVE );
1415 -
1416 - // Start/end don't line up
1417 - if ( $start > $end || $end < $start ) {
1418 - $this->showError( 'centralnotice-invalid-date-range' );
1419 - return;
1420 - }
1421 -
1422 - // Invalid campaign name
1423 - if ( !$this->noticeExists( $noticeName ) ) {
1424 - $this->showError( 'centralnotice-notice-doesnt-exist' );
1425 - return;
1426 - }
1427 -
1428 - // Overlap over a date within the same project and language
1429 - $startDate = $dbr->timestamp( $start );
1430 - $endDate = $dbr->timestamp( $end );
1431 -
1432 - $dbw = wfGetDB( DB_MASTER );
1433 - $res = $dbw->update( 'cn_notices',
1434 - array(
1435 - 'not_start' => $startDate,
1436 - 'not_end' => $endDate
1437 - ),
1438 - array( 'not_name' => $noticeName )
1439 - );
1440 - }
1441 -
1442 - /**
1443 - * Update the enabled/disabled state of a campaign
1444 - */
1445 - private function updateEnabled( $noticeName, $isEnabled ) {
1446 - if ( !$this->noticeExists( $noticeName ) ) {
1447 - $this->showError( 'centralnotice-doesnt-exist' );
1448 - } else {
1449 - $dbw = wfGetDB( DB_MASTER );
1450 - $res = $dbw->update( 'cn_notices',
1451 - array( 'not_enabled' => $isEnabled ),
1452 - array( 'not_name' => $noticeName )
1453 - );
1454 - }
1455 - }
1456 -
1457 - /**
1458 - * Update the preferred/not preferred state of a campaign
1459 - */
1460 - function updatePreferred( $noticeName, $isPreferred ) {
1461 - if ( !$this->noticeExists( $noticeName ) ) {
1462 - $this->showError( 'centralnotice-doesnt-exist' );
1463 - } else {
1464 - $dbw = wfGetDB( DB_MASTER );
1465 - $res = $dbw->update( 'cn_notices',
1466 - array( 'not_preferred' => $isPreferred ),
1467 - array( 'not_name' => $noticeName )
1468 - );
1469 - }
1470 - }
1471 -
1472 - /**
1473 - * Update the geotargeted/not geotargeted state of a campaign
1474 - */
1475 - function updateGeotargeted( $noticeName, $isGeotargeted ) {
1476 - if ( !$this->noticeExists( $noticeName ) ) {
1477 - $this->showError( 'centralnotice-doesnt-exist' );
1478 - } else {
1479 - $dbw = wfGetDB( DB_MASTER );
1480 - $res = $dbw->update( 'cn_notices',
1481 - array( 'not_geo' => $isGeotargeted ),
1482 - array( 'not_name' => $noticeName )
1483 - );
1484 - }
1485 - }
1486 -
1487 - /**
1488 - * Update the locked/unlocked state of a campaign
1489 - */
1490 - function updateLock( $noticeName, $isLocked ) {
1491 - if ( !$this->noticeExists( $noticeName ) ) {
1492 - $this->showError( 'centralnotice-doesnt-exist' );
1493 - } else {
1494 - $dbw = wfGetDB( DB_MASTER );
1495 - $res = $dbw->update( 'cn_notices',
1496 - array( 'not_locked' => $isLocked ),
1497 - array( 'not_name' => $noticeName )
1498 - );
1499 - }
1500 - }
1501 -
1502 - function updateWeight( $noticeName, $templateId, $weight ) {
1503 - $dbw = wfGetDB( DB_MASTER );
1504 - $noticeId = $this->getNoticeId( $noticeName );
1505 - $dbw->update( 'cn_assignments',
1506 - array ( 'tmp_weight' => $weight ),
1507 - array(
1508 - 'tmp_id' => $templateId,
1509 - 'not_id' => $noticeId
1510 - )
1511 - );
1512 - }
1513 -
1514 - /**
1515 - * Generates a multiple select list of all languages.
1516 - * @param $selected The language codes of the selected languages
1517 - * @param $customisedOnly If true only languages which have some content are listed
1518 - * @return multiple select list
1519 - */
1520 - function languageMultiSelector( $selected = array(), $customisedOnly = true ) {
1521 - global $wgLanguageCode, $wgExtensionAssetsPath, $wgLang;
1522 - $scriptPath = "$wgExtensionAssetsPath/CentralNotice";
1523 - if ( is_callable( array( 'LanguageNames', 'getNames' ) ) ) {
1524 - // Retrieve the list of languages in user's language (via CLDR)
1525 - $languages = LanguageNames::getNames(
1526 - $wgLang->getCode(), // User's language
1527 - LanguageNames::FALLBACK_NORMAL, // Use fallback chain
1528 - LanguageNames::LIST_MW // Pull all languages that are in Names.php
1529 - );
1530 - } else {
1531 - // Use this as fallback if CLDR extension is not enabled
1532 - $languages = Language::getLanguageNames();
1533 - }
1534 - // Make sure the site language is in the list; a custom language code
1535 - // might not have a defined name...
1536 - if( !array_key_exists( $wgLanguageCode, $languages ) ) {
1537 - $languages[$wgLanguageCode] = $wgLanguageCode;
1538 - }
1539 - ksort( $languages );
1540 -
1541 - $options = "\n";
1542 - foreach( $languages as $code => $name ) {
1543 - $options .= Xml::option(
1544 - wfMsg( 'centralnotice-language-listing', $code, $name ),
1545 - $code,
1546 - in_array( $code, $selected )
1547 - ) . "\n";
1548 - }
1549 - $htmlOut = '';
1550 - if ( $this->editable ) {
1551 - $htmlOut .= Xml::tags( 'select',
1552 - array( 'multiple' => 'multiple', 'size' => 4, 'id' => 'project_languages[]', 'name' => 'project_languages[]' ),
1553 - $options
1554 - );
1555 - $buttons = array();
1556 - $buttons[] = '<a href="#" onclick="selectLanguages(true);return false;">' .
1557 - wfMsg( 'powersearch-toggleall' ) . '</a>';
1558 - $buttons[] = '<a href="#" onclick="selectLanguages(false);return false;">' .
1559 - wfMsg( 'powersearch-togglenone' ) . '</a>';
1560 - $buttons[] = '<a href="#" onclick="top10Languages();return false;">' .
1561 - wfMsg( 'centralnotice-top-ten-languages' ) . '</a>';
1562 - $htmlOut .= Xml::tags( 'div',
1563 - array( 'style' => 'margin-top: 0.2em;' ),
1564 - '<img src="'.$scriptPath.'/up-arrow.png" style="vertical-align:baseline;"/>' .
1565 - wfMsg( 'centralnotice-select', $wgLang->commaList( $buttons ) )
1566 - );
1567 - } else {
1568 - $htmlOut .= Xml::tags( 'select',
1569 - array(
1570 - 'multiple' => 'multiple',
1571 - 'size' => 4,
1572 - 'id' => 'project_languages[]',
1573 - 'name' => 'project_languages[]',
1574 - 'disabled' => 'disabled'
1575 - ),
1576 - $options
1577 - );
1578 - }
1579 - return $htmlOut;
1580 - }
1581 -
1582 - /**
1583 - * Generates a multiple select list of all project types.
1584 - * @param $selected The name of the selected project type
1585 - * @return multiple select list
1586 - */
1587 - function projectMultiSelector( $selected = array() ) {
1588 - global $wgNoticeProjects, $wgExtensionAssetsPath, $wgLang;
1589 - $scriptPath = "$wgExtensionAssetsPath/CentralNotice";
1590 -
1591 - $options = "\n";
1592 - foreach( $wgNoticeProjects as $project ) {
1593 - $options .= Xml::option(
1594 - $project,
1595 - $project,
1596 - in_array( $project, $selected )
1597 - ) . "\n";
1598 - }
1599 - $htmlOut = '';
1600 - if ( $this->editable ) {
1601 - $htmlOut .= Xml::tags( 'select',
1602 - array( 'multiple' => 'multiple', 'size' => 4, 'id' => 'projects[]', 'name' => 'projects[]' ),
1603 - $options
1604 - );
1605 - $buttons = array();
1606 - $buttons[] = '<a href="#" onclick="selectProjects(true);return false;">' .
1607 - wfMsg( 'powersearch-toggleall' ) . '</a>';
1608 - $buttons[] = '<a href="#" onclick="selectProjects(false);return false;">' .
1609 - wfMsg( 'powersearch-togglenone' ) . '</a>';
1610 - $htmlOut .= Xml::tags( 'div',
1611 - array( 'style' => 'margin-top: 0.2em;' ),
1612 - '<img src="'.$scriptPath.'/up-arrow.png" style="vertical-align:baseline;"/>' .
1613 - wfMsg( 'centralnotice-select', $wgLang->commaList( $buttons ) )
1614 - );
1615 - } else {
1616 - $htmlOut .= Xml::tags( 'select',
1617 - array(
1618 - 'multiple' => 'multiple',
1619 - 'size' => 4,
1620 - 'id' => 'projects[]',
1621 - 'name' => 'projects[]',
1622 - 'disabled' => 'disabled'
1623 - ),
1624 - $options
1625 - );
1626 - }
1627 - return $htmlOut;
1628 - }
1629 -
1630 - function getProjectName( $value ) {
1631 - return $value; // @fixme -- use wfMsg()
1632 - }
1633 -
1634 - function updateProjectName( $notice, $projectName ) {
1635 - $dbw = wfGetDB( DB_MASTER );
1636 - $res = $dbw->update( 'cn_notices',
1637 - array ( 'not_project' => $projectName ),
1638 - array(
1639 - 'not_name' => $notice
1640 - )
1641 - );
1642 - }
1643 -
1644 - function updateProjects( $notice, $newProjects ) {
1645 - $dbw = wfGetDB( DB_MASTER );
1646 - $dbw->begin();
1647 -
1648 - // Get the previously assigned projects
1649 - $oldProjects = $this->getNoticeProjects( $notice );
1650 -
1651 - // Get the notice id
1652 - $row = $dbw->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $notice ) );
1653 -
1654 - // Add newly assigned projects
1655 - $addProjects = array_diff( $newProjects, $oldProjects );
1656 - $insertArray = array();
1657 - foreach( $addProjects as $project ) {
1658 - $insertArray[] = array( 'np_notice_id' => $row->not_id, 'np_project' => $project );
1659 - }
1660 - $res = $dbw->insert( 'cn_notice_projects', $insertArray, __METHOD__, array( 'IGNORE' ) );
1661 -
1662 - // Remove disassociated projects
1663 - $removeProjects = array_diff( $oldProjects, $newProjects );
1664 - if ( $removeProjects ) {
1665 - $res = $dbw->delete( 'cn_notice_projects',
1666 - array( 'np_notice_id' => $row->not_id, 'np_project' => $removeProjects )
1667 - );
1668 - }
1669 -
1670 - $dbw->commit();
1671 - }
1672 -
1673 - function updateProjectLanguages( $notice, $newLanguages ) {
1674 - $dbw = wfGetDB( DB_MASTER );
1675 - $dbw->begin();
1676 -
1677 - // Get the previously assigned languages
1678 - $oldLanguages = $this->getNoticeLanguages( $notice );
1679 -
1680 - // Get the notice id
1681 - $row = $dbw->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $notice ) );
1682 -
1683 - // Add newly assigned languages
1684 - $addLanguages = array_diff( $newLanguages, $oldLanguages );
1685 - $insertArray = array();
1686 - foreach( $addLanguages as $code ) {
1687 - $insertArray[] = array( 'nl_notice_id' => $row->not_id, 'nl_language' => $code );
1688 - }
1689 - $res = $dbw->insert( 'cn_notice_languages', $insertArray, __METHOD__, array( 'IGNORE' ) );
1690 -
1691 - // Remove disassociated languages
1692 - $removeLanguages = array_diff( $oldLanguages, $newLanguages );
1693 - if ( $removeLanguages ) {
1694 - $res = $dbw->delete( 'cn_notice_languages',
1695 - array( 'nl_notice_id' => $row->not_id, 'nl_language' => $removeLanguages )
1696 - );
1697 - }
1698 -
1699 - $dbw->commit();
1700 - }
1701 -
1702 - function updateCountries( $notice, $newCountries ) {
1703 - $dbw = wfGetDB( DB_MASTER );
1704 -
1705 - // Get the previously assigned languages
1706 - $oldCountries = $this->getNoticeCountries( $notice );
1707 -
1708 - // Get the notice id
1709 - $row = $dbw->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $notice ) );
1710 -
1711 - // Add newly assigned countries
1712 - $addCountries = array_diff( $newCountries, $oldCountries );
1713 - $insertArray = array();
1714 - foreach( $addCountries as $code ) {
1715 - $insertArray[] = array( 'nc_notice_id' => $row->not_id, 'nc_country' => $code );
1716 - }
1717 - $res = $dbw->insert( 'cn_notice_countries', $insertArray, __METHOD__, array( 'IGNORE' ) );
1718 -
1719 - // Remove disassociated countries
1720 - $removeCountries = array_diff( $oldCountries, $newCountries );
1721 - if ( $removeCountries ) {
1722 - $dbw->delete( 'cn_notice_countries',
1723 - array( 'nc_notice_id' => $row->not_id, 'nc_country' => $removeCountries )
1724 - );
1725 - }
1726 - }
1727 -
1728 - public static function noticeExists( $noticeName ) {
1729 - $dbr = wfGetDB( DB_SLAVE );
1730 - $eNoticeName = htmlspecialchars( $noticeName );
1731 - $row = $dbr->selectRow( 'cn_notices', 'not_name', array( 'not_name' => $eNoticeName ) );
1732 - if ( $row ) {
1733 - return true;
1734 - } else {
1735 - return false;
1736 - }
1737 - }
1738 -
1739 - public static function dropDownList( $text, $values ) {
1740 - $dropDown = "*{$text}\n";
1741 - foreach ( $values as $value ) {
1742 - $dropDown .= "**{$value}\n";
1743 - }
1744 - return $dropDown;
1745 - }
1746 -
1747 - function addZero( $text ) {
1748 - // Prepend a 0 for text needing it
1749 - if ( strlen( $text ) == 1 ) {
1750 - $text = "0{$text}";
1751 - }
1752 - return $text;
1753 - }
1754 -
1755 - function showError( $message ) {
1756 - global $wgOut;
1757 - $wgOut->wrapWikiMsg( "<div class='cn-error'>\n$1\n</div>", $message );
1758 - $this->centralNoticeError = true;
1759 - }
1760 -
1761 - /**
1762 - * Generates a multiple select list of all countries.
1763 - * @param $selected The country codes of the selected countries
1764 - * @return multiple select list
1765 - */
1766 - function geoMultiSelector( $selected = array() ) {
1767 - $countries = CentralNoticeDB::getCountriesList();
1768 - $options = "\n";
1769 - foreach( $countries as $code => $name ) {
1770 - $options .= Xml::option(
1771 - $name,
1772 - $code,
1773 - in_array( $code, $selected )
1774 - ) . "\n";
1775 - }
1776 - $htmlOut = '';
1777 - if ( $this->editable ) {
1778 - $htmlOut .= Xml::tags( 'select',
1779 - array(
1780 - 'multiple' => 'multiple',
1781 - 'size' => 5,
1782 - 'id' => 'geo_countries[]',
1783 - 'name' => 'geo_countries[]'
1784 - ),
1785 - $options
1786 - );
1787 - } else {
1788 - $htmlOut .= Xml::tags( 'select',
1789 - array(
1790 - 'multiple' => 'multiple',
1791 - 'size' => 5,
1792 - 'id' => 'geo_countries[]',
1793 - 'name' => 'geo_countries[]',
1794 - 'disabled' => 'disabled'
1795 - ),
1796 - $options
1797 - );
1798 - }
1799 - return $htmlOut;
1800 - }
1801 -
1802 - /**
1803 - * Log any changes related to a campaign
1804 - * @param $action string: 'created', 'modified', or 'removed'
1805 - * @param $campaign integer: id of campaign
1806 - * @param $beginSettings array of campaign settings before changes (optional)
1807 - * @param $endSettings array of campaign settings after changes (optional)
1808 - * @param $beginAssignments array of banner assignments before changes (optional)
1809 - * @param $endAssignments array of banner assignments after changes (optional)
1810 - */
1811 - function logCampaignChange( $action, $campaign, $beginSettings = array(),
1812 - $endSettings = array(), $beginAssignments = array(), $endAssignments = array() )
1813 - {
1814 - global $wgUser;
1815 -
1816 - $dbw = wfGetDB( DB_MASTER );
1817 -
1818 - $log = array(
1819 - 'notlog_timestamp' => $dbw->timestamp(),
1820 - 'notlog_user_id' => $wgUser->getId(),
1821 - 'notlog_action' => $action,
1822 - 'notlog_not_id' => $campaign
1823 - );
1824 -
1825 - $log = array_merge($log, $beginSettings, $endSettings);
1826 -
1827 - $res = $dbw->insert( 'cn_notice_log', $log );
1828 - $log_id = $dbw->insertId();
1829 - return $log_id;
1830 - }
1831 -}
1832 -
1833 -class CentralNoticePager extends TemplatePager {
1834 - var $viewPage, $special;
1835 - var $editable;
1836 -
1837 - function __construct( $special ) {
1838 - parent::__construct( $special );
1839 - }
1840 -
1841 - /**
1842 - * Pull banners from the database
1843 - */
1844 - function getQueryInfo() {
1845 - $notice = $this->mRequest->getVal( 'notice' );
1846 - $noticeId = CentralNotice::getNoticeId( $notice );
1847 - if ( $noticeId ) {
1848 - // Return all the banners not already assigned to the current campaign
1849 - return array(
1850 - 'tables' => array( 'cn_assignments', 'cn_templates' ),
1851 - 'fields' => array( 'cn_templates.tmp_name', 'cn_templates.tmp_id' ),
1852 - 'conds' => array( 'cn_assignments.tmp_id IS NULL' ),
1853 - 'join_conds' => array(
1854 - 'cn_assignments' => array(
1855 - 'LEFT JOIN',
1856 - "cn_assignments.tmp_id = cn_templates.tmp_id " .
1857 - "AND cn_assignments.not_id = $noticeId"
1858 - )
1859 - )
1860 - );
1861 - } else {
1862 - // Return all the banners in the database
1863 - return array(
1864 - 'tables' => 'cn_templates',
1865 - 'fields' => array( 'tmp_name', 'tmp_id' ),
1866 - );
1867 - }
1868 - }
1869 -
1870 - /**
1871 - * Generate the content of each table row (1 row = 1 banner)
1872 - */
1873 - function formatRow( $row ) {
1874 -
1875 - // Begin banner row
1876 - $htmlOut = Xml::openElement( 'tr' );
1877 -
1878 - if ( $this->editable ) {
1879 - // Add box
1880 - $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
1881 - Xml::check( 'addTemplates[]', '', array ( 'value' => $row->tmp_name ) )
1882 - );
1883 - // Weight select
1884 - $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
1885 - Xml::listDropDown( "weight[$row->tmp_id]",
1886 - CentralNotice::dropDownList(
1887 - wfMsg( 'centralnotice-weight' ), range ( 0, 100, 5 )
1888 - ) ,
1889 - '',
1890 - '25',
1891 - '',
1892 - '' )
1893 - );
1894 - }
1895 -
1896 - // Link and Preview
1897 - $render = new SpecialBannerLoader();
1898 - $render->siteName = 'Wikipedia';
1899 - $render->language = $this->mRequest->getVal( 'wpUserLanguage' );
1900 - try {
1901 - $preview = $render->getHtmlNotice( $row->tmp_name );
1902 - } catch ( SpecialBannerLoaderException $e ) {
1903 - $preview = wfMsg( 'centralnotice-nopreview' );
1904 - }
1905 - $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
1906 - $this->getSkin()->makeLinkObj( $this->viewPage,
1907 - htmlspecialchars( $row->tmp_name ),
1908 - 'template=' . urlencode( $row->tmp_name ) ) .
1909 - Xml::fieldset( wfMsg( 'centralnotice-preview' ),
1910 - $preview,
1911 - array( 'class' => 'cn-bannerpreview')
1912 - )
1913 - );
1914 -
1915 - // End banner row
1916 - $htmlOut .= Xml::closeElement( 'tr' );
1917 -
1918 - return $htmlOut;
1919 - }
1920 -
1921 - /**
1922 - * Specify table headers
1923 - */
1924 - function getStartBody() {
1925 - $htmlOut = '';
1926 - $htmlOut .= Xml::openElement( 'table', array( 'cellpadding' => 9 ) );
1927 - $htmlOut .= Xml::openElement( 'tr' );
1928 - if ( $this->editable ) {
1929 - $htmlOut .= Xml::element( 'th', array( 'align' => 'left', 'width' => '5%' ),
1930 - wfMsg ( "centralnotice-add" )
1931 - );
1932 - $htmlOut .= Xml::element( 'th', array( 'align' => 'left', 'width' => '5%' ),
1933 - wfMsg ( "centralnotice-weight" )
1934 - );
1935 - }
1936 - $htmlOut .= Xml::element( 'th', array( 'align' => 'left' ),
1937 - wfMsg ( 'centralnotice-templates' )
1938 - );
1939 - $htmlOut .= Xml::closeElement( 'tr' );
1940 - return $htmlOut;
1941 - }
1942 -
1943 - /**
1944 - * Close table
1945 - */
1946 - function getEndBody() {
1947 - $htmlOut = '';
1948 - $htmlOut .= Xml::closeElement( 'table' );
1949 - return $htmlOut;
1950 - }
1951 -}
Index: trunk/extensions/CentralNotice/SpecialBannerLoader.php
@@ -1,221 +0,0 @@
2 -<?php
3 -
4 -/**
5 - * Generates banner HTML files
6 - */
7 -class SpecialBannerLoader extends UnlistedSpecialPage {
8 - public $siteName = 'Wikipedia'; // Site name
9 - public $language = 'en'; // User language
10 - protected $sharedMaxAge = 600; // Cache for 10 minutes on the server side
11 - protected $maxAge = 0; // No client-side banner caching so we get all impressions
12 -
13 - function __construct() {
14 - // Register special page
15 - parent::__construct( "BannerLoader" );
16 - }
17 -
18 - function execute( $par ) {
19 - global $wgOut, $wgRequest;
20 -
21 - $wgOut->disable();
22 - $this->sendHeaders();
23 -
24 - // Get values from the query string
25 - $this->language = $wgRequest->getText( 'userlang', 'en' );
26 - $this->siteName = $wgRequest->getText( 'sitename', 'Wikipedia' );
27 - $this->campaign = $wgRequest->getText( 'campaign', 'unknown' );
28 - $this->fundraising = $wgRequest->getBool( 'fundraising', false );
29 - $this->landingPages = $wgRequest->getText( 'landingpages' );
30 -
31 - if ( $wgRequest->getText( 'banner' ) ) {
32 - $bannerName = $wgRequest->getText( 'banner' );
33 - try {
34 - $content = $this->getJsNotice( $bannerName );
35 - if ( preg_match( "/&lt;centralnotice-template-\w+&gt;\z/", $content ) ) {
36 - echo "/* Failed cache lookup */";
37 - } elseif ( strlen( $content ) == 0 ) {
38 - // Hack for IE/Mac 0-length keepalive problem, see RawPage.php
39 - echo "/* Empty */";
40 - } else {
41 - echo $content;
42 - }
43 - } catch (SpecialBannerLoaderException $e) {
44 - echo "/* Banner could not be generated */";
45 - }
46 - } else {
47 - echo "/* No banner specified */";
48 - }
49 - }
50 -
51 - /**
52 - * Generate the HTTP response headers for the banner file
53 - */
54 - function sendHeaders() {
55 - global $wgJsMimeType;
56 - header( "Content-type: $wgJsMimeType; charset=utf-8" );
57 - header( "Cache-Control: public, s-maxage=$this->sharedMaxAge, max-age=$this->maxAge" );
58 - }
59 -
60 - /**
61 - * Generate the JS for the requested banner
62 - * @return a string of Javascript containing a call to insertBanner()
63 - * with JSON containing the banner content as the parameter
64 - * @throws SpecialBannerLoaderException
65 - */
66 - function getJsNotice( $bannerName ) {
67 - // Make sure the banner exists
68 - if ( CentralNoticeDB::bannerExists( $bannerName ) ) {
69 - $this->bannerName = $bannerName;
70 - $bannerHtml = '';
71 - $bannerHtml .= preg_replace_callback(
72 - '/{{{(.*?)}}}/',
73 - array( $this, 'getNoticeField' ),
74 - $this->getNoticeTemplate()
75 - );
76 - $bannerArray = array(
77 - 'bannerName' => $bannerName,
78 - 'bannerHtml' => $bannerHtml,
79 - 'campaign' => $this->campaign,
80 - 'fundraising' => $this->fundraising,
81 - 'landingPages' => $this->landingPages
82 - );
83 - $bannerJs = 'insertBanner('.FormatJson::encode( $bannerArray ).');';
84 - return $bannerJs;
85 - }
86 - }
87 -
88 - /**
89 - * Generate the HTML for the requested banner
90 - * @throws SpecialBannerLoaderException
91 - */
92 - function getHtmlNotice( $bannerName ) {
93 - // Make sure the banner exists
94 - if ( CentralNoticeDB::bannerExists( $bannerName ) ) {
95 - $this->bannerName = $bannerName;
96 - $bannerHtml = '';
97 - $bannerHtml .= preg_replace_callback(
98 - '/{{{(.*?)}}}/',
99 - array( $this, 'getNoticeField' ),
100 - $this->getNoticeTemplate()
101 - );
102 - return $bannerHtml;
103 - }
104 - }
105 -
106 - /**
107 - * Get the body of the banner with only {{int:...}} messages translated
108 - */
109 - function getNoticeTemplate() {
110 - $out = $this->getMessage( "centralnotice-template-{$this->bannerName}" );
111 - return $out;
112 - }
113 -
114 - /**
115 - * Extract a message name and send to getMessage() for translation
116 - * @param $match A message array with 2 members: raw match, short name of message
117 - * @return translated messsage string
118 - * @throws SpecialBannerLoaderException
119 - */
120 - function getNoticeField( $match ) {
121 - $field = $match[1];
122 - $params = array();
123 - if ( $field == 'amount' ) {
124 - $params = array( $this->toMillions( $this->getDonationAmount() ) );
125 - }
126 - $message = "centralnotice-{$this->bannerName}-$field";
127 - $source = $this->getMessage( $message, $params );
128 - return $source;
129 - }
130 -
131 - /**
132 - * Convert number of dollars to millions of dollars
133 - */
134 - private function toMillions( $num ) {
135 - $num = sprintf( "%.1f", $num / 1e6 );
136 - if ( substr( $num, - 2 ) == '.0' ) {
137 - $num = substr( $num, 0, - 2 );
138 - }
139 - $lang = Language::factory( $this->language );
140 - return $lang->formatNum( $num );
141 - }
142 -
143 - /**
144 - * Retrieve a translated message
145 - * @param $msg The full name of the message
146 - * @return translated messsage string
147 - */
148 - private function getMessage( $msg, $params = array() ) {
149 - global $wgLang, $wgSitename;
150 -
151 - // A god-damned dirty hack! :D
152 - $oldLang = $wgLang;
153 - $oldSitename = $wgSitename;
154 -
155 - $wgSitename = $this->siteName; // hack for {{SITENAME}}
156 - $wgLang = Language::factory( $this->language ); // hack for {{int:...}}
157 -
158 - $options = array( 'language' => $this->language, 'parsemag' );
159 - array_unshift( $params, $options );
160 - array_unshift( $params, $msg );
161 - $out = call_user_func_array( 'wfMsgExt', $params );
162 -
163 - // Restore global variables
164 - $wgLang = $oldLang;
165 - $wgSitename = $oldSitename;
166 -
167 - return $out;
168 - }
169 -
170 - /**
171 - * Pull the current amount raised during a fundraiser
172 - * @throws SpecialBannerLoaderException
173 - */
174 - private function getDonationAmount() {
175 - global $wgNoticeCounterSource, $wgMemc;
176 - // Pull short-cached amount
177 - $count = intval( $wgMemc->get( wfMemcKey( 'centralnotice', 'counter' ) ) );
178 - if ( !$count ) {
179 - // Pull from dynamic counter
180 - $counter_value = Http::get( $wgNoticeCounterSource );
181 - if( !$counter_value ) {
182 - throw new RemoteServerProblemException();
183 - }
184 - $count = intval( $counter_value );
185 - if ( !$count ) {
186 - // Pull long-cached amount
187 - $count = intval( $wgMemc->get(
188 - wfMemcKey( 'centralnotice', 'counter', 'fallback' ) ) );
189 - if ( !$count ) {
190 - throw new DonationAmountUnknownException();
191 - }
192 - }
193 - // Expire in 60 seconds
194 - $wgMemc->set( wfMemcKey( 'centralnotice', 'counter' ), $count, 60 );
195 - // No expiration
196 - $wgMemc->set( wfMemcKey( 'centralnotice', 'counter', 'fallback' ), $count );
197 - }
198 - return $count;
199 - }
200 -}
201 -/**
202 - * @defgroup Exception Exception
203 - */
204 -
205 -/**
206 - * SpecialBannerLoaderException exception
207 - *
208 - * This exception is being thrown whenever
209 - * some fatal error occurs that may affect
210 - * how the banner is presented.
211 - *
212 - * @ingroup Exception
213 - */
214 -
215 -class SpecialBannerLoaderException extends Exception {
216 -}
217 -
218 -class RemoteServerProblemException extends SpecialBannerLoaderException {
219 -}
220 -
221 -class DonationAmountUnknownException extends SpecialBannerLoaderException {
222 -}
Index: trunk/extensions/CentralNotice/SpecialBannerController.php
@@ -1,210 +0,0 @@
2 -<?php
3 -
4 -/**
5 - * Generates Javascript file which controls banner selection on the client side
6 - */
7 -class SpecialBannerController extends UnlistedSpecialPage {
8 - protected $sharedMaxAge = 3600; // Cache for 1 hour on the server side
9 - protected $maxAge = 3600; // Cache for 1 hour on the client side
10 -
11 - function __construct() {
12 - // Register special page
13 - parent::__construct( "BannerController" );
14 - }
15 -
16 - function execute( $par ) {
17 - global $wgOut;
18 -
19 - $wgOut->disable();
20 - $this->sendHeaders();
21 -
22 - $content = $this->getOutput();
23 - if ( strlen( $content ) == 0 ) {
24 - // Hack for IE/Mac 0-length keepalive problem, see RawPage.php
25 - echo "/* Empty */";
26 - } else {
27 - echo $content;
28 - }
29 - }
30 -
31 - /**
32 - * Generate the HTTP response headers for the banner controller
33 - */
34 - function sendHeaders() {
35 - global $wgJsMimeType;
36 - header( "Content-type: $wgJsMimeType; charset=utf-8" );
37 - header( "Cache-Control: public, s-maxage=$this->sharedMaxAge, max-age=$this->maxAge" );
38 - }
39 -
40 - /**
41 - * Generate the body for the Javascript file
42 - *
43 - * We use a jsonp scheme for actual delivery of the banner so that they can be served from meta.
44 - * In order to circumvent the normal squid cache override we add '/cn.js' to the bannerlist URL.
45 - */
46 - function getOutput() {
47 - global $wgCentralPagePath, $wgNoticeFundraisingUrl, $wgContLang;
48 -
49 - $js = $this->getScriptFunctions() . $this->getToggleScripts();
50 - $js .= <<<JAVASCRIPT
51 -( function( $ ) {
52 - $.ajaxSetup({ cache: true });
53 - $.centralNotice = {
54 - 'data': {
55 - 'getVars': {}
56 - },
57 - 'fn': {
58 - 'loadBanner': function( bannerName, fundraising, landingPages, campaign ) {
59 - // Get the requested banner
60 - var bannerPageQuery = $.param( {
61 - 'banner': bannerName, 'campaign': campaign, 'userlang': wgUserLanguage,
62 - 'db': wgDBname, 'sitename': wgSiteName, 'country': Geo.country,
63 - 'fundraising': fundraising, 'landingpages': landingPages
64 - } );
65 - var bannerPage = '?title=Special:BannerLoader&' + bannerPageQuery;
66 -JAVASCRIPT;
67 - $js .= "\n\t\t\t\tvar bannerScript = '<script type=\"text/javascript\" src=\"" .
68 - Xml::escapeJsString( $wgCentralPagePath ) .
69 - "' + bannerPage + '\"></script>';\n";
70 - $js .= <<<JAVASCRIPT
71 - $( '#siteNotice' ).prepend( '<div id="centralNotice" class="' +
72 - ( wgNoticeToggleState ? 'expanded' : 'collapsed' ) +
73 - '">'+bannerScript+'</div>' );
74 - },
75 - 'loadBannerList': function( geoOverride ) {
76 - if ( geoOverride ) {
77 - var geoLocation = geoOverride; // override the geo info
78 - } else {
79 - var geoLocation = Geo.country; // pull the geo info
80 - }
81 - var bannerListQuery = $.param( { 'language': wgContentLanguage, 'project': wgNoticeProject, 'country': geoLocation } );
82 -JAVASCRIPT;
83 - $js .= "\n\t\t\t\tvar bannerListURL = wgScript + '?title=' + encodeURIComponent('" .
84 - $wgContLang->specialPage( 'BannerListLoader' ) .
85 - "') + '&cache=/cn.js&' + bannerListQuery;\n";
86 - $js .= <<<JAVASCRIPT
87 - var request = $.ajax( {
88 - url: bannerListURL,
89 - dataType: 'json',
90 - success: $.centralNotice.fn.chooseBanner
91 - } );
92 - },
93 - 'chooseBanner': function( bannerList ) {
94 - // Convert the json object to a true array
95 - bannerList = Array.prototype.slice.call( bannerList );
96 -
97 - // Make sure there are some banners to choose from
98 - if ( bannerList.length == 0 ) return false;
99 -
100 - var groomedBannerList = [];
101 -
102 - for( var i = 0; i < bannerList.length; i++ ) {
103 - // Only include this banner if it's intended for the current user
104 - if( ( wgUserName && bannerList[i].display_account ) ||
105 - ( !wgUserName && bannerList[i].display_anon == 1 ) )
106 - {
107 - // add the banner to our list once per weight
108 - for( var j=0; j < bannerList[i].weight; j++ ) {
109 - groomedBannerList.push( bannerList[i] );
110 - }
111 - }
112 - }
113 -
114 - // Return if there's nothing left after the grooming
115 - if( groomedBannerList.length == 0 ) return false;
116 -
117 - // Choose a random key
118 - var pointer = Math.floor( Math.random() * groomedBannerList.length );
119 -
120 - // Load a random banner from our groomed list
121 - $.centralNotice.fn.loadBanner(
122 - groomedBannerList[pointer].name,
123 - groomedBannerList[pointer].fundraising,
124 - groomedBannerList[pointer].landing_pages,
125 - groomedBannerList[pointer].campaign
126 - );
127 - },
128 - 'getQueryStringVariables': function() {
129 - document.location.search.replace( /\??(?:([^=]+)=([^&]*)&?)/g, function () {
130 - function decode( s ) {
131 - return decodeURIComponent( s.split( "+" ).join( " " ) );
132 - }
133 - $.centralNotice.data.getVars[decode( arguments[1] )] = decode( arguments[2] );
134 - } );
135 - }
136 - }
137 - }
138 - $( document ).ready( function () {
139 - // Initialize the query string vars
140 - $.centralNotice.fn.getQueryStringVariables();
141 - if( $.centralNotice.data.getVars['banner'] ) {
142 - // if we're forcing one banner
143 - $.centralNotice.fn.loadBanner( $.centralNotice.data.getVars['banner'] );
144 - } else {
145 - // Look for banners ready to go NOW
146 - $.centralNotice.fn.loadBannerList( $.centralNotice.data.getVars['country'] );
147 - }
148 - } ); //document ready
149 -} )( jQuery );
150 -JAVASCRIPT;
151 - return $js;
152 -
153 - }
154 -
155 - function getToggleScripts() {
156 - $script = "var wgNoticeToggleState = (document.cookie.indexOf('hidesnmessage=1')==-1);\n\n";
157 - return $script;
158 - }
159 -
160 - function getScriptFunctions() {
161 - global $wgNoticeFundraisingUrl;
162 - $script = <<<JAVASCRIPT
163 -function insertBanner(bannerJson) {
164 - jQuery( 'div#centralNotice' ).prepend( bannerJson.bannerHtml );
165 - if ( bannerJson.fundraising ) {
166 -JAVASCRIPT;
167 - $script .= "\n\t\tvar url = '" .
168 - Xml::escapeJsString( $wgNoticeFundraisingUrl ) . "';";
169 - $script .= <<<JAVASCRIPT
170 - if ( bannerJson.landingPages.length ) {
171 - targets = String( bannerJson.landingPages ).split(',');
172 - url += "?" + jQuery.param( {
173 - 'landing_page': targets[Math.floor( Math.random() * targets.length )].replace( /^\s+|\s+$/, '' )
174 - } );
175 - url += "&" + jQuery.param( {
176 - 'utm_medium': 'sitenotice', 'utm_campaign': bannerJson.campaign,
177 - 'utm_source': bannerJson.bannerName, 'language': wgUserLanguage,
178 - 'country': Geo.country
179 - } );
180 - jQuery( '#cn_fundraising_link' ).attr( 'href', url );
181 - }
182 - }
183 -}
184 -function toggleNotice() {
185 - var notice = document.getElementById('centralNotice');
186 - if (!wgNoticeToggleState) {
187 - notice.className = notice.className.replace('collapsed', 'expanded');
188 - toggleNoticeCookie('0');
189 - } else {
190 - notice.className = notice.className.replace('expanded', 'collapsed');
191 - toggleNoticeCookie('1');
192 - }
193 - wgNoticeToggleState = !wgNoticeToggleState;
194 -}
195 -function toggleNoticeStyle(elems, display) {
196 - if(elems)
197 - for(var i=0;i<elems.length;i++)
198 - elems[i].style.display = display;
199 -}
200 -function toggleNoticeCookie(state) {
201 - var e = new Date();
202 - e.setTime( e.getTime() + (7*24*60*60*1000) ); // one week
203 - var work='hidesnmessage='+state+'; expires=' + e.toGMTString() + '; path=/';
204 - document.cookie = work;
205 -}
206 -
207 -JAVASCRIPT;
208 - return $script;
209 - }
210 -
211 -}
Index: trunk/extensions/CentralNotice/SpecialBannerAllocation.php
@@ -1,201 +0,0 @@
2 -<?php
3 -
4 -if ( !defined( 'MEDIAWIKI' ) ) {
5 - echo "CentralNotice extension\n";
6 - exit( 1 );
7 -}
8 -
9 -class SpecialBannerAllocation extends UnlistedSpecialPage {
10 - public $project = 'wikipedia';
11 - public $language = 'en';
12 - public $location = 'US';
13 -
14 - function __construct() {
15 - // Register special page
16 - parent::__construct( "BannerAllocation" );
17 - }
18 -
19 - /**
20 - * Handle different types of page requests
21 - */
22 - function execute( $sub ) {
23 - global $wgOut, $wgRequest, $wgExtensionAssetsPath, $wgNoticeProjects, $wgLanguageCode;
24 -
25 - if ( $wgRequest->wasPosted() ) {
26 - $this->project = $wgRequest->getText( 'project', 'wikipedia' );
27 - $this->language = $wgRequest->getText( 'language', 'en' );
28 - $this->location = $wgRequest->getText( 'country', 'US' );
29 - }
30 -
31 - // Begin output
32 - $this->setHeaders();
33 -
34 - // Add style file to the output headers
35 - $wgOut->addExtensionStyle( "$wgExtensionAssetsPath/CentralNotice/centralnotice.css" );
36 -
37 - // Add script file to the output headers
38 - $wgOut->addScriptFile( "$wgExtensionAssetsPath/CentralNotice/centralnotice.js" );
39 -
40 - // Initialize error variable
41 - $this->centralNoticeError = false;
42 -
43 - // Show summary
44 - $wgOut->addWikiMsg( 'centralnotice-summary' );
45 -
46 - // Show header
47 - CentralNotice::printHeader();
48 -
49 - // Begin Banners tab content
50 - $wgOut->addHTML( Xml::openElement( 'div', array( 'id' => 'preferences' ) ) );
51 -
52 - $htmlOut = '';
53 -
54 - // Begin Allocation selection fieldset
55 - $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
56 -
57 - $htmlOut .= Xml::openElement( 'form', array( 'method' => 'post' ) );
58 - $htmlOut .= Xml::element( 'h2', null, wfMsg( 'centralnotice-view-allocation' ) );
59 - $htmlOut .= Xml::tags( 'p', null, wfMsg( 'centralnotice-allocation-instructions' ) );
60 -
61 - $htmlOut .= Xml::openElement( 'table', array ( 'id' => 'envpicker', 'cellpadding' => 7 ) );
62 - $htmlOut .= Xml::openElement( 'tr' );
63 - $htmlOut .= Xml::tags( 'td',
64 - array( 'style' => 'width: 20%;' ),
65 - wfMsg( 'centralnotice-project-name' ) );
66 - $htmlOut .= Xml::openElement( 'td' );
67 - $htmlOut .= Xml::openElement( 'select', array( 'name' => 'project' ) );
68 - foreach ( $wgNoticeProjects as $value ) {
69 - $htmlOut .= Xml::option( $value, $value, $value === $this->project );
70 - }
71 - $htmlOut .= Xml::closeElement( 'select' );
72 - $htmlOut .= Xml::closeElement( 'td' );
73 - $htmlOut .= Xml::closeElement( 'tr' );
74 - $htmlOut .= Xml::openElement( 'tr' );
75 - $htmlOut .= Xml::tags( 'td',
76 - array( 'valign' => 'top' ),
77 - wfMsg( 'centralnotice-project-lang' ) );
78 - $htmlOut .= Xml::openElement( 'td' );
79 - // Make sure the site language is in the list; a custom language code
80 - // might not have a defined name...
81 - $languages = Language::getLanguageNames( true );
82 - if( !array_key_exists( $wgLanguageCode, $languages ) ) {
83 - $languages[$wgLanguageCode] = $wgLanguageCode;
84 - }
85 - ksort( $languages );
86 - $htmlOut .= Xml::openElement( 'select', array( 'name' => 'language' ) );
87 - foreach( $languages as $code => $name ) {
88 - $htmlOut .= Xml::option(
89 - wfMsg( 'centralnotice-language-listing', $code, $name ),
90 - $code, $code === $this->language );
91 - }
92 - $htmlOut .= Xml::closeElement( 'select' );
93 - $htmlOut .= Xml::closeElement( 'td' );
94 - $htmlOut .= Xml::closeElement( 'tr' );
95 - $htmlOut .= Xml::openElement( 'tr' );
96 - $htmlOut .= Xml::tags( 'td', array(), wfMsg( 'centralnotice-country' ) );
97 - $htmlOut .= Xml::openElement( 'td' );
98 - $countries = CentralNoticeDB::getCountriesList();
99 - $htmlOut .= Xml::openElement( 'select', array( 'name' => 'country' ) );
100 - foreach( $countries as $code => $name ) {
101 - $htmlOut .= Xml::option( $name, $code, $code === $this->location );
102 - }
103 - $htmlOut .= Xml::closeElement( 'select' );
104 - $htmlOut .= Xml::closeElement( 'td' );
105 - $htmlOut .= Xml::closeElement( 'tr' );
106 - $htmlOut .= Xml::closeElement( 'table' );
107 -
108 - $htmlOut .= Xml::tags( 'div',
109 - array( 'class' => 'cn-buttons' ),
110 - Xml::submitButton( wfMsg( 'centralnotice-modify' ) )
111 - );
112 - $htmlOut .= Xml::closeElement( 'form' );
113 -
114 - // End Allocation selection fieldset
115 - $htmlOut .= Xml::closeElement( 'fieldset' );
116 -
117 - $wgOut->addHTML( $htmlOut );
118 -
119 - // Handle form submissions
120 - if ( $wgRequest->wasPosted() ) {
121 - $this->showList();
122 - }
123 -
124 - // End Banners tab content
125 - $wgOut->addHTML( Xml::closeElement( 'div' ) );
126 - }
127 -
128 - /**
129 - * Show a list of banners with allocation. Newer banners are shown first.
130 - */
131 - function showList() {
132 - global $wgOut, $wgUser, $wgRequest, $wgLang;
133 -
134 - $sk = $wgUser->getSkin();
135 - $viewBanner = $this->getTitleFor( 'NoticeTemplate', 'view' );
136 - $viewCampaign = $this->getTitleFor( 'CentralNotice' );
137 -
138 - // Begin building HTML
139 - $htmlOut = '';
140 -
141 - // Begin Allocation list fieldset
142 - $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
143 -
144 - $bannerLister = new SpecialBannerListLoader();
145 - $bannerLister->project = $wgRequest->getVal( 'project' );
146 - $bannerLister->language = $wgRequest->getVal( 'language' );
147 - $bannerLister->location = $wgRequest->getVal( 'country' );
148 -
149 - $htmlOut .= Xml::tags( 'p', null,
150 - wfMsg (
151 - 'centralnotice-allocation-description',
152 - htmlspecialchars( $bannerLister->language ),
153 - htmlspecialchars( $bannerLister->project ),
154 - htmlspecialchars( $bannerLister->location )
155 - )
156 - );
157 -
158 - $bannerList = $bannerLister->getJsonList();
159 - $banners = FormatJson::decode( $bannerList, true );
160 - $totalWeight = 0;
161 - foreach ( $banners as $banner ) {
162 - $totalWeight += $banner['weight'];
163 - }
164 - if ( $banners ) {
165 - $htmlOut .= Xml::openElement( 'table',
166 - array ( 'cellpadding' => 9, 'class' => 'wikitable sortable' ) );
167 - $htmlOut .= Xml::openElement( 'tr' );
168 - $htmlOut .= Xml::element( 'th', array( 'width' => '20%' ),
169 - wfMsg ( 'centralnotice-percentage' ) );
170 - $htmlOut .= Xml::element( 'th', array( 'width' => '30%' ),
171 - wfMsg ( 'centralnotice-banner' ) );
172 - $htmlOut .= Xml::element( 'th', array( 'width' => '30%' ),
173 - wfMsg ( 'centralnotice-notice' ) );
174 - $htmlOut .= Xml::closeElement( 'tr' );
175 - foreach ( $banners as $banner ) {
176 - $htmlOut .= Xml::openElement( 'tr' );
177 - $htmlOut .= Xml::openElement( 'td' );
178 - $percentage = ( $banner['weight'] / $totalWeight ) * 100;
179 - $htmlOut .= wfMsg ( 'percent', $wgLang->formatNum( $percentage ) );
180 - $htmlOut .= Xml::closeElement( 'td' );
181 - $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
182 - $sk->makeLinkObj( $viewBanner, htmlspecialchars( $banner['name'] ),
183 - 'template=' . urlencode( $banner['name'] ) )
184 - );
185 - $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
186 - $sk->makeLinkObj( $viewCampaign, htmlspecialchars( $banner['campaign'] ),
187 - 'method=listNoticeDetail&notice=' . urlencode( $banner['campaign'] ) )
188 - );
189 - $htmlOut .= Xml::closeElement( 'tr' );
190 - }
191 - $htmlOut .= Xml::closeElement( 'table' );
192 - } else {
193 - $htmlOut .= Xml::tags( 'p', null, wfMsg ( 'centralnotice-no-allocation' ) );
194 - }
195 -
196 - // End Allocation list fieldset
197 - $htmlOut .= Xml::closeElement( 'fieldset' );
198 -
199 - $wgOut->addHTML( $htmlOut );
200 - }
201 -
202 -}
Index: trunk/extensions/CentralNotice/SpecialNoticeTemplate.php
@@ -1,964 +0,0 @@
2 -<?php
3 -
4 -if ( !defined( 'MEDIAWIKI' ) ) {
5 - echo "CentralNotice extension\n";
6 - exit( 1 );
7 -}
8 -
9 -class SpecialNoticeTemplate extends UnlistedSpecialPage {
10 - var $editable, $centralNoticeError;
11 -
12 - function __construct() {
13 - // Register special page
14 - parent::__construct( 'NoticeTemplate' );
15 - }
16 -
17 - /**
18 - * Handle different types of page requests
19 - */
20 - function execute( $sub ) {
21 - global $wgOut, $wgUser, $wgRequest, $wgExtensionAssetsPath;
22 -
23 - // Begin output
24 - $this->setHeaders();
25 -
26 - // Add style file to the output headers
27 - $wgOut->addExtensionStyle( "$wgExtensionAssetsPath/CentralNotice/centralnotice.css" );
28 -
29 - // Add localized script error messages
30 - $scriptVars = array(
31 - 'documentWriteError' => wfMsg( 'centralnotice-documentwrite-error' )
32 - );
33 - $wgOut->addScript( Skin::makeVariablesScript( $scriptVars ) );
34 -
35 - // Add script file to the output headers
36 - $wgOut->addScriptFile( "$wgExtensionAssetsPath/CentralNotice/centralnotice.js" );
37 -
38 - // Check permissions
39 - $this->editable = $wgUser->isAllowed( 'centralnotice-admin' );
40 -
41 - // Initialize error variable
42 - $this->centralNoticeError = false;
43 -
44 - // Show summary
45 - $wgOut->addWikiMsg( 'centralnotice-summary' );
46 -
47 - // Show header
48 - CentralNotice::printHeader();
49 -
50 - // Begin Banners tab content
51 - $wgOut->addHTML( Xml::openElement( 'div', array( 'id' => 'preferences' ) ) );
52 -
53 - $method = $wgRequest->getVal( 'wpMethod' );
54 -
55 - // Handle form submissions
56 - if ( $this->editable && $wgRequest->wasPosted() ) {
57 -
58 - // Check authentication token
59 - if ( $wgUser->matchEditToken( $wgRequest->getVal( 'authtoken' ) ) ) {
60 -
61 - // Handle removing banners
62 - $toRemove = $wgRequest->getArray( 'removeTemplates' );
63 - if ( isset( $toRemove ) ) {
64 - // Remove banners in list
65 - foreach ( $toRemove as $template ) {
66 - $this->removeTemplate( $template );
67 - }
68 - }
69 -
70 - // Handle translation message update
71 - $update = $wgRequest->getArray( 'updateText' );
72 - if ( isset ( $update ) ) {
73 - foreach ( $update as $lang => $messages ) {
74 - foreach ( $messages as $text => $translation ) {
75 - // If we actually have text
76 - if ( $translation ) {
77 - $this->updateMessage( $text, $translation, $lang );
78 - }
79 - }
80 - }
81 - }
82 -
83 - // Handle adding banner
84 - if ( $method == 'addTemplate' ) {
85 - $newTemplateName = $wgRequest->getText( 'templateName' );
86 - $newTemplateBody = $wgRequest->getText( 'templateBody' );
87 - if ( $newTemplateName != '' && $newTemplateBody != '' ) {
88 - $this->addTemplate(
89 - $newTemplateName,
90 - $newTemplateBody,
91 - $wgRequest->getBool( 'displayAnon' ),
92 - $wgRequest->getBool( 'displayAccount' ),
93 - $wgRequest->getBool( 'fundraising' ),
94 - $wgRequest->getVal( 'landingPages' )
95 - );
96 - $sub = 'view';
97 - } else {
98 - $this->showError( 'centralnotice-null-string' );
99 - }
100 - }
101 -
102 - // Handle editing banner
103 - if ( $method == 'editTemplate' ) {
104 - $this->editTemplate(
105 - $wgRequest->getText( 'template' ),
106 - $wgRequest->getText( 'templateBody' ),
107 - $wgRequest->getBool( 'displayAnon' ),
108 - $wgRequest->getBool( 'displayAccount' ),
109 - $wgRequest->getBool( 'fundraising' ),
110 - $wgRequest->getVal( 'landingPages' )
111 - );
112 - $sub = 'view';
113 - }
114 -
115 - } else {
116 - $this->showError( 'sessionfailure' );
117 - }
118 -
119 - }
120 -
121 - // Handle viewing of a banner in all languages
122 - if ( $sub == 'view' && $wgRequest->getVal( 'wpUserLanguage' ) == 'all' ) {
123 - $template = $wgRequest->getVal( 'template' );
124 - $this->showViewAvailable( $template );
125 - $wgOut->addHTML( Xml::closeElement( 'div' ) );
126 - return;
127 - }
128 -
129 - // Handle viewing a specific banner
130 - if ( $sub == 'view' && $wgRequest->getText( 'template' ) != '' ) {
131 - $this->showView();
132 - $wgOut->addHTML( Xml::closeElement( 'div' ) );
133 - return;
134 - }
135 -
136 - if ( $this->editable ) {
137 - // Handle showing "Add a banner" interface
138 - if ( $sub == 'add' ) {
139 - $this->showAdd();
140 - $wgOut->addHTML( Xml::closeElement( 'div' ) );
141 - return;
142 - }
143 -
144 - // Handle cloning a specific banner
145 - if ( $sub == 'clone' ) {
146 -
147 - // Check authentication token
148 - if ( $wgUser->matchEditToken( $wgRequest->getVal( 'authtoken' ) ) ) {
149 -
150 - $oldTemplate = $wgRequest->getVal( 'oldTemplate' );
151 - $newTemplate = $wgRequest->getVal( 'newTemplate' );
152 - // We use the returned name in case any special characters had to be removed
153 - $template = $this->cloneTemplate( $oldTemplate, $newTemplate );
154 - $wgOut->redirect(
155 - $this->getTitle( 'view' )->getLocalUrl( "template=$template" ) );
156 - return;
157 -
158 - } else {
159 - $this->showError( 'sessionfailure' );
160 - }
161 -
162 - }
163 -
164 - }
165 -
166 - // Show list of banners by default
167 - $this->showList();
168 -
169 - // End Banners tab content
170 - $wgOut->addHTML( Xml::closeElement( 'div' ) );
171 - }
172 -
173 - /**
174 - * Show a list of available banners. Newer banners are shown first.
175 - */
176 - function showList() {
177 - global $wgOut, $wgUser;
178 -
179 - $sk = $wgUser->getSkin();
180 - $pager = new TemplatePager( $this );
181 -
182 - // Begin building HTML
183 - $htmlOut = '';
184 -
185 - // Begin Manage Banners fieldset
186 - $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
187 -
188 - if ( !$pager->getNumRows() ) {
189 - $htmlOut .= Xml::element( 'p', null, wfMsg( 'centralnotice-no-templates' ) );
190 - } else {
191 - if ( $this->editable ) {
192 - $htmlOut .= Xml::openElement( 'form', array( 'method' => 'post' ) );
193 - }
194 - $htmlOut .= Xml::element( 'h2', null, wfMsg( 'centralnotice-manage-templates' ) );
195 -
196 - // Show paginated list of banners
197 - $htmlOut .= Xml::tags( 'div', array( 'class' => 'cn-pager' ),
198 - $pager->getNavigationBar() );
199 - $htmlOut .= $pager->getBody();
200 - $htmlOut .= Xml::tags( 'div', array( 'class' => 'cn-pager' ),
201 - $pager->getNavigationBar() );
202 -
203 - if ( $this->editable ) {
204 - $htmlOut .= Xml::closeElement( 'form' );
205 - }
206 - }
207 -
208 - if ( $this->editable ) {
209 - $htmlOut .= Xml::element( 'p' );
210 - $newPage = $this->getTitle( 'add' );
211 - $htmlOut .= $sk->makeLinkObj( $newPage, wfMsgHtml( 'centralnotice-add-template' ) );
212 - }
213 -
214 - // End Manage Banners fieldset
215 - $htmlOut .= Xml::closeElement( 'fieldset' );
216 -
217 - $wgOut->addHTML( $htmlOut );
218 - }
219 -
220 - /**
221 - * Show "Add a banner" interface
222 - */
223 - function showAdd() {
224 - global $wgOut, $wgUser, $wgExtensionAssetsPath, $wgLang, $wgRequest,
225 - $wgNoticeEnableFundraising;
226 - $scriptPath = "$wgExtensionAssetsPath/CentralNotice";
227 -
228 - // Build HTML
229 - $htmlOut = '';
230 - $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
231 - $htmlOut .= Xml::openElement( 'form',
232 - array( 'method' => 'post', 'onsubmit' => 'return validateBannerForm(this)' ) );
233 - $htmlOut .= Xml::element( 'h2', null, wfMsg( 'centralnotice-add-template' ) );
234 - $htmlOut .= Html::hidden( 'wpMethod', 'addTemplate' );
235 -
236 - // If there was an error, we'll need to restore the state of the form
237 - if ( $wgRequest->wasPosted() ) {
238 - $templateName = $wgRequest->getVal( 'templateName' );
239 - $displayAnon = $wgRequest->getCheck( 'displayAnon' );
240 - $displayAccount = $wgRequest->getCheck( 'displayAccount' );
241 - $fundraising = $wgRequest->getCheck( 'fundraising' );
242 - $landingPages = $wgRequest->getVal( 'landingPages' );
243 - $body = $wgRequest->getVal( 'templateBody' );
244 - } else { // Use default values
245 - $templateName = '';
246 - $displayAnon = true;
247 - $displayAccount = true;
248 - $fundraising = false;
249 - $landingPages = '';
250 - $body = '';
251 - }
252 -
253 - $htmlOut .= Xml::tags( 'p', null,
254 - Xml::inputLabel(
255 - wfMsg( 'centralnotice-banner-name' ),
256 - 'templateName', 'templateName', 25, $templateName
257 - )
258 - );
259 -
260 - // Display settings
261 - $htmlOut .= Xml::openElement( 'p', null );
262 - $htmlOut .= wfMsg( 'centralnotice-banner-display' );
263 - $htmlOut .= Xml::check( 'displayAnon', $displayAnon, array( 'id' => 'displayAnon' ) );
264 - $htmlOut .= Xml::label( wfMsg( 'centralnotice-banner-anonymous' ), 'displayAnon' );
265 - $htmlOut .= Xml::check( 'displayAccount', $displayAccount,
266 - array( 'id' => 'displayAccount' ) );
267 - $htmlOut .= Xml::label( wfMsg( 'centralnotice-banner-logged-in' ), 'displayAccount' );
268 - $htmlOut .= Xml::closeElement( 'p' );
269 -
270 - // Fundraising settings
271 - if ( $wgNoticeEnableFundraising ) {
272 - $htmlOut .= Xml::openElement( 'p', null );
273 - $htmlOut .= Xml::check( 'fundraising', $fundraising, array( 'id' => 'fundraising' ) );
274 - $htmlOut .= Xml::label( wfMsg( 'centralnotice-banner-fundraising' ), 'fundraising' );
275 - $htmlOut .= Xml::closeElement( 'p' );
276 - $htmlOut .= Xml::openElement( 'div',
277 - array( 'id' => 'fundraisingInterface', 'style' => 'display: none;' ) );
278 - $htmlOut .= Xml::tags( 'p', array(), wfMsg( 'centralnotice-banner-fundraising-help' ) );
279 - $htmlOut .= Xml::tags( 'p', array(),
280 - Xml::inputLabel(
281 - wfMsg( 'centralnotice-banner-landing-pages' ),
282 - 'landingPages', 'landingPages', 40, $landingPages,
283 - array( 'maxlength' => 255 )
284 - )
285 - );
286 - $htmlOut .= Xml::closeElement( 'div' );
287 - }
288 -
289 - // Begin banner body section
290 - $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-banner' ) );
291 - $htmlOut .= wfMsg( 'centralnotice-edit-template-summary' );
292 - $buttons = array();
293 - $buttons[] = '<a href="#" onclick="insertButton(\'close\');return false;">' .
294 - wfMsg( 'centralnotice-close-button' ) . '</a>';
295 - $htmlOut .= Xml::tags( 'div',
296 - array( 'style' => 'margin-bottom: 0.2em;' ),
297 - '<img src="'.$scriptPath.'/down-arrow.png" style="vertical-align:baseline;"/>' .
298 - wfMsg( 'centralnotice-insert', $wgLang->commaList( $buttons ) )
299 - );
300 -
301 - $htmlOut .= Xml::textarea( 'templateBody', $body, 60, 20 );
302 - $htmlOut .= Xml::closeElement( 'fieldset' );
303 - $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
304 -
305 - // Submit button
306 - $htmlOut .= Xml::tags( 'div',
307 - array( 'class' => 'cn-buttons' ),
308 - Xml::submitButton( wfMsg( 'centralnotice-save-banner' ) )
309 - );
310 -
311 - $htmlOut .= Xml::closeElement( 'form' );
312 - $htmlOut .= Xml::closeElement( 'fieldset' );
313 -
314 - // Output HTML
315 - $wgOut->addHTML( $htmlOut );
316 - }
317 -
318 - /**
319 - * View or edit an individual banner
320 - */
321 - private function showView() {
322 - global $wgOut, $wgUser, $wgRequest, $wgLanguageCode, $wgExtensionAssetsPath, $wgLang,
323 - $wgNoticeEnableFundraising;
324 -
325 - $scriptPath = "$wgExtensionAssetsPath/CentralNotice";
326 - $sk = $wgUser->getSkin();
327 -
328 - if ( $this->editable ) {
329 - $readonly = array();
330 - $disabled = array();
331 - } else {
332 - $readonly = array( 'readonly' => 'readonly' );
333 - $disabled = array( 'disabled' => 'disabled' );
334 - }
335 -
336 - // Get user's language
337 - $wpUserLang = $wgRequest->getVal( 'wpUserLanguage', $wgLanguageCode );
338 -
339 - // Get current banner
340 - $currentTemplate = $wgRequest->getText( 'template' );
341 -
342 - // Pull banner settings from database
343 - $dbr = wfGetDB( DB_SLAVE );
344 - $row = $dbr->selectRow( 'cn_templates',
345 - array(
346 - 'tmp_display_anon',
347 - 'tmp_display_account',
348 - 'tmp_fundraising',
349 - 'tmp_landing_pages'
350 - ),
351 - array( 'tmp_name' => $currentTemplate ),
352 - __METHOD__
353 - );
354 -
355 - if ( !$row ) {
356 - $this->showError( 'centralnotice-banner-doesnt-exist' );
357 - return;
358 - } else {
359 - // Begin building HTML
360 - $htmlOut = '';
361 -
362 - // Begin View Banner fieldset
363 - $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
364 -
365 - $htmlOut .= Xml::element( 'h2', null,
366 - wfMsg( 'centralnotice-banner-heading', $currentTemplate ) );
367 -
368 - // Show preview of banner
369 - $render = new SpecialBannerLoader();
370 - $render->siteName = 'Wikipedia';
371 - $render->language = $wpUserLang;
372 - try {
373 - $preview = $render->getHtmlNotice( $wgRequest->getText( 'template' ) );
374 - } catch ( SpecialBannerLoaderException $e ) {
375 - $preview = wfMsg( 'centralnotice-nopreview' );
376 - }
377 - if ( $render->language != '' ) {
378 - $htmlOut .= Xml::fieldset(
379 - wfMsg( 'centralnotice-preview' ) . " ($render->language)",
380 - $preview
381 - );
382 - } else {
383 - $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-preview' ),
384 - $preview
385 - );
386 - }
387 -
388 - // Pull banner text and respect any inc: markup
389 - $bodyPage = Title::newFromText(
390 - "Centralnotice-template-{$currentTemplate}", NS_MEDIAWIKI );
391 - $curRev = Revision::newFromTitle( $bodyPage );
392 - $body = $curRev ? $curRev->getText() : '';
393 -
394 - // Extract message fields from the banner body
395 - $fields = array();
396 - $allowedChars = Title::legalChars();
397 - preg_match_all( "/\{\{\{([$allowedChars]+)\}\}\}/u", $body, $fields );
398 -
399 - // If there are any message fields in the banner, display translation tools.
400 - if ( count( $fields[0] ) > 0 ) {
401 - if ( $this->editable ) {
402 - $htmlOut .= Xml::openElement( 'form', array( 'method' => 'post' ) );
403 - }
404 - $htmlOut .= Xml::fieldset(
405 - wfMsg( 'centralnotice-translate-heading', $currentTemplate ),
406 - false,
407 - array( 'id' => 'mw-centralnotice-translations-for' )
408 - );
409 - $htmlOut .= Xml::openElement( 'table',
410 - array (
411 - 'cellpadding' => 9,
412 - 'width' => '100%'
413 - )
414 - );
415 -
416 - // Table headers
417 - $htmlOut .= Xml::element( 'th', array( 'width' => '15%' ),
418 - wfMsg( 'centralnotice-message' ) );
419 - $htmlOut .= Xml::element( 'th', array( 'width' => '5%' ),
420 - wfMsg ( 'centralnotice-number-uses' ) );
421 - $htmlOut .= Xml::element( 'th', array( 'width' => '40%' ),
422 - wfMsg ( 'centralnotice-english' ) );
423 - $languages = Language::getLanguageNames();
424 - $htmlOut .= Xml::element( 'th', array( 'width' => '40%' ),
425 - $languages[$wpUserLang] );
426 -
427 - // Remove duplicate message fields
428 - $filteredFields = array();
429 - foreach ( $fields[1] as $field ) {
430 - $filteredFields[$field] = array_key_exists( $field, $filteredFields )
431 - ? $filteredFields[$field] + 1 : 1;
432 - }
433 -
434 - // Table rows
435 - foreach ( $filteredFields as $field => $count ) {
436 - // Message
437 - $message = ( $wpUserLang == 'en' )
438 - ? "Centralnotice-{$currentTemplate}-{$field}"
439 - : "Centralnotice-{$currentTemplate}-{$field}/{$wpUserLang}";
440 -
441 - // English value
442 - $htmlOut .= Xml::openElement( 'tr' );
443 -
444 - $title = Title::newFromText( "MediaWiki:{$message}" );
445 - $htmlOut .= Xml::tags( 'td', null,
446 - $sk->makeLinkObj( $title, htmlspecialchars( $field ) )
447 - );
448 -
449 - $htmlOut .= Xml::element( 'td', null, $count );
450 -
451 - // English text
452 - $englishText = wfMsg( 'centralnotice-message-not-set' );
453 - $englishTextExists = false;
454 - if (
455 - Title::newFromText(
456 - "Centralnotice-{$currentTemplate}-{$field}", NS_MEDIAWIKI
457 - )->exists() )
458 - {
459 - $englishText = wfMsgExt( "Centralnotice-{$currentTemplate}-{$field}",
460 - array( 'language' => 'en' )
461 - );
462 - $englishTextExists = true;
463 - }
464 - $htmlOut .= Xml::tags( 'td', null,
465 - Xml::element( 'span',
466 - array(
467 - 'style' => 'font-style:italic;' .
468 - ( !$englishTextExists ? 'color:silver' : '' )
469 - ),
470 - $englishText
471 - )
472 - );
473 -
474 - // Foreign text input
475 - $foreignText = '';
476 - $foreignTextExists = false;
477 - if ( Title::newFromText( $message, NS_MEDIAWIKI )->exists() ) {
478 - $foreignText = wfMsgExt( "Centralnotice-{$currentTemplate}-{$field}",
479 - array( 'language' => $wpUserLang )
480 - );
481 - $foreignTextExists = true;
482 - }
483 - $htmlOut .= Xml::tags( 'td', null,
484 - Xml::input(
485 - "updateText[{$wpUserLang}][{$currentTemplate}-{$field}]",
486 - '',
487 - $foreignText,
488 - wfArrayMerge( $readonly,
489 - array( 'style' => 'width:100%;' .
490 - ( !$foreignTextExists ? 'color:red' : '' ) ) )
491 - )
492 - );
493 - $htmlOut .= Xml::closeElement( 'tr' );
494 - }
495 - $htmlOut .= Xml::closeElement( 'table' );
496 -
497 - if ( $this->editable ) {
498 - $htmlOut .= Html::hidden( 'wpUserLanguage', $wpUserLang );
499 - $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
500 - $htmlOut .= Xml::tags( 'div',
501 - array( 'class' => 'cn-buttons' ),
502 - Xml::submitButton(
503 - wfMsg( 'centralnotice-modify' ),
504 - array( 'name' => 'update' )
505 - )
506 - );
507 - }
508 -
509 - $htmlOut .= Xml::closeElement( 'fieldset' );
510 -
511 - if ( $this->editable ) {
512 - $htmlOut .= Xml::closeElement( 'form' );
513 - }
514 -
515 - // Show language selection form
516 - $htmlOut .= Xml::openElement( 'form', array( 'method' => 'post' ) );
517 - $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-change-lang' ) );
518 - $htmlOut .= Xml::openElement( 'table', array ( 'cellpadding' => 9 ) );
519 - list( $lsLabel, $lsSelect ) = Xml::languageSelector( $wpUserLang );
520 -
521 - $newPage = $this->getTitle( 'view' );
522 -
523 - $htmlOut .= Xml::tags( 'tr', null,
524 - Xml::tags( 'td', null, $lsLabel ) .
525 - Xml::tags( 'td', null, $lsSelect ) .
526 - Xml::tags( 'td', array( 'colspan' => 2 ),
527 - Xml::submitButton( wfMsg( 'centralnotice-modify' ) )
528 - )
529 - );
530 - $htmlOut .= Xml::tags( 'tr', null,
531 - Xml::tags( 'td', null, '' ) .
532 - Xml::tags( 'td', null,
533 - $sk->makeLinkObj(
534 - $newPage,
535 - wfMsgHtml( 'centralnotice-preview-all-template-translations' ),
536 - "template=$currentTemplate&wpUserLanguage=all" )
537 - )
538 - );
539 - $htmlOut .= Xml::closeElement( 'table' );
540 - $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
541 - $htmlOut .= Xml::closeElement( 'fieldset' );
542 - $htmlOut .= Xml::closeElement( 'form' );
543 - }
544 -
545 - // Show edit form
546 - if ( $this->editable ) {
547 - $htmlOut .= Xml::openElement( 'form',
548 - array(
549 - 'method' => 'post',
550 - 'onsubmit' => 'return validateBannerForm(this)'
551 - )
552 - );
553 - $htmlOut .= Html::hidden( 'wpMethod', 'editTemplate' );
554 - }
555 -
556 - // If there was an error, we'll need to restore the state of the form
557 - if ( $wgRequest->wasPosted() && $wgRequest->getVal( 'mainform' ) ) {
558 - $displayAnon = $wgRequest->getCheck( 'displayAnon' );
559 - $displayAccount = $wgRequest->getCheck( 'displayAccount' );
560 - $fundraising = $wgRequest->getCheck( 'fundraising' );
561 - $landingPages = $wgRequest->getVal( 'landingPages' );
562 - $body = $wgRequest->getVal( 'templateBody', $body );
563 - } else { // Use previously stored values
564 - $displayAnon = ( $row->tmp_display_anon == 1 );
565 - $displayAccount = ( $row->tmp_display_account == 1 );
566 - $fundraising = ( $row->tmp_fundraising == 1 );
567 - $landingPages = $row->tmp_landing_pages;
568 - // $body default is defined prior to message interface code
569 - }
570 -
571 - // Show banner settings
572 - $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-settings' ) );
573 - $htmlOut .= Xml::openElement( 'p', null );
574 - $htmlOut .= wfMsg( 'centralnotice-banner-display' );
575 - $htmlOut .= Xml::check( 'displayAnon', $displayAnon,
576 - wfArrayMerge( $disabled, array( 'id' => 'displayAnon' ) ) );
577 - $htmlOut .= Xml::label( wfMsg( 'centralnotice-banner-anonymous' ), 'displayAnon' );
578 - $htmlOut .= Xml::check( 'displayAccount', $displayAccount,
579 - wfArrayMerge( $disabled, array( 'id' => 'displayAccount' ) ) );
580 - $htmlOut .= Xml::label( wfMsg( 'centralnotice-banner-logged-in' ), 'displayAccount' );
581 - $htmlOut .= Xml::closeElement( 'p' );
582 -
583 - // Fundraising settings
584 - if ( $wgNoticeEnableFundraising ) {
585 - $htmlOut .= Xml::openElement( 'p', null );
586 - $htmlOut .= Xml::check( 'fundraising', $fundraising,
587 - wfArrayMerge( $disabled, array( 'id' => 'fundraising' ) ) );
588 - $htmlOut .= Xml::label( wfMsg( 'centralnotice-banner-fundraising' ),
589 - 'fundraising' );
590 - $htmlOut .= Xml::closeElement( 'p' );
591 - if ( $fundraising ) {
592 - $htmlOut .= Xml::openElement( 'div', array( 'id'=>'fundraisingInterface' ) );
593 - } else {
594 - $htmlOut .= Xml::openElement( 'div',
595 - array( 'id'=>'fundraisingInterface', 'style'=>'display:none;' ) );
596 - }
597 - $htmlOut .= Xml::tags( 'p', array(),
598 - wfMsg( 'centralnotice-banner-fundraising-help' ) );
599 - $htmlOut .= Xml::tags( 'p', array(),
600 - Xml::inputLabel(
601 - wfMsg( 'centralnotice-banner-landing-pages' ),
602 - 'landingPages', 'landingPages', 40, $landingPages,
603 - array( 'maxlength' => 255 )
604 - )
605 - );
606 - $htmlOut .= Xml::closeElement( 'div' );
607 - }
608 -
609 - // Begin banner body section
610 - $htmlOut .= Xml::closeElement( 'fieldset' );
611 - if ( $this->editable ) {
612 - $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-edit-template' ) );
613 - $htmlOut .= wfMsg( 'centralnotice-edit-template-summary' );
614 - $buttons = array();
615 - $buttons[] = '<a href="#" onclick="insertButton(\'close\');return false;">' .
616 - wfMsg( 'centralnotice-close-button' ) . '</a>';
617 - $htmlOut .= Xml::tags( 'div',
618 - array( 'style' => 'margin-bottom: 0.2em;' ),
619 - '<img src="' . $scriptPath . '/down-arrow.png" ' .
620 - 'style="vertical-align:baseline;"/>' .
621 - wfMsg( 'centralnotice-insert', $wgLang->commaList( $buttons ) )
622 - );
623 - } else {
624 - $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-banner' ) );
625 - }
626 - $htmlOut .= Xml::textarea( 'templateBody', $body, 60, 20, $readonly );
627 - $htmlOut .= Xml::closeElement( 'fieldset' );
628 - if ( $this->editable ) {
629 - // Indicate which form was submitted
630 - $htmlOut .= Html::hidden( 'mainform', 'true' );
631 - $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
632 - $htmlOut .= Xml::tags( 'div',
633 - array( 'class' => 'cn-buttons' ),
634 - Xml::submitButton( wfMsg( 'centralnotice-save-banner' ) )
635 - );
636 - $htmlOut .= Xml::closeElement( 'form' );
637 - }
638 -
639 - // Show clone form
640 - if ( $this->editable ) {
641 - $htmlOut .= Xml::openElement ( 'form',
642 - array(
643 - 'method' => 'post',
644 - 'action' => $this->getTitle( 'clone' )->getLocalUrl()
645 - )
646 - );
647 -
648 - $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-clone-notice' ) );
649 - $htmlOut .= Xml::openElement( 'table', array( 'cellpadding' => 9 ) );
650 - $htmlOut .= Xml::openElement( 'tr' );
651 - $htmlOut .= Xml::inputLabel(
652 - wfMsg( 'centralnotice-clone-name' ),
653 - 'newTemplate', 'newTemplate', '25' );
654 - $htmlOut .= Xml::submitButton(
655 - wfMsg( 'centralnotice-clone' ),
656 - array ( 'id' => 'clone' ) );
657 - $htmlOut .= Html::hidden( 'oldTemplate', $currentTemplate );
658 -
659 - $htmlOut .= Xml::closeElement( 'tr' );
660 - $htmlOut .= Xml::closeElement( 'table' );
661 - $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
662 - $htmlOut .= Xml::closeElement( 'fieldset' );
663 - $htmlOut .= Xml::closeElement( 'form' );
664 - }
665 -
666 - // End View Banner fieldset
667 - $htmlOut .= Xml::closeElement( 'fieldset' );
668 -
669 - // Output HTML
670 - $wgOut->addHTML( $htmlOut );
671 - }
672 - }
673 -
674 - /**
675 - * Preview all available translations of a banner
676 - */
677 - public function showViewAvailable( $template ) {
678 - global $wgOut, $wgUser;
679 -
680 - // Testing to see if bumping up the memory limit lets meta preview
681 - ini_set( 'memory_limit', '120M' );
682 -
683 - $sk = $wgUser->getSkin();
684 -
685 - // Pull all available text for a banner
686 - $langs = array_keys( $this->getTranslations( $template ) );
687 - $htmlOut = '';
688 -
689 - // Begin View Banner fieldset
690 - $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
691 -
692 - $htmlOut .= Xml::element( 'h2', null, wfMsg( 'centralnotice-banner-heading', $template ) );
693 -
694 - foreach ( $langs as $lang ) {
695 - // Link and Preview all available translations
696 - $viewPage = $this->getTitle( 'view' );
697 - $render = new SpecialBannerLoader();
698 - $render->siteName = 'Wikipedia';
699 - $render->language = $lang;
700 - try {
701 - $preview = $render->getHtmlNotice( $template );
702 - } catch ( SpecialBannerLoaderException $e ) {
703 - $preview = wfMsg( 'centralnotice-nopreview' );
704 - }
705 - $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
706 - $sk->makeLinkObj( $viewPage,
707 - $lang,
708 - 'template=' . urlencode( $template ) . "&wpUserLanguage=$lang" ) .
709 - Xml::fieldset( wfMsg( 'centralnotice-preview' ),
710 - $preview,
711 - array( 'class' => 'cn-bannerpreview')
712 - )
713 - );
714 - }
715 -
716 - // End View Banner fieldset
717 - $htmlOut .= Xml::closeElement( 'fieldset' );
718 -
719 - return $wgOut->addHtml( $htmlOut );
720 - }
721 -
722 - /**
723 - * Add or update a message
724 - */
725 - private function updateMessage( $text, $translation, $lang ) {
726 - $title = Title::newFromText(
727 - ( $lang == 'en' ) ? "Centralnotice-{$text}" : "Centralnotice-{$text}/{$lang}",
728 - NS_MEDIAWIKI
729 - );
730 - $article = new Article( $title );
731 - $article->doEdit( $translation, '', EDIT_FORCE_BOT );
732 - }
733 -
734 - private function getTemplateId ( $templateName ) {
735 - $dbr = wfGetDB( DB_SLAVE );
736 - $res = $dbr->select( 'cn_templates', 'tmp_id',
737 - array( 'tmp_name' => $templateName ),
738 - __METHOD__
739 - );
740 -
741 - $row = $dbr->fetchObject( $res );
742 - if ( $row ) {
743 - return $row->tmp_id;
744 - }
745 - return null;
746 - }
747 -
748 - private function removeTemplate ( $name ) {
749 - $id = $this->getTemplateId( $name );
750 - $dbr = wfGetDB( DB_SLAVE );
751 - $res = $dbr->select( 'cn_assignments', 'asn_id', array( 'tmp_id' => $id ), __METHOD__ );
752 -
753 - if ( $dbr->numRows( $res ) > 0 ) {
754 - $this->showError( 'centralnotice-template-still-bound' );
755 - return;
756 - } else {
757 - $dbw = wfGetDB( DB_MASTER );
758 - $dbw->begin();
759 - $dbw->delete( 'cn_templates',
760 - array( 'tmp_id' => $id ),
761 - __METHOD__
762 - );
763 - $dbw->commit();
764 -
765 - $article = new Article(
766 - Title::newFromText( "centralnotice-template-{$name}", NS_MEDIAWIKI )
767 - );
768 - $article->doDeleteArticle( 'CentralNotice Automated Removal' );
769 - }
770 - }
771 -
772 - /**
773 - * Create a new banner
774 - */
775 - private function addTemplate( $name, $body, $displayAnon, $displayAccount, $fundraising,
776 - $landingPages ) {
777 -
778 - if ( $body == '' || $name == '' ) {
779 - $this->showError( 'centralnotice-null-string' );
780 - return;
781 - }
782 -
783 - // Format name so there are only letters, numbers, and underscores
784 - $name = preg_replace( '/[^A-Za-z0-9_]/', '', $name );
785 -
786 - $dbr = wfGetDB( DB_SLAVE );
787 - $res = $dbr->select(
788 - 'cn_templates',
789 - 'tmp_name',
790 - array( 'tmp_name' => $name ),
791 - __METHOD__
792 - );
793 -
794 - if ( $dbr->numRows( $res ) > 0 ) {
795 - $this->showError( 'centralnotice-template-exists' );
796 - return false;
797 - } else {
798 - $dbw = wfGetDB( DB_MASTER );
799 - $res = $dbw->insert( 'cn_templates',
800 - array(
801 - 'tmp_name' => $name,
802 - 'tmp_display_anon' => $displayAnon,
803 - 'tmp_display_account' => $displayAccount,
804 - 'tmp_fundraising' => $fundraising,
805 - 'tmp_landing_pages' => $landingPages
806 - ),
807 - __METHOD__
808 - );
809 -
810 - // Perhaps these should move into the db as blobs instead of being stored as articles
811 - $article = new Article(
812 - Title::newFromText( "centralnotice-template-{$name}", NS_MEDIAWIKI )
813 - );
814 - $article->doEdit( $body, '', EDIT_FORCE_BOT );
815 - return true;
816 - }
817 - }
818 -
819 - /**
820 - * Update a banner
821 - */
822 - private function editTemplate( $name, $body, $displayAnon, $displayAccount, $fundraising,
823 - $landingPages ) {
824 -
825 - if ( $body == '' || $name == '' ) {
826 - $this->showError( 'centralnotice-null-string' );
827 - return;
828 - }
829 -
830 - $dbr = wfGetDB( DB_SLAVE );
831 - $res = $dbr->select( 'cn_templates', 'tmp_name',
832 - array( 'tmp_name' => $name ),
833 - __METHOD__
834 - );
835 -
836 - if ( $dbr->numRows( $res ) == 1 ) {
837 - $dbw = wfGetDB( DB_MASTER );
838 - $res = $dbw->update( 'cn_templates',
839 - array(
840 - 'tmp_display_anon' => $displayAnon,
841 - 'tmp_display_account' => $displayAccount,
842 - 'tmp_fundraising' => $fundraising,
843 - 'tmp_landing_pages' => $landingPages
844 - ),
845 - array( 'tmp_name' => $name )
846 - );
847 -
848 - // Perhaps these should move into the db as blob
849 - $article = new Article(
850 - Title::newFromText( "centralnotice-template-{$name}", NS_MEDIAWIKI )
851 - );
852 - $article->doEdit( $body, '', EDIT_FORCE_BOT );
853 - return;
854 - }
855 - }
856 -
857 - /**
858 - * Copy all the data from one banner to another
859 - */
860 - public function cloneTemplate( $source, $dest ) {
861 -
862 - // Reset the timer as updates on meta take a long time
863 - set_time_limit( 300 );
864 -
865 - // Pull all possible langs
866 - $langs = $this->getTranslations( $source );
867 -
868 - // Normalize name
869 - $dest = preg_replace( '/[^A-Za-z0-9_]/', '', $dest );
870 -
871 - // Pull banner settings from database
872 - $dbr = wfGetDB( DB_SLAVE );
873 - $row = $dbr->selectRow( 'cn_templates',
874 - array(
875 - 'tmp_display_anon',
876 - 'tmp_display_account',
877 - 'tmp_fundraising',
878 - 'tmp_landing_pages'
879 - ),
880 - array( 'tmp_name' => $source ),
881 - __METHOD__
882 - );
883 - $displayAnon = $row->tmp_display_anon;
884 - $displayAccount = $row->tmp_display_account;
885 - $fundraising = $row->tmp_fundraising;
886 - $landingPages = $row->tmp_landing_pages;
887 -
888 - // Pull banner text and respect any inc: markup
889 - $bodyPage = Title::newFromText( "Centralnotice-template-{$source}", NS_MEDIAWIKI );
890 - $template_body = Revision::newFromTitle( $bodyPage )->getText();
891 -
892 - // Create new banner
893 - if ( $this->addTemplate( $dest, $template_body, $displayAnon, $displayAccount, $fundraising,
894 - $landingPages ) ) {
895 -
896 - // Populate the fields
897 - foreach ( $langs as $lang => $fields ) {
898 - foreach ( $fields as $field => $text ) {
899 - $this->updateMessage( "$dest-$field", $text, $lang );
900 - }
901 - }
902 - return $dest;
903 - }
904 - }
905 -
906 - /**
907 - * Find all message fields set for a banner
908 - */
909 - private function findFields( $template ) {
910 - $body = wfMsg( "Centralnotice-template-{$template}" );
911 -
912 - // Generate list of message fields from parsing the body
913 - $fields = array();
914 - $allowedChars = Title::legalChars();
915 - preg_match_all( "/\{\{\{([$allowedChars]+)\}\}\}/u", $body, $fields );
916 -
917 - // Remove duplicates
918 - $filteredFields = array();
919 - foreach ( $fields[1] as $field ) {
920 - $filteredFields[$field] = array_key_exists( $field, $filteredFields )
921 - ? $filteredFields[$field] + 1
922 - : 1;
923 - }
924 - return $filteredFields;
925 - }
926 -
927 - /**
928 - * Get all the translations of all the messages for a banner
929 - * @return a 2D array of every set message in every language for one banner
930 - */
931 - public function getTranslations( $template ) {
932 - $translations = array();
933 -
934 - // Pull all language codes to enumerate
935 - $allLangs = array_keys( Language::getLanguageNames() );
936 -
937 - // Lookup all the message fields for a banner
938 - $fields = $this->findFields( $template );
939 -
940 - // Iterate through all possible languages to find matches
941 - foreach ( $allLangs as $lang ) {
942 - // Iterate through all possible message fields
943 - foreach ( $fields as $field => $count ) {
944 - // Put all message fields together for a lookup
945 - $message = ( $lang == 'en' )
946 - ? "Centralnotice-{$template}-{$field}"
947 - : "Centralnotice-{$template}-{$field}/{$lang}";
948 - if ( Title::newFromText( $message, NS_MEDIAWIKI )->exists() ) {
949 - $translations[$lang][$field] = wfMsgExt(
950 - "Centralnotice-{$template}-{$field}",
951 - array( 'language' => $lang )
952 - );
953 - }
954 - }
955 - }
956 - return $translations;
957 - }
958 -
959 - function showError( $message ) {
960 - global $wgOut;
961 - $wgOut->wrapWikiMsg( "<div class='cn-error'>\n$1\n</div>", $message );
962 - $this->centralNoticeError = true;
963 - }
964 -
965 -}
Index: trunk/extensions/CentralNotice/SpecialCentralNoticeLogs.php
@@ -1,88 +0,0 @@
2 -<?php
3 -
4 -if ( !defined( 'MEDIAWIKI' ) ) {
5 - echo "CentralNotice extension\n";
6 - exit( 1 );
7 -}
8 -
9 -class SpecialCentralNoticeLogs extends UnlistedSpecialPage {
10 - public $logType = 'campaignsettings';
11 -
12 - function __construct() {
13 - // Register special page
14 - parent::__construct( "CentralNoticeLogs" );
15 - }
16 -
17 - /**
18 - * Handle different types of page requests
19 - */
20 - function execute( $sub ) {
21 - global $wgOut, $wgRequest, $wgExtensionAssetsPath;
22 -
23 - if ( $wgRequest->wasPosted() ) {
24 - $this->logType = $wgRequest->getText( 'log', 'campaignsettings' );
25 - }
26 -
27 - // Begin output
28 - $this->setHeaders();
29 -
30 - // Add style file to the output headers
31 - $wgOut->addExtensionStyle( "$wgExtensionAssetsPath/CentralNotice/centralnotice.css" );
32 -
33 - // Add script file to the output headers
34 - $wgOut->addScriptFile( "$wgExtensionAssetsPath/CentralNotice/centralnotice.js" );
35 -
36 - // Initialize error variable
37 - $this->centralNoticeError = false;
38 -
39 - // Show summary
40 - $wgOut->addWikiMsg( 'centralnotice-summary' );
41 -
42 - // Show header
43 - CentralNotice::printHeader();
44 -
45 - // Begin Banners tab content
46 - $wgOut->addHTML( Xml::openElement( 'div', array( 'id' => 'preferences' ) ) );
47 -
48 - $htmlOut = '';
49 -
50 - // Begin log selection fieldset
51 - $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
52 -
53 - $htmlOut .= Xml::openElement( 'form', array( 'method' => 'post' ) );
54 - $htmlOut .= Xml::element( 'h2', null, wfMsg( 'centralnotice-view-logs' ) );
55 - $htmlOut .= Xml::closeElement( 'form' );
56 -
57 - // End log selection fieldset
58 - $htmlOut .= Xml::closeElement( 'fieldset' );
59 -
60 - $wgOut->addHTML( $htmlOut );
61 -
62 - $this->showLog( $this->logType );
63 -
64 - // End Banners tab content
65 - $wgOut->addHTML( Xml::closeElement( 'div' ) );
66 - }
67 -
68 - /**
69 - * Show a log.
70 - */
71 - function showLog( $logType ) {
72 - global $wgOut;
73 -
74 - $htmlOut = '';
75 -
76 - // Begin log fieldset
77 - $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
78 -
79 - $htmlOut .= Xml::tags( 'p', null,
80 - '<i>Coming soon...</i>'
81 - );
82 -
83 - // End log fieldset
84 - $htmlOut .= Xml::closeElement( 'fieldset' );
85 -
86 - $wgOut->addHTML( $htmlOut );
87 - }
88 -
89 -}
Index: trunk/extensions/CentralNotice/SpecialHideBanners.php
@@ -1,42 +0,0 @@
2 -<?php
3 -if ( !defined( 'MEDIAWIKI' ) ) {
4 - echo "CentralNotice extension\n";
5 - exit( 1 );
6 -}
7 -
8 -/**
9 - * Unlisted Special Page which sets a cookie for hiding banners across all languages of a project.
10 - * This is typically used on donation thank-you pages so that users who have donated will no longer
11 - * see fundrasing banners.
12 - */
13 -class SpecialHideBanners extends UnlistedSpecialPage {
14 - function __construct() {
15 - parent::__construct( 'HideBanners' );
16 - }
17 -
18 - function execute( $par ) {
19 - global $wgOut;
20 -
21 - $this->setHideCookie();
22 -
23 - $wgOut->disable();
24 - wfResetOutputBuffers();
25 -
26 - header( 'Content-Type: image/png' );
27 - header( 'Cache-Control: no-cache' );
28 -
29 - readfile( dirname( __FILE__ ) . '/1x1.png' );
30 - }
31 -
32 - function setHideCookie() {
33 - global $wgNoticeCookieDomain, $wgCookieSecure;
34 - $exp = time() + 86400 * 14; // Cookie expires after 2 weeks
35 - if ( is_callable( array( 'CentralAuthUser', 'getCookieDomain' ) ) ) {
36 - $cookieDomain = CentralAuthUser::getCookieDomain();
37 - } else {
38 - $cookieDomain = $wgNoticeCookieDomain;
39 - }
40 - // Hide banners for this domain
41 - setcookie( 'hidesnmessage', '1', $exp, '/', $cookieDomain, $wgCookieSecure );
42 - }
43 -}
Index: trunk/extensions/CentralNotice/CentralNotice.php
@@ -94,19 +94,21 @@
9595 $wgHooks['SkinAfterBottomScripts'][] = 'efCentralNoticeGeoLoader';
9696 }
9797
 98+ $specialDir = $dir . 'special/';
 99+
98100 $wgSpecialPages['BannerLoader'] = 'SpecialBannerLoader';
99 - $wgAutoloadClasses['SpecialBannerLoader'] = $dir . 'SpecialBannerLoader.php';
 101+ $wgAutoloadClasses['SpecialBannerLoader'] = $specialDir . 'SpecialBannerLoader.php';
100102
101103 $wgSpecialPages['BannerListLoader'] = 'SpecialBannerListLoader';
102 - $wgAutoloadClasses['SpecialBannerListLoader'] = $dir . 'SpecialBannerListLoader.php';
 104+ $wgAutoloadClasses['SpecialBannerListLoader'] = $specialDir . 'SpecialBannerListLoader.php';
103105
104106 $wgSpecialPages['BannerController'] = 'SpecialBannerController';
105 - $wgAutoloadClasses['SpecialBannerController'] = $dir . 'SpecialBannerController.php';
 107+ $wgAutoloadClasses['SpecialBannerController'] = $specialDir . 'SpecialBannerController.php';
106108
107109 $wgSpecialPages['HideBanners'] = 'SpecialHideBanners';
108 - $wgAutoloadClasses['SpecialHideBanners'] = $dir . 'SpecialHideBanners.php';
 110+ $wgAutoloadClasses['SpecialHideBanners'] = $specialDir . 'SpecialHideBanners.php';
109111
110 - $wgAutoloadClasses['CentralNotice'] = $dir . 'SpecialCentralNotice.php';
 112+ $wgAutoloadClasses['CentralNotice'] = $specialDir . 'SpecialCentralNotice.php';
111113 $wgAutoloadClasses['CentralNoticeDB'] = $dir . 'CentralNotice.db.php';
112114 $wgAutoloadClasses['TemplatePager'] = $dir . 'TemplatePager.php';
113115
@@ -115,13 +117,13 @@
116118 $wgSpecialPageGroups['CentralNotice'] = 'wiki'; // Wiki data and tools"
117119
118120 $wgSpecialPages['NoticeTemplate'] = 'SpecialNoticeTemplate';
119 - $wgAutoloadClasses['SpecialNoticeTemplate'] = $dir . 'SpecialNoticeTemplate.php';
 121+ $wgAutoloadClasses['SpecialNoticeTemplate'] = $specialDir . 'SpecialNoticeTemplate.php';
120122
121123 $wgSpecialPages['BannerAllocation'] = 'SpecialBannerAllocation';
122 - $wgAutoloadClasses['SpecialBannerAllocation'] = $dir . 'SpecialBannerAllocation.php';
123 -
 124+ $wgAutoloadClasses['SpecialBannerAllocation'] = $specialDir . 'SpecialBannerAllocation.php';
 125+
124126 $wgSpecialPages['CentralNoticeLogs'] = 'SpecialCentralNoticeLogs';
125 - $wgAutoloadClasses['SpecialCentralNoticeLogs'] = $dir . 'SpecialCentralNoticeLogs.php';
 127+ $wgAutoloadClasses['SpecialCentralNoticeLogs'] = $specialDir . 'SpecialCentralNoticeLogs.php';
126128 }
127129 }
128130
@@ -131,36 +133,36 @@
132134 global $wgDBtype, $wgExtNewTables, $wgExtNewFields;
133135
134136 if ( $wgDBtype == 'mysql' ) {
135 - $wgExtNewTables[] = array( 'cn_notices',
 137+ $wgExtNewTables[] = array( 'cn_notices',
136138 $base . '/CentralNotice.sql' );
137139 $wgExtNewFields[] = array( 'cn_notices', 'not_preferred',
138140 $base . '/patches/patch-notice_preferred.sql' );
139 - $wgExtNewTables[] = array( 'cn_notice_languages',
 141+ $wgExtNewTables[] = array( 'cn_notice_languages',
140142 $base . '/patches/patch-notice_languages.sql' );
141 - $wgExtNewFields[] = array( 'cn_templates', 'tmp_display_anon',
 143+ $wgExtNewFields[] = array( 'cn_templates', 'tmp_display_anon',
142144 $base . '/patches/patch-template_settings.sql' );
143145 $wgExtNewFields[] = array( 'cn_templates', 'tmp_fundraising',
144146 $base . '/patches/patch-template_fundraising.sql' );
145 - $wgExtNewTables[] = array( 'cn_notice_countries',
 147+ $wgExtNewTables[] = array( 'cn_notice_countries',
146148 $base . '/patches/patch-notice_countries.sql' );
147 - $wgExtNewTables[] = array( 'cn_notice_projects',
 149+ $wgExtNewTables[] = array( 'cn_notice_projects',
148150 $base . '/patches/patch-notice_projects.sql' );
149151 }
150152 } else {
151153 if ( $updater->getDB()->getType() == 'mysql' ) {
152 - $updater->addExtensionUpdate( array( 'addTable', 'cn_notices',
 154+ $updater->addExtensionUpdate( array( 'addTable', 'cn_notices',
153155 $base . '/CentralNotice.sql', true ) );
154 - $updater->addExtensionUpdate( array( 'addField', 'cn_notices', 'not_preferred',
 156+ $updater->addExtensionUpdate( array( 'addField', 'cn_notices', 'not_preferred',
155157 $base . '/patches/patch-notice_preferred.sql', true ) );
156 - $updater->addExtensionUpdate( array( 'addTable', 'cn_notice_languages',
 158+ $updater->addExtensionUpdate( array( 'addTable', 'cn_notice_languages',
157159 $base . '/patches/patch-notice_languages.sql', true ) );
158 - $updater->addExtensionUpdate( array( 'addField', 'cn_templates', 'tmp_display_anon',
 160+ $updater->addExtensionUpdate( array( 'addField', 'cn_templates', 'tmp_display_anon',
159161 $base . '/patches/patch-template_settings.sql', true ) );
160 - $updater->addExtensionUpdate( array( 'addField', 'cn_templates', 'tmp_fundraising',
 162+ $updater->addExtensionUpdate( array( 'addField', 'cn_templates', 'tmp_fundraising',
161163 $base . '/patches/patch-template_fundraising.sql', true ) );
162 - $updater->addExtensionUpdate( array( 'addTable', 'cn_notice_countries',
 164+ $updater->addExtensionUpdate( array( 'addTable', 'cn_notice_countries',
163165 $base . '/patches/patch-notice_countries.sql', true ) );
164 - $updater->addExtensionUpdate( array( 'addTable', 'cn_notice_projects',
 166+ $updater->addExtensionUpdate( array( 'addTable', 'cn_notice_projects',
165167 $base . '/patches/patch-notice_projects.sql', true ) );
166168 }
167169 }
@@ -172,7 +174,7 @@
173175
174176 // Include '.js' to exempt script from squid cache override
175177 $centralLoader = SpecialPage::getTitleFor( 'BannerController' )->getLocalUrl( 'cache=/cn.js' );
176 -
 178+
177179 // Insert the banner controller Javascript into the <head>
178180 $wgOut->addScriptFile( $centralLoader );
179181
Index: trunk/extensions/CentralNotice/special/SpecialBannerListLoader.php
@@ -0,0 +1,79 @@
 2+<?php
 3+
 4+/**
 5+ * Generates JSON files listing all the banners for a particular site
 6+ */
 7+class SpecialBannerListLoader extends UnlistedSpecialPage {
 8+ public $project; // Project name
 9+ public $language; // Project language
 10+ public $location; // User country
 11+ protected $sharedMaxAge = 300; // Cache for 5 minutes on the server side
 12+ protected $maxAge = 300; // Cache for 5 minutes on the client side
 13+
 14+ function __construct() {
 15+ // Register special page
 16+ parent::__construct( "BannerListLoader" );
 17+ }
 18+
 19+ function execute( $par ) {
 20+ global $wgOut, $wgRequest;
 21+
 22+ $wgOut->disable();
 23+ $this->sendHeaders();
 24+
 25+ // Get project language from the query string
 26+ $this->language = $wgRequest->getText( 'language', 'en' );
 27+
 28+ // Get project name from the query string
 29+ $this->project = $wgRequest->getText( 'project', 'wikipedia' );
 30+
 31+ // Get location from the query string
 32+ $this->location = $wgRequest->getText( 'country' );
 33+
 34+ if ( $this->project && $this->language ) {
 35+ $content = $this->getJsonList();
 36+ if ( strlen( $content ) == 0 ) {
 37+ // Hack for IE/Mac 0-length keepalive problem, see RawPage.php
 38+ echo "/* Empty */";
 39+ } else {
 40+ echo $content;
 41+ }
 42+ } else {
 43+ echo "/* No site specified */";
 44+ }
 45+ }
 46+
 47+ /**
 48+ * Generate the HTTP response headers for the banner file
 49+ */
 50+ function sendHeaders() {
 51+ global $wgJsMimeType;
 52+ header( "Content-type: $wgJsMimeType; charset=utf-8" );
 53+ header( "Cache-Control: public, s-maxage=$this->sharedMaxAge, max-age=$this->maxAge" );
 54+ }
 55+
 56+ /**
 57+ * Generate JSON for the specified site
 58+ */
 59+ function getJsonList() {
 60+ $banners = array();
 61+
 62+ // See if we have any preferred campaigns for this language and project
 63+ $campaigns = CentralNoticeDB::getCampaigns( $this->project, $this->language, null, 1, 1, $this->location );
 64+
 65+ // Quick short circuit to show preferred campaigns
 66+ if ( $campaigns ) {
 67+ // Pull banners
 68+ $banners = CentralNoticeDB::selectBannersAssigned( $campaigns );
 69+ }
 70+
 71+ // Didn't find any preferred banners so do an old style lookup
 72+ if ( !$banners ) {
 73+ $banners = CentralNotice::selectNoticeTemplates(
 74+ $this->project, $this->language, $this->location );
 75+ }
 76+
 77+ return FormatJson::encode( $banners );
 78+ }
 79+
 80+}
Property changes on: trunk/extensions/CentralNotice/special/SpecialBannerListLoader.php
___________________________________________________________________
Added: svn:eol-style
181 + native
Index: trunk/extensions/CentralNotice/special/SpecialBannerController.php
@@ -0,0 +1,210 @@
 2+<?php
 3+
 4+/**
 5+ * Generates Javascript file which controls banner selection on the client side
 6+ */
 7+class SpecialBannerController extends UnlistedSpecialPage {
 8+ protected $sharedMaxAge = 3600; // Cache for 1 hour on the server side
 9+ protected $maxAge = 3600; // Cache for 1 hour on the client side
 10+
 11+ function __construct() {
 12+ // Register special page
 13+ parent::__construct( "BannerController" );
 14+ }
 15+
 16+ function execute( $par ) {
 17+ global $wgOut;
 18+
 19+ $wgOut->disable();
 20+ $this->sendHeaders();
 21+
 22+ $content = $this->getOutput();
 23+ if ( strlen( $content ) == 0 ) {
 24+ // Hack for IE/Mac 0-length keepalive problem, see RawPage.php
 25+ echo "/* Empty */";
 26+ } else {
 27+ echo $content;
 28+ }
 29+ }
 30+
 31+ /**
 32+ * Generate the HTTP response headers for the banner controller
 33+ */
 34+ function sendHeaders() {
 35+ global $wgJsMimeType;
 36+ header( "Content-type: $wgJsMimeType; charset=utf-8" );
 37+ header( "Cache-Control: public, s-maxage=$this->sharedMaxAge, max-age=$this->maxAge" );
 38+ }
 39+
 40+ /**
 41+ * Generate the body for the Javascript file
 42+ *
 43+ * We use a jsonp scheme for actual delivery of the banner so that they can be served from meta.
 44+ * In order to circumvent the normal squid cache override we add '/cn.js' to the bannerlist URL.
 45+ */
 46+ function getOutput() {
 47+ global $wgCentralPagePath, $wgNoticeFundraisingUrl, $wgContLang;
 48+
 49+ $js = $this->getScriptFunctions() . $this->getToggleScripts();
 50+ $js .= <<<JAVASCRIPT
 51+( function( $ ) {
 52+ $.ajaxSetup({ cache: true });
 53+ $.centralNotice = {
 54+ 'data': {
 55+ 'getVars': {}
 56+ },
 57+ 'fn': {
 58+ 'loadBanner': function( bannerName, fundraising, landingPages, campaign ) {
 59+ // Get the requested banner
 60+ var bannerPageQuery = $.param( {
 61+ 'banner': bannerName, 'campaign': campaign, 'userlang': wgUserLanguage,
 62+ 'db': wgDBname, 'sitename': wgSiteName, 'country': Geo.country,
 63+ 'fundraising': fundraising, 'landingpages': landingPages
 64+ } );
 65+ var bannerPage = '?title=Special:BannerLoader&' + bannerPageQuery;
 66+JAVASCRIPT;
 67+ $js .= "\n\t\t\t\tvar bannerScript = '<script type=\"text/javascript\" src=\"" .
 68+ Xml::escapeJsString( $wgCentralPagePath ) .
 69+ "' + bannerPage + '\"></script>';\n";
 70+ $js .= <<<JAVASCRIPT
 71+ $( '#siteNotice' ).prepend( '<div id="centralNotice" class="' +
 72+ ( wgNoticeToggleState ? 'expanded' : 'collapsed' ) +
 73+ '">'+bannerScript+'</div>' );
 74+ },
 75+ 'loadBannerList': function( geoOverride ) {
 76+ if ( geoOverride ) {
 77+ var geoLocation = geoOverride; // override the geo info
 78+ } else {
 79+ var geoLocation = Geo.country; // pull the geo info
 80+ }
 81+ var bannerListQuery = $.param( { 'language': wgContentLanguage, 'project': wgNoticeProject, 'country': geoLocation } );
 82+JAVASCRIPT;
 83+ $js .= "\n\t\t\t\tvar bannerListURL = wgScript + '?title=' + encodeURIComponent('" .
 84+ $wgContLang->specialPage( 'BannerListLoader' ) .
 85+ "') + '&cache=/cn.js&' + bannerListQuery;\n";
 86+ $js .= <<<JAVASCRIPT
 87+ var request = $.ajax( {
 88+ url: bannerListURL,
 89+ dataType: 'json',
 90+ success: $.centralNotice.fn.chooseBanner
 91+ } );
 92+ },
 93+ 'chooseBanner': function( bannerList ) {
 94+ // Convert the json object to a true array
 95+ bannerList = Array.prototype.slice.call( bannerList );
 96+
 97+ // Make sure there are some banners to choose from
 98+ if ( bannerList.length == 0 ) return false;
 99+
 100+ var groomedBannerList = [];
 101+
 102+ for( var i = 0; i < bannerList.length; i++ ) {
 103+ // Only include this banner if it's intended for the current user
 104+ if( ( wgUserName && bannerList[i].display_account ) ||
 105+ ( !wgUserName && bannerList[i].display_anon == 1 ) )
 106+ {
 107+ // add the banner to our list once per weight
 108+ for( var j=0; j < bannerList[i].weight; j++ ) {
 109+ groomedBannerList.push( bannerList[i] );
 110+ }
 111+ }
 112+ }
 113+
 114+ // Return if there's nothing left after the grooming
 115+ if( groomedBannerList.length == 0 ) return false;
 116+
 117+ // Choose a random key
 118+ var pointer = Math.floor( Math.random() * groomedBannerList.length );
 119+
 120+ // Load a random banner from our groomed list
 121+ $.centralNotice.fn.loadBanner(
 122+ groomedBannerList[pointer].name,
 123+ groomedBannerList[pointer].fundraising,
 124+ groomedBannerList[pointer].landing_pages,
 125+ groomedBannerList[pointer].campaign
 126+ );
 127+ },
 128+ 'getQueryStringVariables': function() {
 129+ document.location.search.replace( /\??(?:([^=]+)=([^&]*)&?)/g, function () {
 130+ function decode( s ) {
 131+ return decodeURIComponent( s.split( "+" ).join( " " ) );
 132+ }
 133+ $.centralNotice.data.getVars[decode( arguments[1] )] = decode( arguments[2] );
 134+ } );
 135+ }
 136+ }
 137+ }
 138+ $( document ).ready( function () {
 139+ // Initialize the query string vars
 140+ $.centralNotice.fn.getQueryStringVariables();
 141+ if( $.centralNotice.data.getVars['banner'] ) {
 142+ // if we're forcing one banner
 143+ $.centralNotice.fn.loadBanner( $.centralNotice.data.getVars['banner'] );
 144+ } else {
 145+ // Look for banners ready to go NOW
 146+ $.centralNotice.fn.loadBannerList( $.centralNotice.data.getVars['country'] );
 147+ }
 148+ } ); //document ready
 149+} )( jQuery );
 150+JAVASCRIPT;
 151+ return $js;
 152+
 153+ }
 154+
 155+ function getToggleScripts() {
 156+ $script = "var wgNoticeToggleState = (document.cookie.indexOf('hidesnmessage=1')==-1);\n\n";
 157+ return $script;
 158+ }
 159+
 160+ function getScriptFunctions() {
 161+ global $wgNoticeFundraisingUrl;
 162+ $script = <<<JAVASCRIPT
 163+function insertBanner(bannerJson) {
 164+ jQuery( 'div#centralNotice' ).prepend( bannerJson.bannerHtml );
 165+ if ( bannerJson.fundraising ) {
 166+JAVASCRIPT;
 167+ $script .= "\n\t\tvar url = '" .
 168+ Xml::escapeJsString( $wgNoticeFundraisingUrl ) . "';";
 169+ $script .= <<<JAVASCRIPT
 170+ if ( bannerJson.landingPages.length ) {
 171+ targets = String( bannerJson.landingPages ).split(',');
 172+ url += "?" + jQuery.param( {
 173+ 'landing_page': targets[Math.floor( Math.random() * targets.length )].replace( /^\s+|\s+$/, '' )
 174+ } );
 175+ url += "&" + jQuery.param( {
 176+ 'utm_medium': 'sitenotice', 'utm_campaign': bannerJson.campaign,
 177+ 'utm_source': bannerJson.bannerName, 'language': wgUserLanguage,
 178+ 'country': Geo.country
 179+ } );
 180+ jQuery( '#cn_fundraising_link' ).attr( 'href', url );
 181+ }
 182+ }
 183+}
 184+function toggleNotice() {
 185+ var notice = document.getElementById('centralNotice');
 186+ if (!wgNoticeToggleState) {
 187+ notice.className = notice.className.replace('collapsed', 'expanded');
 188+ toggleNoticeCookie('0');
 189+ } else {
 190+ notice.className = notice.className.replace('expanded', 'collapsed');
 191+ toggleNoticeCookie('1');
 192+ }
 193+ wgNoticeToggleState = !wgNoticeToggleState;
 194+}
 195+function toggleNoticeStyle(elems, display) {
 196+ if(elems)
 197+ for(var i=0;i<elems.length;i++)
 198+ elems[i].style.display = display;
 199+}
 200+function toggleNoticeCookie(state) {
 201+ var e = new Date();
 202+ e.setTime( e.getTime() + (7*24*60*60*1000) ); // one week
 203+ var work='hidesnmessage='+state+'; expires=' + e.toGMTString() + '; path=/';
 204+ document.cookie = work;
 205+}
 206+
 207+JAVASCRIPT;
 208+ return $script;
 209+ }
 210+
 211+}
Property changes on: trunk/extensions/CentralNotice/special/SpecialBannerController.php
___________________________________________________________________
Added: svn:eol-style
1212 + native
Index: trunk/extensions/CentralNotice/special/SpecialCentralNotice.php
@@ -0,0 +1,1950 @@
 2+<?php
 3+
 4+if ( !defined( 'MEDIAWIKI' ) ) {
 5+ echo "CentralNotice extension\n";
 6+ exit( 1 );
 7+}
 8+
 9+class CentralNotice extends SpecialPage {
 10+ var $editable, $centralNoticeError;
 11+
 12+ function __construct() {
 13+ // Register special page
 14+ parent::__construct( 'CentralNotice' );
 15+ }
 16+
 17+ /**
 18+ * Handle different types of page requests
 19+ */
 20+ function execute( $sub ) {
 21+ global $wgOut, $wgUser, $wgRequest, $wgExtensionAssetsPath;
 22+
 23+ // Begin output
 24+ $this->setHeaders();
 25+ $this->outputHeader();
 26+
 27+ // Add style file to the output headers
 28+ $wgOut->addExtensionStyle( "$wgExtensionAssetsPath/CentralNotice/centralnotice.css" );
 29+
 30+ // Add script file to the output headers
 31+ $wgOut->addScriptFile( "$wgExtensionAssetsPath/CentralNotice/centralnotice.js" );
 32+
 33+ // Check permissions
 34+ $this->editable = $wgUser->isAllowed( 'centralnotice-admin' );
 35+
 36+ // Initialize error variable
 37+ $this->centralNoticeError = false;
 38+
 39+ // Show header
 40+ $this->printHeader( $sub );
 41+
 42+ // Begin Campaigns tab content
 43+ $wgOut->addHTML( Xml::openElement( 'div', array( 'id' => 'preferences' ) ) );
 44+
 45+ $method = $wgRequest->getVal( 'method' );
 46+
 47+ // Switch to campaign detail interface if requested
 48+ if ( $method == 'listNoticeDetail' ) {
 49+ $notice = $wgRequest->getVal ( 'notice' );
 50+ $this->listNoticeDetail( $notice );
 51+ $wgOut->addHTML( Xml::closeElement( 'div' ) );
 52+ return;
 53+ }
 54+
 55+ // Handle form submissions from "Manage campaigns" or "Add a campaign" interface
 56+ if ( $this->editable && $wgRequest->wasPosted() ) {
 57+
 58+ // Check authentication token
 59+ if ( $wgUser->matchEditToken( $wgRequest->getVal( 'authtoken' ) ) ) {
 60+
 61+ // Handle removing campaigns
 62+ $toRemove = $wgRequest->getArray( 'removeNotices' );
 63+ if ( $toRemove ) {
 64+ // Remove campaigns in list
 65+ foreach ( $toRemove as $notice ) {
 66+ $this->removeNotice( $notice );
 67+ }
 68+
 69+ // Skip subsequent form handling and show list of campaigns
 70+ $this->listNotices();
 71+ $wgOut->addHTML( Xml::closeElement( 'div' ) );
 72+ return;
 73+ }
 74+
 75+ // Handle locking/unlocking campaigns
 76+ $lockedNotices = $wgRequest->getArray( 'locked' );
 77+ if ( $lockedNotices ) {
 78+ // Build list of campaigns to lock
 79+ $unlockedNotices = array_diff( $this->getAllCampaignNames(), $lockedNotices );
 80+
 81+ // Set locked/unlocked flag accordingly
 82+ foreach ( $lockedNotices as $notice ) {
 83+ $this->updateLock( $notice, '1' );
 84+ }
 85+ foreach ( $unlockedNotices as $notice ) {
 86+ $this->updateLock( $notice, '0' );
 87+ }
 88+ // Handle updates if no post content came through (all checkboxes unchecked)
 89+ } elseif ( $method !== 'addNotice' ) {
 90+ $allNotices = $this->getAllCampaignNames();
 91+ foreach ( $allNotices as $notice ) {
 92+ $this->updateLock( $notice, '0' );
 93+ }
 94+ }
 95+
 96+ // Handle enabling/disabling campaigns
 97+ $enabledNotices = $wgRequest->getArray( 'enabled' );
 98+ if ( $enabledNotices ) {
 99+ // Build list of campaigns to disable
 100+ $disabledNotices = array_diff( $this->getAllCampaignNames(), $enabledNotices );
 101+
 102+ // Set enabled/disabled flag accordingly
 103+ foreach ( $enabledNotices as $notice ) {
 104+ $this->updateEnabled( $notice, '1' );
 105+ }
 106+ foreach ( $disabledNotices as $notice ) {
 107+ $this->updateEnabled( $notice, '0' );
 108+ }
 109+ // Handle updates if no post content came through (all checkboxes unchecked)
 110+ } elseif ( $method !== 'addNotice' ) {
 111+ $allNotices = $this->getAllCampaignNames();
 112+ foreach ( $allNotices as $notice ) {
 113+ $this->updateEnabled( $notice, '0' );
 114+ }
 115+ }
 116+
 117+ // Handle setting preferred campaigns
 118+ $preferredNotices = $wgRequest->getArray( 'preferred' );
 119+ if ( $preferredNotices ) {
 120+ // Build list of campaigns to unset
 121+ $unsetNotices = array_diff( $this->getAllCampaignNames(), $preferredNotices );
 122+
 123+ // Set flag accordingly
 124+ foreach ( $preferredNotices as $notice ) {
 125+ $this->updatePreferred( $notice, '1' );
 126+ }
 127+ foreach ( $unsetNotices as $notice ) {
 128+ $this->updatePreferred( $notice, '0' );
 129+ }
 130+ // Handle updates if no post content came through (all checkboxes unchecked)
 131+ } elseif ( $method !== 'addNotice' ) {
 132+ $allNotices = $this->getAllCampaignNames();
 133+ foreach ( $allNotices as $notice ) {
 134+ $this->updatePreferred( $notice, '0' );
 135+ }
 136+ }
 137+
 138+ // Handle adding of campaign
 139+ if ( $method == 'addNotice' ) {
 140+ $noticeName = $wgRequest->getVal( 'noticeName' );
 141+ $start = $wgRequest->getArray( 'start' );
 142+ $projects = $wgRequest->getArray( 'projects' );
 143+ $project_languages = $wgRequest->getArray( 'project_languages' );
 144+ $geotargeted = $wgRequest->getCheck( 'geotargeted' );
 145+ $geo_countries = $wgRequest->getArray( 'geo_countries' );
 146+ if ( $noticeName == '' ) {
 147+ $this->showError( 'centralnotice-null-string' );
 148+ } else {
 149+ $this->addNotice( $noticeName, '0', $start, $projects,
 150+ $project_languages, $geotargeted, $geo_countries );
 151+ }
 152+ }
 153+
 154+ // If there were no errors, reload the page to prevent duplicate form submission
 155+ if ( !$this->centralNoticeError ) {
 156+ $wgOut->redirect( $this->getTitle()->getLocalUrl() );
 157+ return;
 158+ }
 159+
 160+ } else {
 161+ $this->showError( 'sessionfailure' );
 162+ }
 163+
 164+ }
 165+
 166+ // Show list of campaigns
 167+ $this->listNotices();
 168+
 169+ // End Campaigns tab content
 170+ $wgOut->addHTML( Xml::closeElement( 'div' ) );
 171+ }
 172+
 173+ /**
 174+ * Output the tabs for the different CentralNotice interfaces (Allocation, Logs, etc.)
 175+ */
 176+ public static function printHeader() {
 177+ global $wgOut, $wgTitle, $wgUser;
 178+ $sk = $wgUser->getSkin();
 179+
 180+ $pages = array(
 181+ 'CentralNotice' => wfMsg( 'centralnotice-notices' ),
 182+ 'NoticeTemplate' => wfMsg ( 'centralnotice-templates' ),
 183+ 'BannerAllocation' => wfMsg ( 'centralnotice-allocation' ),
 184+ 'CentralNoticeLogs' => wfMsg ( 'centralnotice-logs' )
 185+ );
 186+ $htmlOut = Xml::openElement( 'ul', array( 'id' => 'preftoc' ) );
 187+ foreach ( $pages as $page => $msg ) {
 188+ $title = SpecialPage::getTitleFor( $page );
 189+ $attribs = array();
 190+ if ( $wgTitle->equals( $title ) ) {
 191+ $attribs['class'] = 'selected';
 192+ }
 193+ $htmlOut .= Xml::tags( 'li', $attribs,
 194+ $sk->makeLinkObj( $title, htmlspecialchars( $msg ) )
 195+ );
 196+ }
 197+ $htmlOut .= Xml::closeElement( 'ul' );
 198+
 199+ $wgOut->addHTML( $htmlOut );
 200+ }
 201+
 202+ /**
 203+ * Get all the campaigns in the database
 204+ * @return an array of campaign names
 205+ */
 206+ function getAllCampaignNames() {
 207+ $dbr = wfGetDB( DB_SLAVE );
 208+ $res = $dbr->select( 'cn_notices', 'not_name', null, __METHOD__ );
 209+ $notices = array();
 210+ foreach ( $res as $row ) {
 211+ $notices[] = $row->not_name;
 212+ }
 213+ return $notices;
 214+ }
 215+
 216+ /**
 217+ * Build a table row. Needed since Xml::buildTableRow escapes all HTML.
 218+ */
 219+ function tableRow( $fields, $element = 'td', $attribs = array() ) {
 220+ $cells = array();
 221+ foreach ( $fields as $field ) {
 222+ $cells[] = Xml::tags( $element, array(), $field );
 223+ }
 224+ return Xml::tags( 'tr', $attribs, implode( "\n", $cells ) ) . "\n";
 225+ }
 226+
 227+ function dateSelector( $prefix, $timestamp = null ) {
 228+ if ( $this->editable ) {
 229+ // Default ranges...
 230+ $years = range( 2008, 2014 );
 231+ $months = range( 1, 12 );
 232+ $months = array_map( array( $this, 'addZero' ), $months );
 233+ $days = range( 1 , 31 );
 234+ $days = array_map( array( $this, 'addZero' ), $days );
 235+
 236+ // Normalize timestamp format. If no timestamp passed, defaults to now.
 237+ $ts = wfTimestamp( TS_MW, $timestamp );
 238+
 239+ $fields = array(
 240+ array( "month", "centralnotice-month", $months, substr( $ts, 4, 2 ) ),
 241+ array( "day", "centralnotice-day", $days, substr( $ts, 6, 2 ) ),
 242+ array( "year", "centralnotice-year", $years, substr( $ts, 0, 4 ) ),
 243+ );
 244+
 245+ return $this->createSelector( $prefix, $fields );
 246+ } else {
 247+ global $wgLang;
 248+ return $wgLang->date( $timestamp );
 249+ }
 250+ }
 251+
 252+ function timeSelector( $prefix, $timestamp = null ) {
 253+ if ( $this->editable ) {
 254+ // Default ranges...
 255+ $minutes = range( 0, 59 ); // formerly in 15-minute increments
 256+ $minutes = array_map( array( $this, 'addZero' ), $minutes );
 257+ $hours = range( 0 , 23 );
 258+ $hours = array_map( array( $this, 'addZero' ), $hours );
 259+
 260+ // Normalize timestamp format...
 261+ $ts = wfTimestamp( TS_MW, $timestamp );
 262+
 263+ $fields = array(
 264+ array( "hour", "centralnotice-hours", $hours, substr( $ts, 8, 2 ) ),
 265+ array( "min", "centralnotice-min", $minutes, substr( $ts, 10, 2 ) ),
 266+ );
 267+
 268+ return $this->createSelector( $prefix, $fields );
 269+ } else {
 270+ global $wgLang;
 271+ return $wgLang->time( $timestamp );
 272+ }
 273+ }
 274+
 275+ /**
 276+ * Build a set of select lists. Used by dateSelector and timeSelector.
 277+ * @param $prefix string
 278+ * @param $fields array
 279+ */
 280+ private function createSelector( $prefix, $fields ) {
 281+ $out = '';
 282+ foreach ( $fields as $data ) {
 283+ list( $field, $label, $set, $current ) = $data;
 284+ $out .= Xml::listDropDown( "{$prefix}[{$field}]",
 285+ $this->dropDownList( wfMsg( $label ), $set ),
 286+ '',
 287+ $current );
 288+ }
 289+ return $out;
 290+ }
 291+
 292+ /**
 293+ * Show all campaigns found in the database, show "Add a campaign" form
 294+ */
 295+ function listNotices() {
 296+ global $wgOut, $wgUser, $wgLang, $wgRequest, $wgNoticeProjects;
 297+
 298+ // Get connection
 299+ $dbr = wfGetDB( DB_SLAVE );
 300+ $sk = $wgUser->getSkin();
 301+ if ( $this->editable ) {
 302+ $readonly = array();
 303+ } else {
 304+ $readonly = array( 'disabled' => 'disabled' );
 305+ }
 306+
 307+ // Get all campaigns from the database
 308+ $res = $dbr->select( 'cn_notices',
 309+ array(
 310+ 'not_name',
 311+ 'not_start',
 312+ 'not_end',
 313+ 'not_enabled',
 314+ 'not_preferred',
 315+ 'not_locked'
 316+ ),
 317+ null,
 318+ __METHOD__,
 319+ array( 'ORDER BY' => 'not_id DESC' )
 320+ );
 321+
 322+ // Begin building HTML
 323+ $htmlOut = '';
 324+
 325+ // Begin Manage campaigns fieldset
 326+ $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
 327+
 328+ // If there are campaigns to show...
 329+ if ( $dbr->numRows( $res ) >= 1 ) {
 330+ if ( $this->editable ) {
 331+ $htmlOut .= Xml::openElement( 'form', array( 'method' => 'post' ) );
 332+ }
 333+ $htmlOut .= Xml::element( 'h2', null, wfMsg( 'centralnotice-manage' ) );
 334+
 335+ // Begin table of campaigns
 336+ $htmlOut .= Xml::openElement( 'table',
 337+ array(
 338+ 'cellpadding' => 9,
 339+ 'width' => '100%',
 340+ 'class' => 'wikitable sortable'
 341+ )
 342+ );
 343+
 344+ // Table headers
 345+ $headers = array(
 346+ wfMsgHtml( 'centralnotice-notice-name' ),
 347+ wfMsgHtml( 'centralnotice-projects' ),
 348+ wfMsgHtml( 'centralnotice-languages' ),
 349+ wfMsgHtml( 'centralnotice-start-date' ),
 350+ wfMsgHtml( 'centralnotice-end-date' ),
 351+ wfMsgHtml( 'centralnotice-enabled' ),
 352+ wfMsgHtml( 'centralnotice-preferred' ),
 353+ wfMsgHtml( 'centralnotice-locked' ),
 354+ );
 355+ if ( $this->editable ) {
 356+ $headers[] = wfMsgHtml( 'centralnotice-remove' );
 357+ }
 358+ $htmlOut .= $this->tableRow( $headers, 'th' );
 359+
 360+ // Table rows
 361+ foreach ( $res as $row ) {
 362+ $fields = array();
 363+
 364+ // Name
 365+ $fields[] = $sk->makeLinkObj( $this->getTitle(),
 366+ htmlspecialchars( $row->not_name ),
 367+ 'method=listNoticeDetail&notice=' . urlencode( $row->not_name ) );
 368+
 369+ // Projects
 370+ $projects = $this->getNoticeProjects( $row->not_name );
 371+ $project_count = count( $projects );
 372+ $projectList = '';
 373+ if ( $project_count > 1 ) {
 374+ $allProjects = true;
 375+ foreach ( $wgNoticeProjects as $project ) {
 376+ if ( !in_array( $project, $projects ) ) {
 377+ $allProjects = false;
 378+ break;
 379+ }
 380+ }
 381+ if ( $allProjects ) {
 382+ $projectList = wfMsg ( 'centralnotice-all-projects' );
 383+ } else {
 384+ $projectList = wfMsg ( 'centralnotice-multiple-projects', $project_count );
 385+ }
 386+ } elseif ( $project_count == 1 ) {
 387+ $projectList = htmlspecialchars( $projects[0] );
 388+ }
 389+ $fields[] = $projectList;
 390+
 391+ // Languages
 392+ $project_langs = $this->getNoticeLanguages( $row->not_name );
 393+ $language_count = count( $project_langs );
 394+ $languageList = '';
 395+ if ( $language_count > 3 ) {
 396+ $languageList = wfMsg ( 'centralnotice-multiple-languages', $language_count );
 397+ } elseif ( $language_count > 0 ) {
 398+ $languageList = $wgLang->commaList( $project_langs );
 399+ }
 400+ $fields[] = $languageList;
 401+
 402+ // Date and time calculations
 403+ $start_timestamp = wfTimestamp( TS_MW, $row->not_start );
 404+ $start_year = substr( $start_timestamp, 0 , 4 );
 405+ $start_month = substr( $start_timestamp, 4, 2 );
 406+ $start_day = substr( $start_timestamp, 6, 2 );
 407+ $start_hour = substr( $start_timestamp, 8, 2 );
 408+ $start_min = substr( $start_timestamp, 10, 2 );
 409+ $end_timestamp = wfTimestamp( TS_MW, $row->not_end );
 410+ $end_year = substr( $end_timestamp, 0 , 4 );
 411+ $end_month = substr( $end_timestamp, 4, 2 );
 412+ $end_day = substr( $end_timestamp, 6, 2 );
 413+ $end_hour = substr( $end_timestamp, 8, 2 );
 414+ $end_min = substr( $end_timestamp, 10, 2 );
 415+
 416+ // Start
 417+ $fields[] = "{$start_year}/{$start_month}/{$start_day} {$start_hour}:{$start_min}";
 418+
 419+ // End
 420+ $fields[] = "{$end_year}/{$end_month}/{$end_day} {$end_hour}:{$end_min}";
 421+
 422+ // Enabled
 423+ $fields[] =
 424+ Xml::check( 'enabled[]', ( $row->not_enabled == '1' ),
 425+ wfArrayMerge( $readonly,
 426+ array( 'value' => $row->not_name, 'class' => 'noshiftselect' ) ) );
 427+
 428+ // Preferred
 429+ $fields[] =
 430+ Xml::check( 'preferred[]', ( $row->not_preferred == '1' ),
 431+ wfArrayMerge( $readonly,
 432+ array( 'value' => $row->not_name, 'class' => 'noshiftselect' ) ) );
 433+
 434+ // Locked
 435+ $fields[] =
 436+ Xml::check( 'locked[]', ( $row->not_locked == '1' ),
 437+ wfArrayMerge( $readonly,
 438+ array( 'value' => $row->not_name, 'class' => 'noshiftselect' ) ) );
 439+
 440+ if ( $this->editable ) {
 441+ // Remove
 442+ $fields[] = Xml::check( 'removeNotices[]', false,
 443+ array( 'value' => $row->not_name, 'class' => 'noshiftselect' ) );
 444+ }
 445+
 446+ // If campaign is currently active, set special class on table row.
 447+ $attribs = array();
 448+ if ( wfTimestamp() > wfTimestamp( TS_UNIX , $row->not_start )
 449+ && wfTimestamp() < wfTimestamp( TS_UNIX , $row->not_end )
 450+ && $row->not_enabled == '1' )
 451+ {
 452+ $attribs = array( 'class' => 'cn-active-campaign' );
 453+ }
 454+
 455+ $htmlOut .= $this->tableRow( $fields, 'td', $attribs );
 456+ }
 457+ // End table of campaigns
 458+ $htmlOut .= Xml::closeElement( 'table' );
 459+
 460+ if ( $this->editable ) {
 461+ $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
 462+ $htmlOut .= Xml::openElement( 'div', array( 'class' => 'cn-buttons' ) );
 463+ $htmlOut .= Xml::submitButton( wfMsg( 'centralnotice-modify' ),
 464+ array(
 465+ 'id' => 'centralnoticesubmit',
 466+ 'name' => 'centralnoticesubmit'
 467+ )
 468+ );
 469+ $htmlOut .= Xml::closeElement( 'div' );
 470+ $htmlOut .= Xml::closeElement( 'form' );
 471+ }
 472+
 473+ // No campaigns to show
 474+ } else {
 475+ $htmlOut .= wfMsg( 'centralnotice-no-notices-exist' );
 476+ }
 477+
 478+ // End Manage Campaigns fieldset
 479+ $htmlOut .= Xml::closeElement( 'fieldset' );
 480+
 481+ if ( $this->editable ) {
 482+
 483+ // If there was an error, we'll need to restore the state of the form
 484+ if ( $wgRequest->wasPosted() && ( $wgRequest->getVal( 'method' ) == 'addNotice' ) ) {
 485+ $startArray = $wgRequest->getArray( 'start' );
 486+ $startTimestamp = $startArray['year'] .
 487+ $startArray['month'] .
 488+ $startArray['day'] .
 489+ $startArray['hour'] .
 490+ $startArray['min'] . '00'
 491+ ;
 492+ $noticeProjects = $wgRequest->getArray( 'projects', array() );
 493+ $noticeLanguages = $wgRequest->getArray( 'project_languages', array() );
 494+ } else { // Defaults
 495+ $startTimestamp = null;
 496+ $noticeProjects = array();
 497+ $noticeLanguages = array();
 498+ }
 499+
 500+ // Begin Add a campaign fieldset
 501+ $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
 502+
 503+ // Form for adding a campaign
 504+ $htmlOut .= Xml::openElement( 'form', array( 'method' => 'post' ) );
 505+ $htmlOut .= Xml::element( 'h2', null, wfMsg( 'centralnotice-add-notice' ) );
 506+ $htmlOut .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
 507+ $htmlOut .= Html::hidden( 'method', 'addNotice' );
 508+
 509+ $htmlOut .= Xml::openElement( 'table', array ( 'cellpadding' => 9 ) );
 510+
 511+ // Name
 512+ $htmlOut .= Xml::openElement( 'tr' );
 513+ $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-notice-name' ) );
 514+ $htmlOut .= Xml::tags( 'td', array(),
 515+ Xml::input( 'noticeName', 25, $wgRequest->getVal( 'noticeName' ) ) );
 516+ $htmlOut .= Xml::closeElement( 'tr' );
 517+ // Start Date
 518+ $htmlOut .= Xml::openElement( 'tr' );
 519+ $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-start-date' ) );
 520+ $htmlOut .= Xml::tags( 'td', array(), $this->dateSelector( 'start', $startTimestamp ) );
 521+ $htmlOut .= Xml::closeElement( 'tr' );
 522+ // Start Time
 523+ $htmlOut .= Xml::openElement( 'tr' );
 524+ $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-start-time' ) );
 525+ $htmlOut .= Xml::tags( 'td', array(), $this->timeSelector( 'start', $startTimestamp ) );
 526+ $htmlOut .= Xml::closeElement( 'tr' );
 527+ // Project
 528+ $htmlOut .= Xml::openElement( 'tr' );
 529+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
 530+ wfMsgHtml( 'centralnotice-projects' ) );
 531+ $htmlOut .= Xml::tags( 'td', array(), $this->projectMultiSelector( $noticeProjects ) );
 532+ $htmlOut .= Xml::closeElement( 'tr' );
 533+ // Languages
 534+ $htmlOut .= Xml::openElement( 'tr' );
 535+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
 536+ wfMsgHtml( 'centralnotice-languages' ) );
 537+ $htmlOut .= Xml::tags( 'td', array(),
 538+ $this->languageMultiSelector( $noticeLanguages ) );
 539+ $htmlOut .= Xml::closeElement( 'tr' );
 540+ // Countries
 541+ $htmlOut .= Xml::openElement( 'tr' );
 542+ $htmlOut .= Xml::tags( 'td', array(),
 543+ Xml::label( wfMsg( 'centralnotice-geotargeted' ), 'geotargeted' ) );
 544+ $htmlOut .= Xml::tags( 'td', array(),
 545+ Xml::check( 'geotargeted', false,
 546+ wfArrayMerge( $readonly, array( 'value' => 1, 'id' => 'geotargeted' ) ) ) );
 547+ $htmlOut .= Xml::closeElement( 'tr' );
 548+ $htmlOut .= Xml::openElement( 'tr',
 549+ array( 'id'=>'geoMultiSelector', 'style'=>'display:none;' ) );
 550+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
 551+ wfMsgHtml( 'centralnotice-countries' ) );
 552+ $htmlOut .= Xml::tags( 'td', array(), $this->geoMultiSelector() );
 553+ $htmlOut .= Xml::closeElement( 'tr' );
 554+
 555+ $htmlOut .= Xml::closeElement( 'table' );
 556+ $htmlOut .= Html::hidden( 'change', 'weight' );
 557+ $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
 558+
 559+ // Submit button
 560+ $htmlOut .= Xml::tags( 'div',
 561+ array( 'class' => 'cn-buttons' ),
 562+ Xml::submitButton( wfMsg( 'centralnotice-modify' ) )
 563+ );
 564+
 565+ $htmlOut .= Xml::closeElement( 'form' );
 566+
 567+ // End Add a campaign fieldset
 568+ $htmlOut .= Xml::closeElement( 'fieldset' );
 569+ }
 570+
 571+ // Output HTML
 572+ $wgOut->addHTML( $htmlOut );
 573+ }
 574+
 575+ /**
 576+ * Show the interface for viewing/editing an individual campaign
 577+ * @param $notice The name of the campaign to view
 578+ */
 579+ function listNoticeDetail( $notice ) {
 580+ global $wgOut, $wgRequest, $wgUser;
 581+
 582+ // Make sure notice exists
 583+ if ( !$this->noticeExists( $notice ) ) {
 584+ $this->showError( 'centralnotice-notice-doesnt-exist' );
 585+ } else {
 586+
 587+ // Handle form submissions from campaign detail interface
 588+ if ( $this->editable && $wgRequest->wasPosted() ) {
 589+
 590+ // Check authentication token
 591+ if ( $wgUser->matchEditToken( $wgRequest->getVal( 'authtoken' ) ) ) {
 592+
 593+ // Handle removing campaign
 594+ if ( $wgRequest->getVal( 'remove' ) ) {
 595+ $this->removeNotice( $notice );
 596+ if ( !$this->centralNoticeError ) {
 597+ // Leave campaign detail interface
 598+ $wgOut->redirect( $this->getTitle()->getLocalUrl() );
 599+ return;
 600+ }
 601+ }
 602+
 603+ // Handle locking/unlocking campaign
 604+ if ( $wgRequest->getCheck( 'locked' ) ) {
 605+ $this->updateLock( $notice, '1' );
 606+ } else {
 607+ $this->updateLock( $notice, 0 );
 608+ }
 609+
 610+ // Handle enabling/disabling campaign
 611+ if ( $wgRequest->getCheck( 'enabled' ) ) {
 612+ $this->updateEnabled( $notice, '1' );
 613+ } else {
 614+ $this->updateEnabled( $notice, 0 );
 615+ }
 616+
 617+ // Handle setting campaign to preferred/not preferred
 618+ if ( $wgRequest->getCheck( 'preferred' ) ) {
 619+ $this->updatePreferred( $notice, '1' );
 620+ } else {
 621+ $this->updatePreferred( $notice, 0 );
 622+ }
 623+
 624+ // Handle updating geotargeting
 625+ if ( $wgRequest->getCheck( 'geotargeted' ) ) {
 626+ $this->updateGeotargeted( $notice, 1 );
 627+ $countries = $wgRequest->getArray( 'geo_countries' );
 628+ if ( $countries ) {
 629+ $this->updateCountries( $notice, $countries );
 630+ }
 631+ } else {
 632+ $this->updateGeotargeted( $notice, 0 );
 633+ }
 634+
 635+ // Handle updating the start and end settings
 636+ $start = $wgRequest->getArray( 'start' );
 637+ $end = $wgRequest->getArray( 'end' );
 638+ if ( $start && $end ) {
 639+ $updatedStart = sprintf( "%04d%02d%02d%02d%02d00",
 640+ $start['year'],
 641+ $start['month'],
 642+ $start['day'],
 643+ $start['hour'],
 644+ $start['min']
 645+ );
 646+ $updatedEnd = sprintf( "%04d%02d%02d%02d%02d00",
 647+ $end['year'],
 648+ $end['month'],
 649+ $end['day'],
 650+ $end['hour'],
 651+ $end['min']
 652+ );
 653+
 654+ $this->updateNoticeDate( $notice, $updatedStart, $updatedEnd );
 655+ }
 656+
 657+ // Handle adding of banners to the campaign
 658+ $templatesToAdd = $wgRequest->getArray( 'addTemplates' );
 659+ if ( $templatesToAdd ) {
 660+ $weight = $wgRequest->getArray( 'weight' );
 661+ foreach ( $templatesToAdd as $templateName ) {
 662+ $templateId = $this->getTemplateId( $templateName );
 663+ $this->addTemplateTo( $notice, $templateName, $weight[$templateId] );
 664+ }
 665+ }
 666+
 667+ // Handle removing of banners from the campaign
 668+ $templateToRemove = $wgRequest->getArray( 'removeTemplates' );
 669+ if ( $templateToRemove ) {
 670+ foreach ( $templateToRemove as $template ) {
 671+ $this->removeTemplateFor( $notice, $template );
 672+ }
 673+ }
 674+
 675+ // Handle weight changes
 676+ $updatedWeights = $wgRequest->getArray( 'weight' );
 677+ if ( $updatedWeights ) {
 678+ foreach ( $updatedWeights as $templateId => $weight ) {
 679+ $this->updateWeight( $notice, $templateId, $weight );
 680+ }
 681+ }
 682+
 683+ // Handle new projects
 684+ $projects = $wgRequest->getArray( 'projects' );
 685+ if ( $projects ) {
 686+ $this->updateProjects( $notice, $projects );
 687+ }
 688+
 689+ // Handle new project languages
 690+ $projectLangs = $wgRequest->getArray( 'project_languages' );
 691+ if ( $projectLangs ) {
 692+ $this->updateProjectLanguages( $notice, $projectLangs );
 693+ }
 694+
 695+ // If there were no errors, reload the page to prevent duplicate form submission
 696+ if ( !$this->centralNoticeError ) {
 697+ $wgOut->redirect( $this->getTitle()->getLocalUrl(
 698+ "method=listNoticeDetail&notice=$notice" ) );
 699+ return;
 700+ }
 701+ } else {
 702+ $this->showError( 'sessionfailure' );
 703+ }
 704+
 705+ }
 706+
 707+ $htmlOut = '';
 708+
 709+ // Begin Campaign detail fieldset
 710+ $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
 711+
 712+ if ( $this->editable ) {
 713+ $htmlOut .= Xml::openElement( 'form',
 714+ array(
 715+ 'method' => 'post',
 716+ 'action' => $this->getTitle()->getLocalUrl(
 717+ "method=listNoticeDetail&notice=$notice" )
 718+ )
 719+ );
 720+ }
 721+
 722+ $output_detail = $this->noticeDetailForm( $notice );
 723+ $output_assigned = $this->assignedTemplatesForm( $notice );
 724+ $output_templates = $this->addTemplatesForm( $notice );
 725+
 726+ $htmlOut .= $output_detail;
 727+
 728+ // Catch for no banners so that we don't double message
 729+ if ( $output_assigned == '' && $output_templates == '' ) {
 730+ $htmlOut .= wfMsg( 'centralnotice-no-templates' );
 731+ $htmlOut .= Xml::element( 'p' );
 732+ $newPage = $this->getTitleFor( 'NoticeTemplate', 'add' );
 733+ $sk = $wgUser->getSkin();
 734+ $htmlOut .= $sk->makeLinkObj( $newPage, wfMsgHtml( 'centralnotice-add-template' ) );
 735+ $htmlOut .= Xml::element( 'p' );
 736+ } elseif ( $output_assigned == '' ) {
 737+ $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-assigned-templates' ) );
 738+ $htmlOut .= wfMsg( 'centralnotice-no-templates-assigned' );
 739+ $htmlOut .= Xml::closeElement( 'fieldset' );
 740+ if ( $this->editable ) {
 741+ $htmlOut .= $output_templates;
 742+ }
 743+ } else {
 744+ $htmlOut .= $output_assigned;
 745+ if ( $this->editable ) {
 746+ $htmlOut .= $output_templates;
 747+ }
 748+ }
 749+ if ( $this->editable ) {
 750+ $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
 751+
 752+ // Submit button
 753+ $htmlOut .= Xml::tags( 'div',
 754+ array( 'class' => 'cn-buttons' ),
 755+ Xml::submitButton( wfMsg( 'centralnotice-modify' ) )
 756+ );
 757+ }
 758+
 759+ if ( $this->editable ) {
 760+ $htmlOut .= Xml::closeElement( 'form' );
 761+ }
 762+ $htmlOut .= Xml::closeElement( 'fieldset' );
 763+ $wgOut->addHTML( $htmlOut );
 764+ }
 765+ }
 766+
 767+ /**
 768+ * Create form for managing campaign settings (start date, end date, languages, etc.)
 769+ */
 770+ function noticeDetailForm( $notice ) {
 771+ global $wgRequest;
 772+
 773+ if ( $this->editable ) {
 774+ $readonly = array();
 775+ } else {
 776+ $readonly = array( 'disabled' => 'disabled' );
 777+ }
 778+ $dbr = wfGetDB( DB_SLAVE );
 779+
 780+ // Get campaign info from database
 781+ $row = $dbr->selectRow( 'cn_notices',
 782+ array(
 783+ 'not_id',
 784+ 'not_name',
 785+ 'not_start',
 786+ 'not_end',
 787+ 'not_enabled',
 788+ 'not_preferred',
 789+ 'not_locked',
 790+ 'not_geo'
 791+ ),
 792+ array( 'not_name' => $notice ),
 793+ __METHOD__
 794+ );
 795+
 796+ if ( $row ) {
 797+
 798+ // If there was an error, we'll need to restore the state of the form
 799+ if ( $wgRequest->wasPosted() ) {
 800+ $startArray = $wgRequest->getArray( 'start' );
 801+ $startTimestamp = $startArray['year'] .
 802+ $startArray['month'] .
 803+ $startArray['day'] .
 804+ $startArray['hour'] .
 805+ $startArray['min'] . '00'
 806+ ;
 807+ $endArray = $wgRequest->getArray( 'end' );
 808+ $endTimestamp = $endArray['year'] .
 809+ $endArray['month'] .
 810+ $endArray['day'] .
 811+ $endArray['hour'] .
 812+ $endArray['min'] .'00'
 813+ ;
 814+ $isEnabled = $wgRequest->getCheck( 'enabled' );
 815+ $isPreferred = $wgRequest->getCheck( 'preferred' );
 816+ $isLocked = $wgRequest->getCheck( 'locked' );
 817+ $noticeProjects = $wgRequest->getArray( 'projects', array() );
 818+ $noticeLanguages = $wgRequest->getArray( 'project_languages', array() );
 819+ $isGeotargeted = $wgRequest->getCheck( 'geotargeted' );
 820+ $countries = $wgRequest->getArray( 'geo_countries', array() );
 821+ } else { // Defaults
 822+ $startTimestamp = $row->not_start;
 823+ $endTimestamp = $row->not_end;
 824+ $isEnabled = ( $row->not_enabled == '1' );
 825+ $isPreferred = ( $row->not_preferred == '1' );
 826+ $isLocked = ( $row->not_locked == '1' );
 827+ $noticeProjects = $this->getNoticeProjects( $notice );
 828+ $noticeLanguages = $this->getNoticeLanguages( $notice );
 829+ $isGeotargeted = ( $row->not_geo == '1' );
 830+ $countries = $this->getNoticeCountries( $notice );
 831+ }
 832+
 833+ // Build Html
 834+ $htmlOut = '';
 835+ $htmlOut .= Xml::tags( 'h2', null, wfMsg( 'centralnotice-notice-heading', $notice ) );
 836+ $htmlOut .= Xml::openElement( 'table', array( 'cellpadding' => 9 ) );
 837+
 838+ // Rows
 839+ // Start Date
 840+ $htmlOut .= Xml::openElement( 'tr' );
 841+ $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-start-date' ) );
 842+ $htmlOut .= Xml::tags( 'td', array(), $this->dateSelector( 'start', $startTimestamp ) );
 843+ $htmlOut .= Xml::closeElement( 'tr' );
 844+ // Start Time
 845+ $htmlOut .= Xml::openElement( 'tr' );
 846+ $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-start-time' ) );
 847+ $htmlOut .= Xml::tags( 'td', array(), $this->timeSelector( 'start', $startTimestamp ) );
 848+ $htmlOut .= Xml::closeElement( 'tr' );
 849+ // End Date
 850+ $htmlOut .= Xml::openElement( 'tr' );
 851+ $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-end-date' ) );
 852+ $htmlOut .= Xml::tags( 'td', array(), $this->dateSelector( 'end', $endTimestamp ) );
 853+ $htmlOut .= Xml::closeElement( 'tr' );
 854+ // End Time
 855+ $htmlOut .= Xml::openElement( 'tr' );
 856+ $htmlOut .= Xml::tags( 'td', array(), wfMsgHtml( 'centralnotice-end-time' ) );
 857+ $htmlOut .= Xml::tags( 'td', array(), $this->timeSelector( 'end', $endTimestamp ) );
 858+ $htmlOut .= Xml::closeElement( 'tr' );
 859+ // Project
 860+ $htmlOut .= Xml::openElement( 'tr' );
 861+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
 862+ wfMsgHtml( 'centralnotice-projects' ) );
 863+ $htmlOut .= Xml::tags( 'td', array(),
 864+ $this->projectMultiSelector( $noticeProjects ) );
 865+ $htmlOut .= Xml::closeElement( 'tr' );
 866+ // Languages
 867+ $htmlOut .= Xml::openElement( 'tr' );
 868+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
 869+ wfMsgHtml( 'centralnotice-languages' ) );
 870+ $htmlOut .= Xml::tags( 'td', array(),
 871+ $this->languageMultiSelector( $noticeLanguages ) );
 872+ $htmlOut .= Xml::closeElement( 'tr' );
 873+ // Countries
 874+ $htmlOut .= Xml::openElement( 'tr' );
 875+ $htmlOut .= Xml::tags( 'td', array(),
 876+ Xml::label( wfMsg( 'centralnotice-geotargeted' ), 'geotargeted' ) );
 877+ $htmlOut .= Xml::tags( 'td', array(),
 878+ Xml::check( 'geotargeted', $isGeotargeted,
 879+ wfArrayMerge(
 880+ $readonly,
 881+ array( 'value' => $row->not_name, 'id' => 'geotargeted' ) ) ) );
 882+ $htmlOut .= Xml::closeElement( 'tr' );
 883+ if ( $isGeotargeted ) {
 884+ $htmlOut .= Xml::openElement( 'tr', array( 'id'=>'geoMultiSelector' ) );
 885+ } else {
 886+ $htmlOut .= Xml::openElement( 'tr',
 887+ array( 'id'=>'geoMultiSelector', 'style'=>'display:none;' ) );
 888+ }
 889+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
 890+ wfMsgHtml( 'centralnotice-countries' ) );
 891+ $htmlOut .= Xml::tags( 'td', array(), $this->geoMultiSelector( $countries ) );
 892+ $htmlOut .= Xml::closeElement( 'tr' );
 893+ // Enabled
 894+ $htmlOut .= Xml::openElement( 'tr' );
 895+ $htmlOut .= Xml::tags( 'td', array(),
 896+ Xml::label( wfMsg( 'centralnotice-enabled' ), 'enabled' ) );
 897+ $htmlOut .= Xml::tags( 'td', array(),
 898+ Xml::check( 'enabled', $isEnabled,
 899+ wfArrayMerge( $readonly,
 900+ array( 'value' => $row->not_name, 'id' => 'enabled' ) ) ) );
 901+ $htmlOut .= Xml::closeElement( 'tr' );
 902+ // Preferred
 903+ $htmlOut .= Xml::openElement( 'tr' );
 904+ $htmlOut .= Xml::tags( 'td', array(),
 905+ Xml::label( wfMsg( 'centralnotice-preferred' ), 'preferred' ) );
 906+ $htmlOut .= Xml::tags( 'td', array(),
 907+ Xml::check( 'preferred', $isPreferred,
 908+ wfArrayMerge( $readonly,
 909+ array( 'value' => $row->not_name, 'id' => 'preferred' ) ) ) );
 910+ $htmlOut .= Xml::closeElement( 'tr' );
 911+ // Locked
 912+ $htmlOut .= Xml::openElement( 'tr' );
 913+ $htmlOut .= Xml::tags( 'td', array(),
 914+ Xml::label( wfMsg( 'centralnotice-locked' ), 'locked' ) );
 915+ $htmlOut .= Xml::tags( 'td', array(),
 916+ Xml::check( 'locked', $isLocked,
 917+ wfArrayMerge( $readonly,
 918+ array( 'value' => $row->not_name, 'id' => 'locked' ) ) ) );
 919+ $htmlOut .= Xml::closeElement( 'tr' );
 920+ if ( $this->editable ) {
 921+ // Locked
 922+ $htmlOut .= Xml::openElement( 'tr' );
 923+ $htmlOut .= Xml::tags( 'td', array(),
 924+ Xml::label( wfMsg( 'centralnotice-remove' ), 'remove' ) );
 925+ $htmlOut .= Xml::tags( 'td', array(),
 926+ Xml::check( 'remove', false,
 927+ array( 'value' => $row->not_name, 'id' => 'remove' ) ) );
 928+ $htmlOut .= Xml::closeElement( 'tr' );
 929+ }
 930+ $htmlOut .= Xml::closeElement( 'table' );
 931+ return $htmlOut;
 932+ } else {
 933+ return '';
 934+ }
 935+ }
 936+
 937+ /**
 938+ * Create form for managing banners assigned to a campaign
 939+ */
 940+ function assignedTemplatesForm( $notice ) {
 941+ global $wgUser;
 942+ $sk = $wgUser->getSkin();
 943+
 944+ $dbr = wfGetDB( DB_SLAVE );
 945+ $res = $dbr->select(
 946+ array(
 947+ 'cn_notices',
 948+ 'cn_assignments',
 949+ 'cn_templates'
 950+ ),
 951+ array(
 952+ 'cn_templates.tmp_id',
 953+ 'cn_templates.tmp_name',
 954+ 'cn_assignments.tmp_weight'
 955+ ),
 956+ array(
 957+ 'cn_notices.not_name' => $notice,
 958+ 'cn_notices.not_id = cn_assignments.not_id',
 959+ 'cn_assignments.tmp_id = cn_templates.tmp_id'
 960+ ),
 961+ __METHOD__,
 962+ array( 'ORDER BY' => 'cn_notices.not_id' )
 963+ );
 964+
 965+ // No banners found
 966+ if ( $dbr->numRows( $res ) < 1 ) {
 967+ return;
 968+ }
 969+
 970+ // Build Assigned banners HTML
 971+ $htmlOut = Html::hidden( 'change', 'weight' );
 972+ $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-assigned-templates' ) );
 973+ $htmlOut .= Xml::openElement( 'table',
 974+ array(
 975+ 'cellpadding' => 9,
 976+ 'width' => '100%'
 977+ )
 978+ );
 979+ if ( $this->editable ) {
 980+ $htmlOut .= Xml::element( 'th', array( 'align' => 'left', 'width' => '5%' ),
 981+ wfMsg ( "centralnotice-remove" ) );
 982+ }
 983+ $htmlOut .= Xml::element( 'th', array( 'align' => 'left', 'width' => '5%' ),
 984+ wfMsg ( "centralnotice-weight" ) );
 985+ $htmlOut .= Xml::element( 'th', array( 'align' => 'left', 'width' => '70%' ),
 986+ wfMsg ( "centralnotice-templates" ) );
 987+
 988+ // Table rows
 989+ foreach( $res as $row ) {
 990+
 991+ $htmlOut .= Xml::openElement( 'tr' );
 992+
 993+ if ( $this->editable ) {
 994+ // Remove
 995+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
 996+ Xml::check( 'removeTemplates[]', false, array( 'value' => $row->tmp_name ) )
 997+ );
 998+ }
 999+
 1000+ // Weight
 1001+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
 1002+ $this->weightDropDown( "weight[$row->tmp_id]", $row->tmp_weight )
 1003+ );
 1004+
 1005+ $viewPage = $this->getTitleFor( 'NoticeTemplate', 'view' );
 1006+
 1007+ /* XXX this code is duplicated in the CentralNoticePager::formatRow */
 1008+ $render = new SpecialBannerLoader();
 1009+ $render->siteName = 'Wikipedia';
 1010+ global $wgRequest;
 1011+ $render->language = $wgRequest->getVal( 'wpUserLanguage' );
 1012+ try {
 1013+ $preview = $render->getHtmlNotice( $row->tmp_name );
 1014+ } catch ( SpecialBannerLoaderException $e ) {
 1015+ $preview = wfMsg( 'centralnotice-nopreview' );
 1016+ }
 1017+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
 1018+ $sk->makeLinkObj( $viewPage,
 1019+ htmlspecialchars( $row->tmp_name ),
 1020+ 'template=' . urlencode( $row->tmp_name ) ) .
 1021+ Xml::fieldset( wfMsg( 'centralnotice-preview' ),
 1022+ $preview,
 1023+ array( 'class' => 'cn-bannerpreview')
 1024+ )
 1025+ );
 1026+
 1027+ $htmlOut .= Xml::closeElement( 'tr' );
 1028+ }
 1029+ $htmlOut .= XMl::closeElement( 'table' );
 1030+ $htmlOut .= Xml::closeElement( 'fieldset' );
 1031+ return $htmlOut;
 1032+
 1033+ }
 1034+
 1035+ function weightDropDown( $name, $selected ) {
 1036+ if ( $this->editable ) {
 1037+ return Xml::listDropDown( $name,
 1038+ $this->dropDownList( wfMsg( 'centralnotice-weight' ),
 1039+ range ( 0, 100, 5 ) ),
 1040+ '',
 1041+ $selected,
 1042+ '',
 1043+ 1 );
 1044+ } else {
 1045+ return htmlspecialchars( $selected );
 1046+ }
 1047+ }
 1048+
 1049+ /**
 1050+ * Create form for adding banners to a campaign
 1051+ */
 1052+ function addTemplatesForm( $notice ) {
 1053+ $pager = new CentralNoticePager( $this );
 1054+ $dbr = wfGetDB( DB_SLAVE );
 1055+ $res = $dbr->select( 'cn_templates', 'tmp_name', '', '', array( 'ORDER BY' => 'tmp_id' ) );
 1056+
 1057+ if ( $dbr->numRows( $res ) > 0 ) {
 1058+ // Build HTML
 1059+ $htmlOut = Xml::fieldset( wfMsg( "centralnotice-available-templates" ) );
 1060+
 1061+ // Show paginated list of banners
 1062+ $htmlOut .= Xml::tags( 'div',
 1063+ array( 'class' => 'cn-pager' ),
 1064+ $pager->getNavigationBar() );
 1065+ $htmlOut .= $pager->getBody();
 1066+ $htmlOut .= Xml::tags( 'div',
 1067+ array( 'class' => 'cn-pager' ),
 1068+ $pager->getNavigationBar() );
 1069+
 1070+ $htmlOut .= Xml::closeElement( 'fieldset' );
 1071+ } else {
 1072+ // Nothing found
 1073+ return;
 1074+ }
 1075+ return $htmlOut;
 1076+ }
 1077+
 1078+ /**
 1079+ * Lookup function for active banners under a given language/project/location. This function is
 1080+ * called by SpecialBannerListLoader::getJsonList() in order to build the banner list JSON for
 1081+ * each project.
 1082+ * @return a 2D array of running banners with associated weights and settings
 1083+ */
 1084+ static function selectNoticeTemplates( $project, $language, $location = null ) {
 1085+ global $wgCentralDBname;
 1086+
 1087+ $campaigns = array();
 1088+ $dbr = wfGetDB( DB_SLAVE, array(), $wgCentralDBname );
 1089+ $encTimestamp = $dbr->addQuotes( $dbr->timestamp() );
 1090+
 1091+ // Pull non-geotargeted campaigns
 1092+ $campaignResults1 = $dbr->select(
 1093+ array(
 1094+ 'cn_notices',
 1095+ 'cn_notice_projects',
 1096+ 'cn_notice_languages'
 1097+ ),
 1098+ array(
 1099+ 'not_id'
 1100+ ),
 1101+ array(
 1102+ "not_start <= $encTimestamp",
 1103+ "not_end >= $encTimestamp",
 1104+ 'not_enabled = 1', // enabled
 1105+ 'not_geo = 0', // not geotargeted
 1106+ 'np_notice_id = cn_notices.not_id',
 1107+ 'np_project' => $project,
 1108+ 'nl_notice_id = cn_notices.not_id',
 1109+ 'nl_language' => $language
 1110+ ),
 1111+ __METHOD__
 1112+ );
 1113+ foreach ( $campaignResults1 as $row ) {
 1114+ $campaigns[] = $row->not_id;
 1115+ }
 1116+ if ( $location ) {
 1117+
 1118+ // Normalize location parameter (should be an uppercase 2-letter country code)
 1119+ preg_match( '/[a-zA-Z][a-zA-Z]/', $location, $matches );
 1120+ if ( $matches ) {
 1121+ $location = strtoupper( $matches[0] );
 1122+
 1123+ // Pull geotargeted campaigns
 1124+ $campaignResults2 = $dbr->select(
 1125+ array(
 1126+ 'cn_notices',
 1127+ 'cn_notice_projects',
 1128+ 'cn_notice_languages',
 1129+ 'cn_notice_countries'
 1130+ ),
 1131+ array(
 1132+ 'not_id'
 1133+ ),
 1134+ array(
 1135+ "not_start <= $encTimestamp",
 1136+ "not_end >= $encTimestamp",
 1137+ 'not_enabled = 1', // enabled
 1138+ 'not_geo = 1', // geotargeted
 1139+ 'nc_notice_id = cn_notices.not_id',
 1140+ 'nc_country' => $location,
 1141+ 'np_notice_id = cn_notices.not_id',
 1142+ 'np_project' => $project,
 1143+ 'nl_notice_id = cn_notices.not_id',
 1144+ 'nl_language' => $language
 1145+ ),
 1146+ __METHOD__
 1147+ );
 1148+ foreach ( $campaignResults2 as $row ) {
 1149+ $campaigns[] = $row->not_id;
 1150+ }
 1151+ }
 1152+ }
 1153+
 1154+ $templates = array();
 1155+ if ( $campaigns ) {
 1156+ // Pull all banners assigned to the campaigns
 1157+ $templates = CentralNoticeDB::selectBannersAssigned( $campaigns );
 1158+ }
 1159+ return $templates;
 1160+ }
 1161+
 1162+ function addNotice( $noticeName, $enabled, $start, $projects,
 1163+ $project_languages, $geotargeted, $geo_countries )
 1164+ {
 1165+ if ( $this->noticeExists( $noticeName ) ) {
 1166+ $this->showError( 'centralnotice-notice-exists' );
 1167+ return;
 1168+ } elseif ( empty( $projects ) ) {
 1169+ $this->showError( 'centralnotice-no-project' );
 1170+ return;
 1171+ } elseif ( empty( $project_languages ) ) {
 1172+ $this->showError( 'centralnotice-no-language' );
 1173+ return;
 1174+ } else {
 1175+ $dbw = wfGetDB( DB_MASTER );
 1176+ $dbw->begin();
 1177+ $start['hour'] = substr( $start['hour'], 0 , 2 );
 1178+ $start['min'] = substr( $start['min'], 0 , 2 );
 1179+ if ( $start['month'] == 12 ) {
 1180+ $end['month'] = '01';
 1181+ $end['year'] = ( $start['year'] + 1 );
 1182+ } elseif ( $start['month'] == '09' ) {
 1183+ $end['month'] = '10';
 1184+ $end['year'] = $start['year'];
 1185+ } else {
 1186+ $end['month'] =
 1187+ ( substr( $start['month'], 0, 1 ) ) == 0
 1188+ ? 0 . ( intval( $start['month'] ) + 1 )
 1189+ : ( $start['month'] + 1 );
 1190+ $end['year'] = $start['year'];
 1191+ }
 1192+
 1193+ $startTs = wfTimeStamp( TS_MW, "{$start['year']}:{$start['month']}:{$start['day']} " .
 1194+ "{$start['hour']}:{$start['min']}:00" );
 1195+ $endTs = wfTimeStamp( TS_MW, "{$end['year']}:{$end['month']}:{$start['day']} " .
 1196+ "{$start['hour']}:{$start['min']}:00" );
 1197+
 1198+ $res = $dbw->insert( 'cn_notices',
 1199+ array( 'not_name' => $noticeName,
 1200+ 'not_enabled' => $enabled,
 1201+ 'not_start' => $dbw->timestamp( $startTs ),
 1202+ 'not_end' => $dbw->timestamp( $endTs ),
 1203+ 'not_geo' => $geotargeted
 1204+ )
 1205+ );
 1206+ $not_id = $dbw->insertId();
 1207+
 1208+ // Do multi-row insert for campaign projects
 1209+ $insertArray = array();
 1210+ foreach( $projects as $project ) {
 1211+ $insertArray[] = array( 'np_notice_id' => $not_id, 'np_project' => $project );
 1212+ }
 1213+ $res = $dbw->insert( 'cn_notice_projects', $insertArray,
 1214+ __METHOD__, array( 'IGNORE' ) );
 1215+
 1216+ // Do multi-row insert for campaign languages
 1217+ $insertArray = array();
 1218+ foreach( $project_languages as $code ) {
 1219+ $insertArray[] = array( 'nl_notice_id' => $not_id, 'nl_language' => $code );
 1220+ }
 1221+ $res = $dbw->insert( 'cn_notice_languages', $insertArray,
 1222+ __METHOD__, array( 'IGNORE' ) );
 1223+
 1224+ if ( $geotargeted && $geo_countries ) {
 1225+ // Do multi-row insert for campaign countries
 1226+ $insertArray = array();
 1227+ foreach( $geo_countries as $code ) {
 1228+ $insertArray[] = array( 'nc_notice_id' => $not_id, 'nc_country' => $code );
 1229+ }
 1230+ $res = $dbw->insert( 'cn_notice_countries', $insertArray,
 1231+ __METHOD__, array( 'IGNORE' ) );
 1232+ }
 1233+
 1234+ $dbw->commit();
 1235+
 1236+ // Log the creation of the campaign
 1237+ $beginSettings = array();
 1238+ $endSettings = array(
 1239+ 'notlog_end_name' => $noticeName,
 1240+ 'notlog_end_projects' => implode( ", ", $projects ),
 1241+ 'notlog_end_languages' => implode( ", ", $project_languages ),
 1242+ 'notlog_end_countries' => implode( ", ", $geo_countries ),
 1243+ 'notlog_end_start' => $dbw->timestamp( $startTs ),
 1244+ 'notlog_end_end' => $dbw->timestamp( $endTs ),
 1245+ 'notlog_end_enabled' => $enabled,
 1246+ 'notlog_end_preferred' => 0,
 1247+ 'notlog_end_locked' => 0,
 1248+ 'notlog_end_geo' => $geotargeted
 1249+ );
 1250+ $this->logCampaignChange( 'created', $not_id, $beginSettings, $endSettings );
 1251+
 1252+ return;
 1253+ }
 1254+ }
 1255+
 1256+ function removeNotice( $noticeName ) {
 1257+ $dbr = wfGetDB( DB_SLAVE );
 1258+
 1259+ $res = $dbr->select( 'cn_notices', 'not_name, not_locked',
 1260+ array( 'not_name' => $noticeName )
 1261+ );
 1262+ if ( $dbr->numRows( $res ) < 1 ) {
 1263+ $this->showError( 'centralnotice-remove-notice-doesnt-exist' );
 1264+ return;
 1265+ }
 1266+ $row = $dbr->fetchObject( $res );
 1267+ if ( $row->not_locked == '1' ) {
 1268+ $this->showError( 'centralnotice-notice-is-locked' );
 1269+ return;
 1270+ } else {
 1271+ $dbw = wfGetDB( DB_MASTER );
 1272+ $dbw->begin();
 1273+ $noticeId = htmlspecialchars( $this->getNoticeId( $noticeName ) );
 1274+ $res = $dbw->delete( 'cn_assignments', array ( 'not_id' => $noticeId ) );
 1275+ $res = $dbw->delete( 'cn_notices', array ( 'not_name' => $noticeName ) );
 1276+ $res = $dbw->delete( 'cn_notice_languages', array ( 'nl_notice_id' => $noticeId ) );
 1277+ $dbw->commit();
 1278+
 1279+ // Log the removal of the campaign
 1280+ $this->logCampaignChange( 'removed', $noticeId );
 1281+
 1282+ return;
 1283+ }
 1284+ }
 1285+
 1286+ function addTemplateTo( $noticeName, $templateName, $weight ) {
 1287+ $dbr = wfGetDB( DB_SLAVE );
 1288+
 1289+ $eNoticeName = htmlspecialchars ( $noticeName );
 1290+ $noticeId = $this->getNoticeId( $eNoticeName );
 1291+ $templateId = $this->getTemplateId( $templateName );
 1292+ $res = $dbr->select( 'cn_assignments', 'asn_id',
 1293+ array(
 1294+ 'tmp_id' => $templateId,
 1295+ 'not_id' => $noticeId
 1296+ )
 1297+ );
 1298+ if ( $dbr->numRows( $res ) > 0 ) {
 1299+ $this->showError( 'centralnotice-template-already-exists' );
 1300+ } else {
 1301+ $dbw = wfGetDB( DB_MASTER );
 1302+ $dbw->begin();
 1303+ $noticeId = $this->getNoticeId( $eNoticeName );
 1304+ $res = $dbw->insert( 'cn_assignments',
 1305+ array(
 1306+ 'tmp_id' => $templateId,
 1307+ 'tmp_weight' => $weight,
 1308+ 'not_id' => $noticeId
 1309+ )
 1310+ );
 1311+ $dbw->commit();
 1312+ }
 1313+ }
 1314+
 1315+ /**
 1316+ * Lookup the ID for a campaign based on the campaign name
 1317+ */
 1318+ public static function getNoticeId( $noticeName ) {
 1319+ $dbr = wfGetDB( DB_SLAVE );
 1320+ $eNoticeName = htmlspecialchars( $noticeName );
 1321+ $row = $dbr->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $eNoticeName ) );
 1322+ if ( $row ) {
 1323+ return $row->not_id;
 1324+ } else {
 1325+ return null;
 1326+ }
 1327+ }
 1328+
 1329+ /*
 1330+ * Lookup the name of a campaign based on the campaign ID
 1331+ */
 1332+ public static function getNoticeName( $noticeId ) {
 1333+ $dbr = wfGetDB( DB_SLAVE );
 1334+ if ( is_numeric( $noticeId ) ) {
 1335+ $row = $dbr->selectRow( 'cn_notices', 'not_name', array( 'not_id' => $noticeId ) );
 1336+ if ( $row ) {
 1337+ return $row->not_name;
 1338+ }
 1339+ }
 1340+ return null;
 1341+ }
 1342+
 1343+ function getNoticeProjects( $noticeName ) {
 1344+ $dbr = wfGetDB( DB_SLAVE );
 1345+ $eNoticeName = htmlspecialchars( $noticeName );
 1346+ $row = $dbr->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $eNoticeName ) );
 1347+ $projects = array();
 1348+ if ( $row ) {
 1349+ $res = $dbr->select( 'cn_notice_projects', 'np_project',
 1350+ array( 'np_notice_id' => $row->not_id ) );
 1351+ foreach ( $res as $projectRow ) {
 1352+ $projects[] = $projectRow->np_project;
 1353+ }
 1354+ }
 1355+ return $projects;
 1356+ }
 1357+
 1358+ function getNoticeLanguages( $noticeName ) {
 1359+ $dbr = wfGetDB( DB_SLAVE );
 1360+ $eNoticeName = htmlspecialchars( $noticeName );
 1361+ $row = $dbr->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $eNoticeName ) );
 1362+ $languages = array();
 1363+ if ( $row ) {
 1364+ $res = $dbr->select( 'cn_notice_languages', 'nl_language',
 1365+ array( 'nl_notice_id' => $row->not_id ) );
 1366+ foreach ( $res as $langRow ) {
 1367+ $languages[] = $langRow->nl_language;
 1368+ }
 1369+ }
 1370+ return $languages;
 1371+ }
 1372+
 1373+ function getNoticeCountries( $noticeName ) {
 1374+ $dbr = wfGetDB( DB_SLAVE );
 1375+ $eNoticeName = htmlspecialchars( $noticeName );
 1376+ $row = $dbr->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $eNoticeName ) );
 1377+ $countries = array();
 1378+ if ( $row ) {
 1379+ $res = $dbr->select( 'cn_notice_countries', 'nc_country',
 1380+ array( 'nc_notice_id' => $row->not_id ) );
 1381+ foreach ( $res as $countryRow ) {
 1382+ $countries[] = $countryRow->nc_country;
 1383+ }
 1384+ }
 1385+ return $countries;
 1386+ }
 1387+
 1388+ function getNoticeProjectName( $noticeName ) {
 1389+ $dbr = wfGetDB( DB_SLAVE );
 1390+ $eNoticeName = htmlspecialchars( $noticeName );
 1391+ $res = $dbr->select( 'cn_notices', 'not_project', array( 'not_name' => $eNoticeName ) );
 1392+ $row = $dbr->fetchObject( $res );
 1393+ return $row->not_project;
 1394+ }
 1395+
 1396+ function getTemplateId( $templateName ) {
 1397+ $dbr = wfGetDB( DB_SLAVE );
 1398+ $templateName = htmlspecialchars ( $templateName );
 1399+ $res = $dbr->select( 'cn_templates', 'tmp_id', array( 'tmp_name' => $templateName ) );
 1400+ $row = $dbr->fetchObject( $res );
 1401+ return $row->tmp_id;
 1402+ }
 1403+
 1404+ function removeTemplateFor( $noticeName, $templateName ) {
 1405+ $dbw = wfGetDB( DB_MASTER );
 1406+ $dbw->begin();
 1407+ $noticeId = $this->getNoticeId( $noticeName );
 1408+ $templateId = $this->getTemplateId( $templateName );
 1409+ $dbw->delete( 'cn_assignments', array ( 'tmp_id' => $templateId, 'not_id' => $noticeId ) );
 1410+ $dbw->commit();
 1411+ }
 1412+
 1413+ function updateNoticeDate( $noticeName, $start, $end ) {
 1414+ $dbr = wfGetDB( DB_SLAVE );
 1415+
 1416+ // Start/end don't line up
 1417+ if ( $start > $end || $end < $start ) {
 1418+ $this->showError( 'centralnotice-invalid-date-range' );
 1419+ return;
 1420+ }
 1421+
 1422+ // Invalid campaign name
 1423+ if ( !$this->noticeExists( $noticeName ) ) {
 1424+ $this->showError( 'centralnotice-notice-doesnt-exist' );
 1425+ return;
 1426+ }
 1427+
 1428+ // Overlap over a date within the same project and language
 1429+ $startDate = $dbr->timestamp( $start );
 1430+ $endDate = $dbr->timestamp( $end );
 1431+
 1432+ $dbw = wfGetDB( DB_MASTER );
 1433+ $res = $dbw->update( 'cn_notices',
 1434+ array(
 1435+ 'not_start' => $startDate,
 1436+ 'not_end' => $endDate
 1437+ ),
 1438+ array( 'not_name' => $noticeName )
 1439+ );
 1440+ }
 1441+
 1442+ /**
 1443+ * Update the enabled/disabled state of a campaign
 1444+ */
 1445+ private function updateEnabled( $noticeName, $isEnabled ) {
 1446+ if ( !$this->noticeExists( $noticeName ) ) {
 1447+ $this->showError( 'centralnotice-doesnt-exist' );
 1448+ } else {
 1449+ $dbw = wfGetDB( DB_MASTER );
 1450+ $res = $dbw->update( 'cn_notices',
 1451+ array( 'not_enabled' => $isEnabled ),
 1452+ array( 'not_name' => $noticeName )
 1453+ );
 1454+ }
 1455+ }
 1456+
 1457+ /**
 1458+ * Update the preferred/not preferred state of a campaign
 1459+ */
 1460+ function updatePreferred( $noticeName, $isPreferred ) {
 1461+ if ( !$this->noticeExists( $noticeName ) ) {
 1462+ $this->showError( 'centralnotice-doesnt-exist' );
 1463+ } else {
 1464+ $dbw = wfGetDB( DB_MASTER );
 1465+ $res = $dbw->update( 'cn_notices',
 1466+ array( 'not_preferred' => $isPreferred ),
 1467+ array( 'not_name' => $noticeName )
 1468+ );
 1469+ }
 1470+ }
 1471+
 1472+ /**
 1473+ * Update the geotargeted/not geotargeted state of a campaign
 1474+ */
 1475+ function updateGeotargeted( $noticeName, $isGeotargeted ) {
 1476+ if ( !$this->noticeExists( $noticeName ) ) {
 1477+ $this->showError( 'centralnotice-doesnt-exist' );
 1478+ } else {
 1479+ $dbw = wfGetDB( DB_MASTER );
 1480+ $res = $dbw->update( 'cn_notices',
 1481+ array( 'not_geo' => $isGeotargeted ),
 1482+ array( 'not_name' => $noticeName )
 1483+ );
 1484+ }
 1485+ }
 1486+
 1487+ /**
 1488+ * Update the locked/unlocked state of a campaign
 1489+ */
 1490+ function updateLock( $noticeName, $isLocked ) {
 1491+ if ( !$this->noticeExists( $noticeName ) ) {
 1492+ $this->showError( 'centralnotice-doesnt-exist' );
 1493+ } else {
 1494+ $dbw = wfGetDB( DB_MASTER );
 1495+ $res = $dbw->update( 'cn_notices',
 1496+ array( 'not_locked' => $isLocked ),
 1497+ array( 'not_name' => $noticeName )
 1498+ );
 1499+ }
 1500+ }
 1501+
 1502+ function updateWeight( $noticeName, $templateId, $weight ) {
 1503+ $dbw = wfGetDB( DB_MASTER );
 1504+ $noticeId = $this->getNoticeId( $noticeName );
 1505+ $dbw->update( 'cn_assignments',
 1506+ array ( 'tmp_weight' => $weight ),
 1507+ array(
 1508+ 'tmp_id' => $templateId,
 1509+ 'not_id' => $noticeId
 1510+ )
 1511+ );
 1512+ }
 1513+
 1514+ /**
 1515+ * Generates a multiple select list of all languages.
 1516+ * @param $selected The language codes of the selected languages
 1517+ * @param $customisedOnly If true only languages which have some content are listed
 1518+ * @return multiple select list
 1519+ */
 1520+ function languageMultiSelector( $selected = array(), $customisedOnly = true ) {
 1521+ global $wgLanguageCode, $wgExtensionAssetsPath, $wgLang;
 1522+ $scriptPath = "$wgExtensionAssetsPath/CentralNotice";
 1523+ if ( is_callable( array( 'LanguageNames', 'getNames' ) ) ) {
 1524+ // Retrieve the list of languages in user's language (via CLDR)
 1525+ $languages = LanguageNames::getNames(
 1526+ $wgLang->getCode(), // User's language
 1527+ LanguageNames::FALLBACK_NORMAL, // Use fallback chain
 1528+ LanguageNames::LIST_MW // Pull all languages that are in Names.php
 1529+ );
 1530+ } else {
 1531+ // Use this as fallback if CLDR extension is not enabled
 1532+ $languages = Language::getLanguageNames();
 1533+ }
 1534+ // Make sure the site language is in the list; a custom language code
 1535+ // might not have a defined name...
 1536+ if( !array_key_exists( $wgLanguageCode, $languages ) ) {
 1537+ $languages[$wgLanguageCode] = $wgLanguageCode;
 1538+ }
 1539+ ksort( $languages );
 1540+
 1541+ $options = "\n";
 1542+ foreach( $languages as $code => $name ) {
 1543+ $options .= Xml::option(
 1544+ wfMsg( 'centralnotice-language-listing', $code, $name ),
 1545+ $code,
 1546+ in_array( $code, $selected )
 1547+ ) . "\n";
 1548+ }
 1549+ $htmlOut = '';
 1550+ if ( $this->editable ) {
 1551+ $htmlOut .= Xml::tags( 'select',
 1552+ array( 'multiple' => 'multiple', 'size' => 4, 'id' => 'project_languages[]', 'name' => 'project_languages[]' ),
 1553+ $options
 1554+ );
 1555+ $buttons = array();
 1556+ $buttons[] = '<a href="#" onclick="selectLanguages(true);return false;">' .
 1557+ wfMsg( 'powersearch-toggleall' ) . '</a>';
 1558+ $buttons[] = '<a href="#" onclick="selectLanguages(false);return false;">' .
 1559+ wfMsg( 'powersearch-togglenone' ) . '</a>';
 1560+ $buttons[] = '<a href="#" onclick="top10Languages();return false;">' .
 1561+ wfMsg( 'centralnotice-top-ten-languages' ) . '</a>';
 1562+ $htmlOut .= Xml::tags( 'div',
 1563+ array( 'style' => 'margin-top: 0.2em;' ),
 1564+ '<img src="'.$scriptPath.'/up-arrow.png" style="vertical-align:baseline;"/>' .
 1565+ wfMsg( 'centralnotice-select', $wgLang->commaList( $buttons ) )
 1566+ );
 1567+ } else {
 1568+ $htmlOut .= Xml::tags( 'select',
 1569+ array(
 1570+ 'multiple' => 'multiple',
 1571+ 'size' => 4,
 1572+ 'id' => 'project_languages[]',
 1573+ 'name' => 'project_languages[]',
 1574+ 'disabled' => 'disabled'
 1575+ ),
 1576+ $options
 1577+ );
 1578+ }
 1579+ return $htmlOut;
 1580+ }
 1581+
 1582+ /**
 1583+ * Generates a multiple select list of all project types.
 1584+ * @param $selected The name of the selected project type
 1585+ * @return multiple select list
 1586+ */
 1587+ function projectMultiSelector( $selected = array() ) {
 1588+ global $wgNoticeProjects, $wgExtensionAssetsPath, $wgLang;
 1589+ $scriptPath = "$wgExtensionAssetsPath/CentralNotice";
 1590+
 1591+ $options = "\n";
 1592+ foreach( $wgNoticeProjects as $project ) {
 1593+ $options .= Xml::option(
 1594+ $project,
 1595+ $project,
 1596+ in_array( $project, $selected )
 1597+ ) . "\n";
 1598+ }
 1599+ $htmlOut = '';
 1600+ if ( $this->editable ) {
 1601+ $htmlOut .= Xml::tags( 'select',
 1602+ array( 'multiple' => 'multiple', 'size' => 4, 'id' => 'projects[]', 'name' => 'projects[]' ),
 1603+ $options
 1604+ );
 1605+ $buttons = array();
 1606+ $buttons[] = '<a href="#" onclick="selectProjects(true);return false;">' .
 1607+ wfMsg( 'powersearch-toggleall' ) . '</a>';
 1608+ $buttons[] = '<a href="#" onclick="selectProjects(false);return false;">' .
 1609+ wfMsg( 'powersearch-togglenone' ) . '</a>';
 1610+ $htmlOut .= Xml::tags( 'div',
 1611+ array( 'style' => 'margin-top: 0.2em;' ),
 1612+ '<img src="'.$scriptPath.'/up-arrow.png" style="vertical-align:baseline;"/>' .
 1613+ wfMsg( 'centralnotice-select', $wgLang->commaList( $buttons ) )
 1614+ );
 1615+ } else {
 1616+ $htmlOut .= Xml::tags( 'select',
 1617+ array(
 1618+ 'multiple' => 'multiple',
 1619+ 'size' => 4,
 1620+ 'id' => 'projects[]',
 1621+ 'name' => 'projects[]',
 1622+ 'disabled' => 'disabled'
 1623+ ),
 1624+ $options
 1625+ );
 1626+ }
 1627+ return $htmlOut;
 1628+ }
 1629+
 1630+ function getProjectName( $value ) {
 1631+ return $value; // @fixme -- use wfMsg()
 1632+ }
 1633+
 1634+ function updateProjectName( $notice, $projectName ) {
 1635+ $dbw = wfGetDB( DB_MASTER );
 1636+ $res = $dbw->update( 'cn_notices',
 1637+ array ( 'not_project' => $projectName ),
 1638+ array(
 1639+ 'not_name' => $notice
 1640+ )
 1641+ );
 1642+ }
 1643+
 1644+ function updateProjects( $notice, $newProjects ) {
 1645+ $dbw = wfGetDB( DB_MASTER );
 1646+ $dbw->begin();
 1647+
 1648+ // Get the previously assigned projects
 1649+ $oldProjects = $this->getNoticeProjects( $notice );
 1650+
 1651+ // Get the notice id
 1652+ $row = $dbw->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $notice ) );
 1653+
 1654+ // Add newly assigned projects
 1655+ $addProjects = array_diff( $newProjects, $oldProjects );
 1656+ $insertArray = array();
 1657+ foreach( $addProjects as $project ) {
 1658+ $insertArray[] = array( 'np_notice_id' => $row->not_id, 'np_project' => $project );
 1659+ }
 1660+ $res = $dbw->insert( 'cn_notice_projects', $insertArray, __METHOD__, array( 'IGNORE' ) );
 1661+
 1662+ // Remove disassociated projects
 1663+ $removeProjects = array_diff( $oldProjects, $newProjects );
 1664+ if ( $removeProjects ) {
 1665+ $res = $dbw->delete( 'cn_notice_projects',
 1666+ array( 'np_notice_id' => $row->not_id, 'np_project' => $removeProjects )
 1667+ );
 1668+ }
 1669+
 1670+ $dbw->commit();
 1671+ }
 1672+
 1673+ function updateProjectLanguages( $notice, $newLanguages ) {
 1674+ $dbw = wfGetDB( DB_MASTER );
 1675+ $dbw->begin();
 1676+
 1677+ // Get the previously assigned languages
 1678+ $oldLanguages = $this->getNoticeLanguages( $notice );
 1679+
 1680+ // Get the notice id
 1681+ $row = $dbw->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $notice ) );
 1682+
 1683+ // Add newly assigned languages
 1684+ $addLanguages = array_diff( $newLanguages, $oldLanguages );
 1685+ $insertArray = array();
 1686+ foreach( $addLanguages as $code ) {
 1687+ $insertArray[] = array( 'nl_notice_id' => $row->not_id, 'nl_language' => $code );
 1688+ }
 1689+ $res = $dbw->insert( 'cn_notice_languages', $insertArray, __METHOD__, array( 'IGNORE' ) );
 1690+
 1691+ // Remove disassociated languages
 1692+ $removeLanguages = array_diff( $oldLanguages, $newLanguages );
 1693+ if ( $removeLanguages ) {
 1694+ $res = $dbw->delete( 'cn_notice_languages',
 1695+ array( 'nl_notice_id' => $row->not_id, 'nl_language' => $removeLanguages )
 1696+ );
 1697+ }
 1698+
 1699+ $dbw->commit();
 1700+ }
 1701+
 1702+ function updateCountries( $notice, $newCountries ) {
 1703+ $dbw = wfGetDB( DB_MASTER );
 1704+
 1705+ // Get the previously assigned languages
 1706+ $oldCountries = $this->getNoticeCountries( $notice );
 1707+
 1708+ // Get the notice id
 1709+ $row = $dbw->selectRow( 'cn_notices', 'not_id', array( 'not_name' => $notice ) );
 1710+
 1711+ // Add newly assigned countries
 1712+ $addCountries = array_diff( $newCountries, $oldCountries );
 1713+ $insertArray = array();
 1714+ foreach( $addCountries as $code ) {
 1715+ $insertArray[] = array( 'nc_notice_id' => $row->not_id, 'nc_country' => $code );
 1716+ }
 1717+ $res = $dbw->insert( 'cn_notice_countries', $insertArray, __METHOD__, array( 'IGNORE' ) );
 1718+
 1719+ // Remove disassociated countries
 1720+ $removeCountries = array_diff( $oldCountries, $newCountries );
 1721+ if ( $removeCountries ) {
 1722+ $dbw->delete( 'cn_notice_countries',
 1723+ array( 'nc_notice_id' => $row->not_id, 'nc_country' => $removeCountries )
 1724+ );
 1725+ }
 1726+ }
 1727+
 1728+ public static function noticeExists( $noticeName ) {
 1729+ $dbr = wfGetDB( DB_SLAVE );
 1730+ $eNoticeName = htmlspecialchars( $noticeName );
 1731+ $row = $dbr->selectRow( 'cn_notices', 'not_name', array( 'not_name' => $eNoticeName ) );
 1732+ if ( $row ) {
 1733+ return true;
 1734+ } else {
 1735+ return false;
 1736+ }
 1737+ }
 1738+
 1739+ public static function dropDownList( $text, $values ) {
 1740+ $dropDown = "*{$text}\n";
 1741+ foreach ( $values as $value ) {
 1742+ $dropDown .= "**{$value}\n";
 1743+ }
 1744+ return $dropDown;
 1745+ }
 1746+
 1747+ function addZero( $text ) {
 1748+ // Prepend a 0 for text needing it
 1749+ if ( strlen( $text ) == 1 ) {
 1750+ $text = "0{$text}";
 1751+ }
 1752+ return $text;
 1753+ }
 1754+
 1755+ function showError( $message ) {
 1756+ global $wgOut;
 1757+ $wgOut->wrapWikiMsg( "<div class='cn-error'>\n$1\n</div>", $message );
 1758+ $this->centralNoticeError = true;
 1759+ }
 1760+
 1761+ /**
 1762+ * Generates a multiple select list of all countries.
 1763+ * @param $selected The country codes of the selected countries
 1764+ * @return multiple select list
 1765+ */
 1766+ function geoMultiSelector( $selected = array() ) {
 1767+ $countries = CentralNoticeDB::getCountriesList();
 1768+ $options = "\n";
 1769+ foreach( $countries as $code => $name ) {
 1770+ $options .= Xml::option(
 1771+ $name,
 1772+ $code,
 1773+ in_array( $code, $selected )
 1774+ ) . "\n";
 1775+ }
 1776+ $htmlOut = '';
 1777+ if ( $this->editable ) {
 1778+ $htmlOut .= Xml::tags( 'select',
 1779+ array(
 1780+ 'multiple' => 'multiple',
 1781+ 'size' => 5,
 1782+ 'id' => 'geo_countries[]',
 1783+ 'name' => 'geo_countries[]'
 1784+ ),
 1785+ $options
 1786+ );
 1787+ } else {
 1788+ $htmlOut .= Xml::tags( 'select',
 1789+ array(
 1790+ 'multiple' => 'multiple',
 1791+ 'size' => 5,
 1792+ 'id' => 'geo_countries[]',
 1793+ 'name' => 'geo_countries[]',
 1794+ 'disabled' => 'disabled'
 1795+ ),
 1796+ $options
 1797+ );
 1798+ }
 1799+ return $htmlOut;
 1800+ }
 1801+
 1802+ /**
 1803+ * Log any changes related to a campaign
 1804+ * @param $action string: 'created', 'modified', or 'removed'
 1805+ * @param $campaign integer: id of campaign
 1806+ * @param $beginSettings array of campaign settings before changes (optional)
 1807+ * @param $endSettings array of campaign settings after changes (optional)
 1808+ * @param $beginAssignments array of banner assignments before changes (optional)
 1809+ * @param $endAssignments array of banner assignments after changes (optional)
 1810+ */
 1811+ function logCampaignChange( $action, $campaign, $beginSettings = array(),
 1812+ $endSettings = array(), $beginAssignments = array(), $endAssignments = array() )
 1813+ {
 1814+ global $wgUser;
 1815+
 1816+ $dbw = wfGetDB( DB_MASTER );
 1817+
 1818+ $log = array(
 1819+ 'notlog_timestamp' => $dbw->timestamp(),
 1820+ 'notlog_user_id' => $wgUser->getId(),
 1821+ 'notlog_action' => $action,
 1822+ 'notlog_not_id' => $campaign
 1823+ );
 1824+
 1825+ $log = array_merge($log, $beginSettings, $endSettings);
 1826+
 1827+ $res = $dbw->insert( 'cn_notice_log', $log );
 1828+ $log_id = $dbw->insertId();
 1829+ return $log_id;
 1830+ }
 1831+}
 1832+
 1833+class CentralNoticePager extends TemplatePager {
 1834+ var $viewPage, $special;
 1835+ var $editable;
 1836+
 1837+ function __construct( $special ) {
 1838+ parent::__construct( $special );
 1839+ }
 1840+
 1841+ /**
 1842+ * Pull banners from the database
 1843+ */
 1844+ function getQueryInfo() {
 1845+ $notice = $this->mRequest->getVal( 'notice' );
 1846+ $noticeId = CentralNotice::getNoticeId( $notice );
 1847+ if ( $noticeId ) {
 1848+ // Return all the banners not already assigned to the current campaign
 1849+ return array(
 1850+ 'tables' => array( 'cn_assignments', 'cn_templates' ),
 1851+ 'fields' => array( 'cn_templates.tmp_name', 'cn_templates.tmp_id' ),
 1852+ 'conds' => array( 'cn_assignments.tmp_id IS NULL' ),
 1853+ 'join_conds' => array(
 1854+ 'cn_assignments' => array(
 1855+ 'LEFT JOIN',
 1856+ "cn_assignments.tmp_id = cn_templates.tmp_id " .
 1857+ "AND cn_assignments.not_id = $noticeId"
 1858+ )
 1859+ )
 1860+ );
 1861+ } else {
 1862+ // Return all the banners in the database
 1863+ return array(
 1864+ 'tables' => 'cn_templates',
 1865+ 'fields' => array( 'tmp_name', 'tmp_id' ),
 1866+ );
 1867+ }
 1868+ }
 1869+
 1870+ /**
 1871+ * Generate the content of each table row (1 row = 1 banner)
 1872+ */
 1873+ function formatRow( $row ) {
 1874+
 1875+ // Begin banner row
 1876+ $htmlOut = Xml::openElement( 'tr' );
 1877+
 1878+ if ( $this->editable ) {
 1879+ // Add box
 1880+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
 1881+ Xml::check( 'addTemplates[]', '', array ( 'value' => $row->tmp_name ) )
 1882+ );
 1883+ // Weight select
 1884+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
 1885+ Xml::listDropDown( "weight[$row->tmp_id]",
 1886+ CentralNotice::dropDownList(
 1887+ wfMsg( 'centralnotice-weight' ), range ( 0, 100, 5 )
 1888+ ) ,
 1889+ '',
 1890+ '25',
 1891+ '',
 1892+ '' )
 1893+ );
 1894+ }
 1895+
 1896+ // Link and Preview
 1897+ $render = new SpecialBannerLoader();
 1898+ $render->siteName = 'Wikipedia';
 1899+ $render->language = $this->mRequest->getVal( 'wpUserLanguage' );
 1900+ try {
 1901+ $preview = $render->getHtmlNotice( $row->tmp_name );
 1902+ } catch ( SpecialBannerLoaderException $e ) {
 1903+ $preview = wfMsg( 'centralnotice-nopreview' );
 1904+ }
 1905+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
 1906+ $this->getSkin()->makeLinkObj( $this->viewPage,
 1907+ htmlspecialchars( $row->tmp_name ),
 1908+ 'template=' . urlencode( $row->tmp_name ) ) .
 1909+ Xml::fieldset( wfMsg( 'centralnotice-preview' ),
 1910+ $preview,
 1911+ array( 'class' => 'cn-bannerpreview')
 1912+ )
 1913+ );
 1914+
 1915+ // End banner row
 1916+ $htmlOut .= Xml::closeElement( 'tr' );
 1917+
 1918+ return $htmlOut;
 1919+ }
 1920+
 1921+ /**
 1922+ * Specify table headers
 1923+ */
 1924+ function getStartBody() {
 1925+ $htmlOut = '';
 1926+ $htmlOut .= Xml::openElement( 'table', array( 'cellpadding' => 9 ) );
 1927+ $htmlOut .= Xml::openElement( 'tr' );
 1928+ if ( $this->editable ) {
 1929+ $htmlOut .= Xml::element( 'th', array( 'align' => 'left', 'width' => '5%' ),
 1930+ wfMsg ( "centralnotice-add" )
 1931+ );
 1932+ $htmlOut .= Xml::element( 'th', array( 'align' => 'left', 'width' => '5%' ),
 1933+ wfMsg ( "centralnotice-weight" )
 1934+ );
 1935+ }
 1936+ $htmlOut .= Xml::element( 'th', array( 'align' => 'left' ),
 1937+ wfMsg ( 'centralnotice-templates' )
 1938+ );
 1939+ $htmlOut .= Xml::closeElement( 'tr' );
 1940+ return $htmlOut;
 1941+ }
 1942+
 1943+ /**
 1944+ * Close table
 1945+ */
 1946+ function getEndBody() {
 1947+ $htmlOut = '';
 1948+ $htmlOut .= Xml::closeElement( 'table' );
 1949+ return $htmlOut;
 1950+ }
 1951+}
Property changes on: trunk/extensions/CentralNotice/special/SpecialCentralNotice.php
___________________________________________________________________
Added: svn:eol-style
11952 + native
Index: trunk/extensions/CentralNotice/special/SpecialBannerAllocation.php
@@ -0,0 +1,201 @@
 2+<?php
 3+
 4+if ( !defined( 'MEDIAWIKI' ) ) {
 5+ echo "CentralNotice extension\n";
 6+ exit( 1 );
 7+}
 8+
 9+class SpecialBannerAllocation extends UnlistedSpecialPage {
 10+ public $project = 'wikipedia';
 11+ public $language = 'en';
 12+ public $location = 'US';
 13+
 14+ function __construct() {
 15+ // Register special page
 16+ parent::__construct( "BannerAllocation" );
 17+ }
 18+
 19+ /**
 20+ * Handle different types of page requests
 21+ */
 22+ function execute( $sub ) {
 23+ global $wgOut, $wgRequest, $wgExtensionAssetsPath, $wgNoticeProjects, $wgLanguageCode;
 24+
 25+ if ( $wgRequest->wasPosted() ) {
 26+ $this->project = $wgRequest->getText( 'project', 'wikipedia' );
 27+ $this->language = $wgRequest->getText( 'language', 'en' );
 28+ $this->location = $wgRequest->getText( 'country', 'US' );
 29+ }
 30+
 31+ // Begin output
 32+ $this->setHeaders();
 33+
 34+ // Add style file to the output headers
 35+ $wgOut->addExtensionStyle( "$wgExtensionAssetsPath/CentralNotice/centralnotice.css" );
 36+
 37+ // Add script file to the output headers
 38+ $wgOut->addScriptFile( "$wgExtensionAssetsPath/CentralNotice/centralnotice.js" );
 39+
 40+ // Initialize error variable
 41+ $this->centralNoticeError = false;
 42+
 43+ // Show summary
 44+ $wgOut->addWikiMsg( 'centralnotice-summary' );
 45+
 46+ // Show header
 47+ CentralNotice::printHeader();
 48+
 49+ // Begin Banners tab content
 50+ $wgOut->addHTML( Xml::openElement( 'div', array( 'id' => 'preferences' ) ) );
 51+
 52+ $htmlOut = '';
 53+
 54+ // Begin Allocation selection fieldset
 55+ $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
 56+
 57+ $htmlOut .= Xml::openElement( 'form', array( 'method' => 'post' ) );
 58+ $htmlOut .= Xml::element( 'h2', null, wfMsg( 'centralnotice-view-allocation' ) );
 59+ $htmlOut .= Xml::tags( 'p', null, wfMsg( 'centralnotice-allocation-instructions' ) );
 60+
 61+ $htmlOut .= Xml::openElement( 'table', array ( 'id' => 'envpicker', 'cellpadding' => 7 ) );
 62+ $htmlOut .= Xml::openElement( 'tr' );
 63+ $htmlOut .= Xml::tags( 'td',
 64+ array( 'style' => 'width: 20%;' ),
 65+ wfMsg( 'centralnotice-project-name' ) );
 66+ $htmlOut .= Xml::openElement( 'td' );
 67+ $htmlOut .= Xml::openElement( 'select', array( 'name' => 'project' ) );
 68+ foreach ( $wgNoticeProjects as $value ) {
 69+ $htmlOut .= Xml::option( $value, $value, $value === $this->project );
 70+ }
 71+ $htmlOut .= Xml::closeElement( 'select' );
 72+ $htmlOut .= Xml::closeElement( 'td' );
 73+ $htmlOut .= Xml::closeElement( 'tr' );
 74+ $htmlOut .= Xml::openElement( 'tr' );
 75+ $htmlOut .= Xml::tags( 'td',
 76+ array( 'valign' => 'top' ),
 77+ wfMsg( 'centralnotice-project-lang' ) );
 78+ $htmlOut .= Xml::openElement( 'td' );
 79+ // Make sure the site language is in the list; a custom language code
 80+ // might not have a defined name...
 81+ $languages = Language::getLanguageNames( true );
 82+ if( !array_key_exists( $wgLanguageCode, $languages ) ) {
 83+ $languages[$wgLanguageCode] = $wgLanguageCode;
 84+ }
 85+ ksort( $languages );
 86+ $htmlOut .= Xml::openElement( 'select', array( 'name' => 'language' ) );
 87+ foreach( $languages as $code => $name ) {
 88+ $htmlOut .= Xml::option(
 89+ wfMsg( 'centralnotice-language-listing', $code, $name ),
 90+ $code, $code === $this->language );
 91+ }
 92+ $htmlOut .= Xml::closeElement( 'select' );
 93+ $htmlOut .= Xml::closeElement( 'td' );
 94+ $htmlOut .= Xml::closeElement( 'tr' );
 95+ $htmlOut .= Xml::openElement( 'tr' );
 96+ $htmlOut .= Xml::tags( 'td', array(), wfMsg( 'centralnotice-country' ) );
 97+ $htmlOut .= Xml::openElement( 'td' );
 98+ $countries = CentralNoticeDB::getCountriesList();
 99+ $htmlOut .= Xml::openElement( 'select', array( 'name' => 'country' ) );
 100+ foreach( $countries as $code => $name ) {
 101+ $htmlOut .= Xml::option( $name, $code, $code === $this->location );
 102+ }
 103+ $htmlOut .= Xml::closeElement( 'select' );
 104+ $htmlOut .= Xml::closeElement( 'td' );
 105+ $htmlOut .= Xml::closeElement( 'tr' );
 106+ $htmlOut .= Xml::closeElement( 'table' );
 107+
 108+ $htmlOut .= Xml::tags( 'div',
 109+ array( 'class' => 'cn-buttons' ),
 110+ Xml::submitButton( wfMsg( 'centralnotice-modify' ) )
 111+ );
 112+ $htmlOut .= Xml::closeElement( 'form' );
 113+
 114+ // End Allocation selection fieldset
 115+ $htmlOut .= Xml::closeElement( 'fieldset' );
 116+
 117+ $wgOut->addHTML( $htmlOut );
 118+
 119+ // Handle form submissions
 120+ if ( $wgRequest->wasPosted() ) {
 121+ $this->showList();
 122+ }
 123+
 124+ // End Banners tab content
 125+ $wgOut->addHTML( Xml::closeElement( 'div' ) );
 126+ }
 127+
 128+ /**
 129+ * Show a list of banners with allocation. Newer banners are shown first.
 130+ */
 131+ function showList() {
 132+ global $wgOut, $wgUser, $wgRequest, $wgLang;
 133+
 134+ $sk = $wgUser->getSkin();
 135+ $viewBanner = $this->getTitleFor( 'NoticeTemplate', 'view' );
 136+ $viewCampaign = $this->getTitleFor( 'CentralNotice' );
 137+
 138+ // Begin building HTML
 139+ $htmlOut = '';
 140+
 141+ // Begin Allocation list fieldset
 142+ $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
 143+
 144+ $bannerLister = new SpecialBannerListLoader();
 145+ $bannerLister->project = $wgRequest->getVal( 'project' );
 146+ $bannerLister->language = $wgRequest->getVal( 'language' );
 147+ $bannerLister->location = $wgRequest->getVal( 'country' );
 148+
 149+ $htmlOut .= Xml::tags( 'p', null,
 150+ wfMsg (
 151+ 'centralnotice-allocation-description',
 152+ htmlspecialchars( $bannerLister->language ),
 153+ htmlspecialchars( $bannerLister->project ),
 154+ htmlspecialchars( $bannerLister->location )
 155+ )
 156+ );
 157+
 158+ $bannerList = $bannerLister->getJsonList();
 159+ $banners = FormatJson::decode( $bannerList, true );
 160+ $totalWeight = 0;
 161+ foreach ( $banners as $banner ) {
 162+ $totalWeight += $banner['weight'];
 163+ }
 164+ if ( $banners ) {
 165+ $htmlOut .= Xml::openElement( 'table',
 166+ array ( 'cellpadding' => 9, 'class' => 'wikitable sortable' ) );
 167+ $htmlOut .= Xml::openElement( 'tr' );
 168+ $htmlOut .= Xml::element( 'th', array( 'width' => '20%' ),
 169+ wfMsg ( 'centralnotice-percentage' ) );
 170+ $htmlOut .= Xml::element( 'th', array( 'width' => '30%' ),
 171+ wfMsg ( 'centralnotice-banner' ) );
 172+ $htmlOut .= Xml::element( 'th', array( 'width' => '30%' ),
 173+ wfMsg ( 'centralnotice-notice' ) );
 174+ $htmlOut .= Xml::closeElement( 'tr' );
 175+ foreach ( $banners as $banner ) {
 176+ $htmlOut .= Xml::openElement( 'tr' );
 177+ $htmlOut .= Xml::openElement( 'td' );
 178+ $percentage = ( $banner['weight'] / $totalWeight ) * 100;
 179+ $htmlOut .= wfMsg ( 'percent', $wgLang->formatNum( $percentage ) );
 180+ $htmlOut .= Xml::closeElement( 'td' );
 181+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
 182+ $sk->makeLinkObj( $viewBanner, htmlspecialchars( $banner['name'] ),
 183+ 'template=' . urlencode( $banner['name'] ) )
 184+ );
 185+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
 186+ $sk->makeLinkObj( $viewCampaign, htmlspecialchars( $banner['campaign'] ),
 187+ 'method=listNoticeDetail&notice=' . urlencode( $banner['campaign'] ) )
 188+ );
 189+ $htmlOut .= Xml::closeElement( 'tr' );
 190+ }
 191+ $htmlOut .= Xml::closeElement( 'table' );
 192+ } else {
 193+ $htmlOut .= Xml::tags( 'p', null, wfMsg ( 'centralnotice-no-allocation' ) );
 194+ }
 195+
 196+ // End Allocation list fieldset
 197+ $htmlOut .= Xml::closeElement( 'fieldset' );
 198+
 199+ $wgOut->addHTML( $htmlOut );
 200+ }
 201+
 202+}
Property changes on: trunk/extensions/CentralNotice/special/SpecialBannerAllocation.php
___________________________________________________________________
Added: svn:eol-style
1203 + native
Index: trunk/extensions/CentralNotice/special/SpecialBannerLoader.php
@@ -0,0 +1,221 @@
 2+<?php
 3+
 4+/**
 5+ * Generates banner HTML files
 6+ */
 7+class SpecialBannerLoader extends UnlistedSpecialPage {
 8+ public $siteName = 'Wikipedia'; // Site name
 9+ public $language = 'en'; // User language
 10+ protected $sharedMaxAge = 600; // Cache for 10 minutes on the server side
 11+ protected $maxAge = 0; // No client-side banner caching so we get all impressions
 12+
 13+ function __construct() {
 14+ // Register special page
 15+ parent::__construct( "BannerLoader" );
 16+ }
 17+
 18+ function execute( $par ) {
 19+ global $wgOut, $wgRequest;
 20+
 21+ $wgOut->disable();
 22+ $this->sendHeaders();
 23+
 24+ // Get values from the query string
 25+ $this->language = $wgRequest->getText( 'userlang', 'en' );
 26+ $this->siteName = $wgRequest->getText( 'sitename', 'Wikipedia' );
 27+ $this->campaign = $wgRequest->getText( 'campaign', 'unknown' );
 28+ $this->fundraising = $wgRequest->getBool( 'fundraising', false );
 29+ $this->landingPages = $wgRequest->getText( 'landingpages' );
 30+
 31+ if ( $wgRequest->getText( 'banner' ) ) {
 32+ $bannerName = $wgRequest->getText( 'banner' );
 33+ try {
 34+ $content = $this->getJsNotice( $bannerName );
 35+ if ( preg_match( "/&lt;centralnotice-template-\w+&gt;\z/", $content ) ) {
 36+ echo "/* Failed cache lookup */";
 37+ } elseif ( strlen( $content ) == 0 ) {
 38+ // Hack for IE/Mac 0-length keepalive problem, see RawPage.php
 39+ echo "/* Empty */";
 40+ } else {
 41+ echo $content;
 42+ }
 43+ } catch (SpecialBannerLoaderException $e) {
 44+ echo "/* Banner could not be generated */";
 45+ }
 46+ } else {
 47+ echo "/* No banner specified */";
 48+ }
 49+ }
 50+
 51+ /**
 52+ * Generate the HTTP response headers for the banner file
 53+ */
 54+ function sendHeaders() {
 55+ global $wgJsMimeType;
 56+ header( "Content-type: $wgJsMimeType; charset=utf-8" );
 57+ header( "Cache-Control: public, s-maxage=$this->sharedMaxAge, max-age=$this->maxAge" );
 58+ }
 59+
 60+ /**
 61+ * Generate the JS for the requested banner
 62+ * @return a string of Javascript containing a call to insertBanner()
 63+ * with JSON containing the banner content as the parameter
 64+ * @throws SpecialBannerLoaderException
 65+ */
 66+ function getJsNotice( $bannerName ) {
 67+ // Make sure the banner exists
 68+ if ( CentralNoticeDB::bannerExists( $bannerName ) ) {
 69+ $this->bannerName = $bannerName;
 70+ $bannerHtml = '';
 71+ $bannerHtml .= preg_replace_callback(
 72+ '/{{{(.*?)}}}/',
 73+ array( $this, 'getNoticeField' ),
 74+ $this->getNoticeTemplate()
 75+ );
 76+ $bannerArray = array(
 77+ 'bannerName' => $bannerName,
 78+ 'bannerHtml' => $bannerHtml,
 79+ 'campaign' => $this->campaign,
 80+ 'fundraising' => $this->fundraising,
 81+ 'landingPages' => $this->landingPages
 82+ );
 83+ $bannerJs = 'insertBanner('.FormatJson::encode( $bannerArray ).');';
 84+ return $bannerJs;
 85+ }
 86+ }
 87+
 88+ /**
 89+ * Generate the HTML for the requested banner
 90+ * @throws SpecialBannerLoaderException
 91+ */
 92+ function getHtmlNotice( $bannerName ) {
 93+ // Make sure the banner exists
 94+ if ( CentralNoticeDB::bannerExists( $bannerName ) ) {
 95+ $this->bannerName = $bannerName;
 96+ $bannerHtml = '';
 97+ $bannerHtml .= preg_replace_callback(
 98+ '/{{{(.*?)}}}/',
 99+ array( $this, 'getNoticeField' ),
 100+ $this->getNoticeTemplate()
 101+ );
 102+ return $bannerHtml;
 103+ }
 104+ }
 105+
 106+ /**
 107+ * Get the body of the banner with only {{int:...}} messages translated
 108+ */
 109+ function getNoticeTemplate() {
 110+ $out = $this->getMessage( "centralnotice-template-{$this->bannerName}" );
 111+ return $out;
 112+ }
 113+
 114+ /**
 115+ * Extract a message name and send to getMessage() for translation
 116+ * @param $match A message array with 2 members: raw match, short name of message
 117+ * @return translated messsage string
 118+ * @throws SpecialBannerLoaderException
 119+ */
 120+ function getNoticeField( $match ) {
 121+ $field = $match[1];
 122+ $params = array();
 123+ if ( $field == 'amount' ) {
 124+ $params = array( $this->toMillions( $this->getDonationAmount() ) );
 125+ }
 126+ $message = "centralnotice-{$this->bannerName}-$field";
 127+ $source = $this->getMessage( $message, $params );
 128+ return $source;
 129+ }
 130+
 131+ /**
 132+ * Convert number of dollars to millions of dollars
 133+ */
 134+ private function toMillions( $num ) {
 135+ $num = sprintf( "%.1f", $num / 1e6 );
 136+ if ( substr( $num, - 2 ) == '.0' ) {
 137+ $num = substr( $num, 0, - 2 );
 138+ }
 139+ $lang = Language::factory( $this->language );
 140+ return $lang->formatNum( $num );
 141+ }
 142+
 143+ /**
 144+ * Retrieve a translated message
 145+ * @param $msg The full name of the message
 146+ * @return translated messsage string
 147+ */
 148+ private function getMessage( $msg, $params = array() ) {
 149+ global $wgLang, $wgSitename;
 150+
 151+ // A god-damned dirty hack! :D
 152+ $oldLang = $wgLang;
 153+ $oldSitename = $wgSitename;
 154+
 155+ $wgSitename = $this->siteName; // hack for {{SITENAME}}
 156+ $wgLang = Language::factory( $this->language ); // hack for {{int:...}}
 157+
 158+ $options = array( 'language' => $this->language, 'parsemag' );
 159+ array_unshift( $params, $options );
 160+ array_unshift( $params, $msg );
 161+ $out = call_user_func_array( 'wfMsgExt', $params );
 162+
 163+ // Restore global variables
 164+ $wgLang = $oldLang;
 165+ $wgSitename = $oldSitename;
 166+
 167+ return $out;
 168+ }
 169+
 170+ /**
 171+ * Pull the current amount raised during a fundraiser
 172+ * @throws SpecialBannerLoaderException
 173+ */
 174+ private function getDonationAmount() {
 175+ global $wgNoticeCounterSource, $wgMemc;
 176+ // Pull short-cached amount
 177+ $count = intval( $wgMemc->get( wfMemcKey( 'centralnotice', 'counter' ) ) );
 178+ if ( !$count ) {
 179+ // Pull from dynamic counter
 180+ $counter_value = Http::get( $wgNoticeCounterSource );
 181+ if( !$counter_value ) {
 182+ throw new RemoteServerProblemException();
 183+ }
 184+ $count = intval( $counter_value );
 185+ if ( !$count ) {
 186+ // Pull long-cached amount
 187+ $count = intval( $wgMemc->get(
 188+ wfMemcKey( 'centralnotice', 'counter', 'fallback' ) ) );
 189+ if ( !$count ) {
 190+ throw new DonationAmountUnknownException();
 191+ }
 192+ }
 193+ // Expire in 60 seconds
 194+ $wgMemc->set( wfMemcKey( 'centralnotice', 'counter' ), $count, 60 );
 195+ // No expiration
 196+ $wgMemc->set( wfMemcKey( 'centralnotice', 'counter', 'fallback' ), $count );
 197+ }
 198+ return $count;
 199+ }
 200+}
 201+/**
 202+ * @defgroup Exception Exception
 203+ */
 204+
 205+/**
 206+ * SpecialBannerLoaderException exception
 207+ *
 208+ * This exception is being thrown whenever
 209+ * some fatal error occurs that may affect
 210+ * how the banner is presented.
 211+ *
 212+ * @ingroup Exception
 213+ */
 214+
 215+class SpecialBannerLoaderException extends Exception {
 216+}
 217+
 218+class RemoteServerProblemException extends SpecialBannerLoaderException {
 219+}
 220+
 221+class DonationAmountUnknownException extends SpecialBannerLoaderException {
 222+}
Property changes on: trunk/extensions/CentralNotice/special/SpecialBannerLoader.php
___________________________________________________________________
Added: svn:eol-style
1223 + native
Index: trunk/extensions/CentralNotice/special/SpecialNoticeTemplate.php
@@ -0,0 +1,964 @@
 2+<?php
 3+
 4+if ( !defined( 'MEDIAWIKI' ) ) {
 5+ echo "CentralNotice extension\n";
 6+ exit( 1 );
 7+}
 8+
 9+class SpecialNoticeTemplate extends UnlistedSpecialPage {
 10+ var $editable, $centralNoticeError;
 11+
 12+ function __construct() {
 13+ // Register special page
 14+ parent::__construct( 'NoticeTemplate' );
 15+ }
 16+
 17+ /**
 18+ * Handle different types of page requests
 19+ */
 20+ function execute( $sub ) {
 21+ global $wgOut, $wgUser, $wgRequest, $wgExtensionAssetsPath;
 22+
 23+ // Begin output
 24+ $this->setHeaders();
 25+
 26+ // Add style file to the output headers
 27+ $wgOut->addExtensionStyle( "$wgExtensionAssetsPath/CentralNotice/centralnotice.css" );
 28+
 29+ // Add localized script error messages
 30+ $scriptVars = array(
 31+ 'documentWriteError' => wfMsg( 'centralnotice-documentwrite-error' )
 32+ );
 33+ $wgOut->addScript( Skin::makeVariablesScript( $scriptVars ) );
 34+
 35+ // Add script file to the output headers
 36+ $wgOut->addScriptFile( "$wgExtensionAssetsPath/CentralNotice/centralnotice.js" );
 37+
 38+ // Check permissions
 39+ $this->editable = $wgUser->isAllowed( 'centralnotice-admin' );
 40+
 41+ // Initialize error variable
 42+ $this->centralNoticeError = false;
 43+
 44+ // Show summary
 45+ $wgOut->addWikiMsg( 'centralnotice-summary' );
 46+
 47+ // Show header
 48+ CentralNotice::printHeader();
 49+
 50+ // Begin Banners tab content
 51+ $wgOut->addHTML( Xml::openElement( 'div', array( 'id' => 'preferences' ) ) );
 52+
 53+ $method = $wgRequest->getVal( 'wpMethod' );
 54+
 55+ // Handle form submissions
 56+ if ( $this->editable && $wgRequest->wasPosted() ) {
 57+
 58+ // Check authentication token
 59+ if ( $wgUser->matchEditToken( $wgRequest->getVal( 'authtoken' ) ) ) {
 60+
 61+ // Handle removing banners
 62+ $toRemove = $wgRequest->getArray( 'removeTemplates' );
 63+ if ( isset( $toRemove ) ) {
 64+ // Remove banners in list
 65+ foreach ( $toRemove as $template ) {
 66+ $this->removeTemplate( $template );
 67+ }
 68+ }
 69+
 70+ // Handle translation message update
 71+ $update = $wgRequest->getArray( 'updateText' );
 72+ if ( isset ( $update ) ) {
 73+ foreach ( $update as $lang => $messages ) {
 74+ foreach ( $messages as $text => $translation ) {
 75+ // If we actually have text
 76+ if ( $translation ) {
 77+ $this->updateMessage( $text, $translation, $lang );
 78+ }
 79+ }
 80+ }
 81+ }
 82+
 83+ // Handle adding banner
 84+ if ( $method == 'addTemplate' ) {
 85+ $newTemplateName = $wgRequest->getText( 'templateName' );
 86+ $newTemplateBody = $wgRequest->getText( 'templateBody' );
 87+ if ( $newTemplateName != '' && $newTemplateBody != '' ) {
 88+ $this->addTemplate(
 89+ $newTemplateName,
 90+ $newTemplateBody,
 91+ $wgRequest->getBool( 'displayAnon' ),
 92+ $wgRequest->getBool( 'displayAccount' ),
 93+ $wgRequest->getBool( 'fundraising' ),
 94+ $wgRequest->getVal( 'landingPages' )
 95+ );
 96+ $sub = 'view';
 97+ } else {
 98+ $this->showError( 'centralnotice-null-string' );
 99+ }
 100+ }
 101+
 102+ // Handle editing banner
 103+ if ( $method == 'editTemplate' ) {
 104+ $this->editTemplate(
 105+ $wgRequest->getText( 'template' ),
 106+ $wgRequest->getText( 'templateBody' ),
 107+ $wgRequest->getBool( 'displayAnon' ),
 108+ $wgRequest->getBool( 'displayAccount' ),
 109+ $wgRequest->getBool( 'fundraising' ),
 110+ $wgRequest->getVal( 'landingPages' )
 111+ );
 112+ $sub = 'view';
 113+ }
 114+
 115+ } else {
 116+ $this->showError( 'sessionfailure' );
 117+ }
 118+
 119+ }
 120+
 121+ // Handle viewing of a banner in all languages
 122+ if ( $sub == 'view' && $wgRequest->getVal( 'wpUserLanguage' ) == 'all' ) {
 123+ $template = $wgRequest->getVal( 'template' );
 124+ $this->showViewAvailable( $template );
 125+ $wgOut->addHTML( Xml::closeElement( 'div' ) );
 126+ return;
 127+ }
 128+
 129+ // Handle viewing a specific banner
 130+ if ( $sub == 'view' && $wgRequest->getText( 'template' ) != '' ) {
 131+ $this->showView();
 132+ $wgOut->addHTML( Xml::closeElement( 'div' ) );
 133+ return;
 134+ }
 135+
 136+ if ( $this->editable ) {
 137+ // Handle showing "Add a banner" interface
 138+ if ( $sub == 'add' ) {
 139+ $this->showAdd();
 140+ $wgOut->addHTML( Xml::closeElement( 'div' ) );
 141+ return;
 142+ }
 143+
 144+ // Handle cloning a specific banner
 145+ if ( $sub == 'clone' ) {
 146+
 147+ // Check authentication token
 148+ if ( $wgUser->matchEditToken( $wgRequest->getVal( 'authtoken' ) ) ) {
 149+
 150+ $oldTemplate = $wgRequest->getVal( 'oldTemplate' );
 151+ $newTemplate = $wgRequest->getVal( 'newTemplate' );
 152+ // We use the returned name in case any special characters had to be removed
 153+ $template = $this->cloneTemplate( $oldTemplate, $newTemplate );
 154+ $wgOut->redirect(
 155+ $this->getTitle( 'view' )->getLocalUrl( "template=$template" ) );
 156+ return;
 157+
 158+ } else {
 159+ $this->showError( 'sessionfailure' );
 160+ }
 161+
 162+ }
 163+
 164+ }
 165+
 166+ // Show list of banners by default
 167+ $this->showList();
 168+
 169+ // End Banners tab content
 170+ $wgOut->addHTML( Xml::closeElement( 'div' ) );
 171+ }
 172+
 173+ /**
 174+ * Show a list of available banners. Newer banners are shown first.
 175+ */
 176+ function showList() {
 177+ global $wgOut, $wgUser;
 178+
 179+ $sk = $wgUser->getSkin();
 180+ $pager = new TemplatePager( $this );
 181+
 182+ // Begin building HTML
 183+ $htmlOut = '';
 184+
 185+ // Begin Manage Banners fieldset
 186+ $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
 187+
 188+ if ( !$pager->getNumRows() ) {
 189+ $htmlOut .= Xml::element( 'p', null, wfMsg( 'centralnotice-no-templates' ) );
 190+ } else {
 191+ if ( $this->editable ) {
 192+ $htmlOut .= Xml::openElement( 'form', array( 'method' => 'post' ) );
 193+ }
 194+ $htmlOut .= Xml::element( 'h2', null, wfMsg( 'centralnotice-manage-templates' ) );
 195+
 196+ // Show paginated list of banners
 197+ $htmlOut .= Xml::tags( 'div', array( 'class' => 'cn-pager' ),
 198+ $pager->getNavigationBar() );
 199+ $htmlOut .= $pager->getBody();
 200+ $htmlOut .= Xml::tags( 'div', array( 'class' => 'cn-pager' ),
 201+ $pager->getNavigationBar() );
 202+
 203+ if ( $this->editable ) {
 204+ $htmlOut .= Xml::closeElement( 'form' );
 205+ }
 206+ }
 207+
 208+ if ( $this->editable ) {
 209+ $htmlOut .= Xml::element( 'p' );
 210+ $newPage = $this->getTitle( 'add' );
 211+ $htmlOut .= $sk->makeLinkObj( $newPage, wfMsgHtml( 'centralnotice-add-template' ) );
 212+ }
 213+
 214+ // End Manage Banners fieldset
 215+ $htmlOut .= Xml::closeElement( 'fieldset' );
 216+
 217+ $wgOut->addHTML( $htmlOut );
 218+ }
 219+
 220+ /**
 221+ * Show "Add a banner" interface
 222+ */
 223+ function showAdd() {
 224+ global $wgOut, $wgUser, $wgExtensionAssetsPath, $wgLang, $wgRequest,
 225+ $wgNoticeEnableFundraising;
 226+ $scriptPath = "$wgExtensionAssetsPath/CentralNotice";
 227+
 228+ // Build HTML
 229+ $htmlOut = '';
 230+ $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
 231+ $htmlOut .= Xml::openElement( 'form',
 232+ array( 'method' => 'post', 'onsubmit' => 'return validateBannerForm(this)' ) );
 233+ $htmlOut .= Xml::element( 'h2', null, wfMsg( 'centralnotice-add-template' ) );
 234+ $htmlOut .= Html::hidden( 'wpMethod', 'addTemplate' );
 235+
 236+ // If there was an error, we'll need to restore the state of the form
 237+ if ( $wgRequest->wasPosted() ) {
 238+ $templateName = $wgRequest->getVal( 'templateName' );
 239+ $displayAnon = $wgRequest->getCheck( 'displayAnon' );
 240+ $displayAccount = $wgRequest->getCheck( 'displayAccount' );
 241+ $fundraising = $wgRequest->getCheck( 'fundraising' );
 242+ $landingPages = $wgRequest->getVal( 'landingPages' );
 243+ $body = $wgRequest->getVal( 'templateBody' );
 244+ } else { // Use default values
 245+ $templateName = '';
 246+ $displayAnon = true;
 247+ $displayAccount = true;
 248+ $fundraising = false;
 249+ $landingPages = '';
 250+ $body = '';
 251+ }
 252+
 253+ $htmlOut .= Xml::tags( 'p', null,
 254+ Xml::inputLabel(
 255+ wfMsg( 'centralnotice-banner-name' ),
 256+ 'templateName', 'templateName', 25, $templateName
 257+ )
 258+ );
 259+
 260+ // Display settings
 261+ $htmlOut .= Xml::openElement( 'p', null );
 262+ $htmlOut .= wfMsg( 'centralnotice-banner-display' );
 263+ $htmlOut .= Xml::check( 'displayAnon', $displayAnon, array( 'id' => 'displayAnon' ) );
 264+ $htmlOut .= Xml::label( wfMsg( 'centralnotice-banner-anonymous' ), 'displayAnon' );
 265+ $htmlOut .= Xml::check( 'displayAccount', $displayAccount,
 266+ array( 'id' => 'displayAccount' ) );
 267+ $htmlOut .= Xml::label( wfMsg( 'centralnotice-banner-logged-in' ), 'displayAccount' );
 268+ $htmlOut .= Xml::closeElement( 'p' );
 269+
 270+ // Fundraising settings
 271+ if ( $wgNoticeEnableFundraising ) {
 272+ $htmlOut .= Xml::openElement( 'p', null );
 273+ $htmlOut .= Xml::check( 'fundraising', $fundraising, array( 'id' => 'fundraising' ) );
 274+ $htmlOut .= Xml::label( wfMsg( 'centralnotice-banner-fundraising' ), 'fundraising' );
 275+ $htmlOut .= Xml::closeElement( 'p' );
 276+ $htmlOut .= Xml::openElement( 'div',
 277+ array( 'id' => 'fundraisingInterface', 'style' => 'display: none;' ) );
 278+ $htmlOut .= Xml::tags( 'p', array(), wfMsg( 'centralnotice-banner-fundraising-help' ) );
 279+ $htmlOut .= Xml::tags( 'p', array(),
 280+ Xml::inputLabel(
 281+ wfMsg( 'centralnotice-banner-landing-pages' ),
 282+ 'landingPages', 'landingPages', 40, $landingPages,
 283+ array( 'maxlength' => 255 )
 284+ )
 285+ );
 286+ $htmlOut .= Xml::closeElement( 'div' );
 287+ }
 288+
 289+ // Begin banner body section
 290+ $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-banner' ) );
 291+ $htmlOut .= wfMsg( 'centralnotice-edit-template-summary' );
 292+ $buttons = array();
 293+ $buttons[] = '<a href="#" onclick="insertButton(\'close\');return false;">' .
 294+ wfMsg( 'centralnotice-close-button' ) . '</a>';
 295+ $htmlOut .= Xml::tags( 'div',
 296+ array( 'style' => 'margin-bottom: 0.2em;' ),
 297+ '<img src="'.$scriptPath.'/down-arrow.png" style="vertical-align:baseline;"/>' .
 298+ wfMsg( 'centralnotice-insert', $wgLang->commaList( $buttons ) )
 299+ );
 300+
 301+ $htmlOut .= Xml::textarea( 'templateBody', $body, 60, 20 );
 302+ $htmlOut .= Xml::closeElement( 'fieldset' );
 303+ $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
 304+
 305+ // Submit button
 306+ $htmlOut .= Xml::tags( 'div',
 307+ array( 'class' => 'cn-buttons' ),
 308+ Xml::submitButton( wfMsg( 'centralnotice-save-banner' ) )
 309+ );
 310+
 311+ $htmlOut .= Xml::closeElement( 'form' );
 312+ $htmlOut .= Xml::closeElement( 'fieldset' );
 313+
 314+ // Output HTML
 315+ $wgOut->addHTML( $htmlOut );
 316+ }
 317+
 318+ /**
 319+ * View or edit an individual banner
 320+ */
 321+ private function showView() {
 322+ global $wgOut, $wgUser, $wgRequest, $wgLanguageCode, $wgExtensionAssetsPath, $wgLang,
 323+ $wgNoticeEnableFundraising;
 324+
 325+ $scriptPath = "$wgExtensionAssetsPath/CentralNotice";
 326+ $sk = $wgUser->getSkin();
 327+
 328+ if ( $this->editable ) {
 329+ $readonly = array();
 330+ $disabled = array();
 331+ } else {
 332+ $readonly = array( 'readonly' => 'readonly' );
 333+ $disabled = array( 'disabled' => 'disabled' );
 334+ }
 335+
 336+ // Get user's language
 337+ $wpUserLang = $wgRequest->getVal( 'wpUserLanguage', $wgLanguageCode );
 338+
 339+ // Get current banner
 340+ $currentTemplate = $wgRequest->getText( 'template' );
 341+
 342+ // Pull banner settings from database
 343+ $dbr = wfGetDB( DB_SLAVE );
 344+ $row = $dbr->selectRow( 'cn_templates',
 345+ array(
 346+ 'tmp_display_anon',
 347+ 'tmp_display_account',
 348+ 'tmp_fundraising',
 349+ 'tmp_landing_pages'
 350+ ),
 351+ array( 'tmp_name' => $currentTemplate ),
 352+ __METHOD__
 353+ );
 354+
 355+ if ( !$row ) {
 356+ $this->showError( 'centralnotice-banner-doesnt-exist' );
 357+ return;
 358+ } else {
 359+ // Begin building HTML
 360+ $htmlOut = '';
 361+
 362+ // Begin View Banner fieldset
 363+ $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
 364+
 365+ $htmlOut .= Xml::element( 'h2', null,
 366+ wfMsg( 'centralnotice-banner-heading', $currentTemplate ) );
 367+
 368+ // Show preview of banner
 369+ $render = new SpecialBannerLoader();
 370+ $render->siteName = 'Wikipedia';
 371+ $render->language = $wpUserLang;
 372+ try {
 373+ $preview = $render->getHtmlNotice( $wgRequest->getText( 'template' ) );
 374+ } catch ( SpecialBannerLoaderException $e ) {
 375+ $preview = wfMsg( 'centralnotice-nopreview' );
 376+ }
 377+ if ( $render->language != '' ) {
 378+ $htmlOut .= Xml::fieldset(
 379+ wfMsg( 'centralnotice-preview' ) . " ($render->language)",
 380+ $preview
 381+ );
 382+ } else {
 383+ $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-preview' ),
 384+ $preview
 385+ );
 386+ }
 387+
 388+ // Pull banner text and respect any inc: markup
 389+ $bodyPage = Title::newFromText(
 390+ "Centralnotice-template-{$currentTemplate}", NS_MEDIAWIKI );
 391+ $curRev = Revision::newFromTitle( $bodyPage );
 392+ $body = $curRev ? $curRev->getText() : '';
 393+
 394+ // Extract message fields from the banner body
 395+ $fields = array();
 396+ $allowedChars = Title::legalChars();
 397+ preg_match_all( "/\{\{\{([$allowedChars]+)\}\}\}/u", $body, $fields );
 398+
 399+ // If there are any message fields in the banner, display translation tools.
 400+ if ( count( $fields[0] ) > 0 ) {
 401+ if ( $this->editable ) {
 402+ $htmlOut .= Xml::openElement( 'form', array( 'method' => 'post' ) );
 403+ }
 404+ $htmlOut .= Xml::fieldset(
 405+ wfMsg( 'centralnotice-translate-heading', $currentTemplate ),
 406+ false,
 407+ array( 'id' => 'mw-centralnotice-translations-for' )
 408+ );
 409+ $htmlOut .= Xml::openElement( 'table',
 410+ array (
 411+ 'cellpadding' => 9,
 412+ 'width' => '100%'
 413+ )
 414+ );
 415+
 416+ // Table headers
 417+ $htmlOut .= Xml::element( 'th', array( 'width' => '15%' ),
 418+ wfMsg( 'centralnotice-message' ) );
 419+ $htmlOut .= Xml::element( 'th', array( 'width' => '5%' ),
 420+ wfMsg ( 'centralnotice-number-uses' ) );
 421+ $htmlOut .= Xml::element( 'th', array( 'width' => '40%' ),
 422+ wfMsg ( 'centralnotice-english' ) );
 423+ $languages = Language::getLanguageNames();
 424+ $htmlOut .= Xml::element( 'th', array( 'width' => '40%' ),
 425+ $languages[$wpUserLang] );
 426+
 427+ // Remove duplicate message fields
 428+ $filteredFields = array();
 429+ foreach ( $fields[1] as $field ) {
 430+ $filteredFields[$field] = array_key_exists( $field, $filteredFields )
 431+ ? $filteredFields[$field] + 1 : 1;
 432+ }
 433+
 434+ // Table rows
 435+ foreach ( $filteredFields as $field => $count ) {
 436+ // Message
 437+ $message = ( $wpUserLang == 'en' )
 438+ ? "Centralnotice-{$currentTemplate}-{$field}"
 439+ : "Centralnotice-{$currentTemplate}-{$field}/{$wpUserLang}";
 440+
 441+ // English value
 442+ $htmlOut .= Xml::openElement( 'tr' );
 443+
 444+ $title = Title::newFromText( "MediaWiki:{$message}" );
 445+ $htmlOut .= Xml::tags( 'td', null,
 446+ $sk->makeLinkObj( $title, htmlspecialchars( $field ) )
 447+ );
 448+
 449+ $htmlOut .= Xml::element( 'td', null, $count );
 450+
 451+ // English text
 452+ $englishText = wfMsg( 'centralnotice-message-not-set' );
 453+ $englishTextExists = false;
 454+ if (
 455+ Title::newFromText(
 456+ "Centralnotice-{$currentTemplate}-{$field}", NS_MEDIAWIKI
 457+ )->exists() )
 458+ {
 459+ $englishText = wfMsgExt( "Centralnotice-{$currentTemplate}-{$field}",
 460+ array( 'language' => 'en' )
 461+ );
 462+ $englishTextExists = true;
 463+ }
 464+ $htmlOut .= Xml::tags( 'td', null,
 465+ Xml::element( 'span',
 466+ array(
 467+ 'style' => 'font-style:italic;' .
 468+ ( !$englishTextExists ? 'color:silver' : '' )
 469+ ),
 470+ $englishText
 471+ )
 472+ );
 473+
 474+ // Foreign text input
 475+ $foreignText = '';
 476+ $foreignTextExists = false;
 477+ if ( Title::newFromText( $message, NS_MEDIAWIKI )->exists() ) {
 478+ $foreignText = wfMsgExt( "Centralnotice-{$currentTemplate}-{$field}",
 479+ array( 'language' => $wpUserLang )
 480+ );
 481+ $foreignTextExists = true;
 482+ }
 483+ $htmlOut .= Xml::tags( 'td', null,
 484+ Xml::input(
 485+ "updateText[{$wpUserLang}][{$currentTemplate}-{$field}]",
 486+ '',
 487+ $foreignText,
 488+ wfArrayMerge( $readonly,
 489+ array( 'style' => 'width:100%;' .
 490+ ( !$foreignTextExists ? 'color:red' : '' ) ) )
 491+ )
 492+ );
 493+ $htmlOut .= Xml::closeElement( 'tr' );
 494+ }
 495+ $htmlOut .= Xml::closeElement( 'table' );
 496+
 497+ if ( $this->editable ) {
 498+ $htmlOut .= Html::hidden( 'wpUserLanguage', $wpUserLang );
 499+ $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
 500+ $htmlOut .= Xml::tags( 'div',
 501+ array( 'class' => 'cn-buttons' ),
 502+ Xml::submitButton(
 503+ wfMsg( 'centralnotice-modify' ),
 504+ array( 'name' => 'update' )
 505+ )
 506+ );
 507+ }
 508+
 509+ $htmlOut .= Xml::closeElement( 'fieldset' );
 510+
 511+ if ( $this->editable ) {
 512+ $htmlOut .= Xml::closeElement( 'form' );
 513+ }
 514+
 515+ // Show language selection form
 516+ $htmlOut .= Xml::openElement( 'form', array( 'method' => 'post' ) );
 517+ $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-change-lang' ) );
 518+ $htmlOut .= Xml::openElement( 'table', array ( 'cellpadding' => 9 ) );
 519+ list( $lsLabel, $lsSelect ) = Xml::languageSelector( $wpUserLang );
 520+
 521+ $newPage = $this->getTitle( 'view' );
 522+
 523+ $htmlOut .= Xml::tags( 'tr', null,
 524+ Xml::tags( 'td', null, $lsLabel ) .
 525+ Xml::tags( 'td', null, $lsSelect ) .
 526+ Xml::tags( 'td', array( 'colspan' => 2 ),
 527+ Xml::submitButton( wfMsg( 'centralnotice-modify' ) )
 528+ )
 529+ );
 530+ $htmlOut .= Xml::tags( 'tr', null,
 531+ Xml::tags( 'td', null, '' ) .
 532+ Xml::tags( 'td', null,
 533+ $sk->makeLinkObj(
 534+ $newPage,
 535+ wfMsgHtml( 'centralnotice-preview-all-template-translations' ),
 536+ "template=$currentTemplate&wpUserLanguage=all" )
 537+ )
 538+ );
 539+ $htmlOut .= Xml::closeElement( 'table' );
 540+ $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
 541+ $htmlOut .= Xml::closeElement( 'fieldset' );
 542+ $htmlOut .= Xml::closeElement( 'form' );
 543+ }
 544+
 545+ // Show edit form
 546+ if ( $this->editable ) {
 547+ $htmlOut .= Xml::openElement( 'form',
 548+ array(
 549+ 'method' => 'post',
 550+ 'onsubmit' => 'return validateBannerForm(this)'
 551+ )
 552+ );
 553+ $htmlOut .= Html::hidden( 'wpMethod', 'editTemplate' );
 554+ }
 555+
 556+ // If there was an error, we'll need to restore the state of the form
 557+ if ( $wgRequest->wasPosted() && $wgRequest->getVal( 'mainform' ) ) {
 558+ $displayAnon = $wgRequest->getCheck( 'displayAnon' );
 559+ $displayAccount = $wgRequest->getCheck( 'displayAccount' );
 560+ $fundraising = $wgRequest->getCheck( 'fundraising' );
 561+ $landingPages = $wgRequest->getVal( 'landingPages' );
 562+ $body = $wgRequest->getVal( 'templateBody', $body );
 563+ } else { // Use previously stored values
 564+ $displayAnon = ( $row->tmp_display_anon == 1 );
 565+ $displayAccount = ( $row->tmp_display_account == 1 );
 566+ $fundraising = ( $row->tmp_fundraising == 1 );
 567+ $landingPages = $row->tmp_landing_pages;
 568+ // $body default is defined prior to message interface code
 569+ }
 570+
 571+ // Show banner settings
 572+ $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-settings' ) );
 573+ $htmlOut .= Xml::openElement( 'p', null );
 574+ $htmlOut .= wfMsg( 'centralnotice-banner-display' );
 575+ $htmlOut .= Xml::check( 'displayAnon', $displayAnon,
 576+ wfArrayMerge( $disabled, array( 'id' => 'displayAnon' ) ) );
 577+ $htmlOut .= Xml::label( wfMsg( 'centralnotice-banner-anonymous' ), 'displayAnon' );
 578+ $htmlOut .= Xml::check( 'displayAccount', $displayAccount,
 579+ wfArrayMerge( $disabled, array( 'id' => 'displayAccount' ) ) );
 580+ $htmlOut .= Xml::label( wfMsg( 'centralnotice-banner-logged-in' ), 'displayAccount' );
 581+ $htmlOut .= Xml::closeElement( 'p' );
 582+
 583+ // Fundraising settings
 584+ if ( $wgNoticeEnableFundraising ) {
 585+ $htmlOut .= Xml::openElement( 'p', null );
 586+ $htmlOut .= Xml::check( 'fundraising', $fundraising,
 587+ wfArrayMerge( $disabled, array( 'id' => 'fundraising' ) ) );
 588+ $htmlOut .= Xml::label( wfMsg( 'centralnotice-banner-fundraising' ),
 589+ 'fundraising' );
 590+ $htmlOut .= Xml::closeElement( 'p' );
 591+ if ( $fundraising ) {
 592+ $htmlOut .= Xml::openElement( 'div', array( 'id'=>'fundraisingInterface' ) );
 593+ } else {
 594+ $htmlOut .= Xml::openElement( 'div',
 595+ array( 'id'=>'fundraisingInterface', 'style'=>'display:none;' ) );
 596+ }
 597+ $htmlOut .= Xml::tags( 'p', array(),
 598+ wfMsg( 'centralnotice-banner-fundraising-help' ) );
 599+ $htmlOut .= Xml::tags( 'p', array(),
 600+ Xml::inputLabel(
 601+ wfMsg( 'centralnotice-banner-landing-pages' ),
 602+ 'landingPages', 'landingPages', 40, $landingPages,
 603+ array( 'maxlength' => 255 )
 604+ )
 605+ );
 606+ $htmlOut .= Xml::closeElement( 'div' );
 607+ }
 608+
 609+ // Begin banner body section
 610+ $htmlOut .= Xml::closeElement( 'fieldset' );
 611+ if ( $this->editable ) {
 612+ $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-edit-template' ) );
 613+ $htmlOut .= wfMsg( 'centralnotice-edit-template-summary' );
 614+ $buttons = array();
 615+ $buttons[] = '<a href="#" onclick="insertButton(\'close\');return false;">' .
 616+ wfMsg( 'centralnotice-close-button' ) . '</a>';
 617+ $htmlOut .= Xml::tags( 'div',
 618+ array( 'style' => 'margin-bottom: 0.2em;' ),
 619+ '<img src="' . $scriptPath . '/down-arrow.png" ' .
 620+ 'style="vertical-align:baseline;"/>' .
 621+ wfMsg( 'centralnotice-insert', $wgLang->commaList( $buttons ) )
 622+ );
 623+ } else {
 624+ $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-banner' ) );
 625+ }
 626+ $htmlOut .= Xml::textarea( 'templateBody', $body, 60, 20, $readonly );
 627+ $htmlOut .= Xml::closeElement( 'fieldset' );
 628+ if ( $this->editable ) {
 629+ // Indicate which form was submitted
 630+ $htmlOut .= Html::hidden( 'mainform', 'true' );
 631+ $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
 632+ $htmlOut .= Xml::tags( 'div',
 633+ array( 'class' => 'cn-buttons' ),
 634+ Xml::submitButton( wfMsg( 'centralnotice-save-banner' ) )
 635+ );
 636+ $htmlOut .= Xml::closeElement( 'form' );
 637+ }
 638+
 639+ // Show clone form
 640+ if ( $this->editable ) {
 641+ $htmlOut .= Xml::openElement ( 'form',
 642+ array(
 643+ 'method' => 'post',
 644+ 'action' => $this->getTitle( 'clone' )->getLocalUrl()
 645+ )
 646+ );
 647+
 648+ $htmlOut .= Xml::fieldset( wfMsg( 'centralnotice-clone-notice' ) );
 649+ $htmlOut .= Xml::openElement( 'table', array( 'cellpadding' => 9 ) );
 650+ $htmlOut .= Xml::openElement( 'tr' );
 651+ $htmlOut .= Xml::inputLabel(
 652+ wfMsg( 'centralnotice-clone-name' ),
 653+ 'newTemplate', 'newTemplate', '25' );
 654+ $htmlOut .= Xml::submitButton(
 655+ wfMsg( 'centralnotice-clone' ),
 656+ array ( 'id' => 'clone' ) );
 657+ $htmlOut .= Html::hidden( 'oldTemplate', $currentTemplate );
 658+
 659+ $htmlOut .= Xml::closeElement( 'tr' );
 660+ $htmlOut .= Xml::closeElement( 'table' );
 661+ $htmlOut .= Html::hidden( 'authtoken', $wgUser->editToken() );
 662+ $htmlOut .= Xml::closeElement( 'fieldset' );
 663+ $htmlOut .= Xml::closeElement( 'form' );
 664+ }
 665+
 666+ // End View Banner fieldset
 667+ $htmlOut .= Xml::closeElement( 'fieldset' );
 668+
 669+ // Output HTML
 670+ $wgOut->addHTML( $htmlOut );
 671+ }
 672+ }
 673+
 674+ /**
 675+ * Preview all available translations of a banner
 676+ */
 677+ public function showViewAvailable( $template ) {
 678+ global $wgOut, $wgUser;
 679+
 680+ // Testing to see if bumping up the memory limit lets meta preview
 681+ ini_set( 'memory_limit', '120M' );
 682+
 683+ $sk = $wgUser->getSkin();
 684+
 685+ // Pull all available text for a banner
 686+ $langs = array_keys( $this->getTranslations( $template ) );
 687+ $htmlOut = '';
 688+
 689+ // Begin View Banner fieldset
 690+ $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
 691+
 692+ $htmlOut .= Xml::element( 'h2', null, wfMsg( 'centralnotice-banner-heading', $template ) );
 693+
 694+ foreach ( $langs as $lang ) {
 695+ // Link and Preview all available translations
 696+ $viewPage = $this->getTitle( 'view' );
 697+ $render = new SpecialBannerLoader();
 698+ $render->siteName = 'Wikipedia';
 699+ $render->language = $lang;
 700+ try {
 701+ $preview = $render->getHtmlNotice( $template );
 702+ } catch ( SpecialBannerLoaderException $e ) {
 703+ $preview = wfMsg( 'centralnotice-nopreview' );
 704+ }
 705+ $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
 706+ $sk->makeLinkObj( $viewPage,
 707+ $lang,
 708+ 'template=' . urlencode( $template ) . "&wpUserLanguage=$lang" ) .
 709+ Xml::fieldset( wfMsg( 'centralnotice-preview' ),
 710+ $preview,
 711+ array( 'class' => 'cn-bannerpreview')
 712+ )
 713+ );
 714+ }
 715+
 716+ // End View Banner fieldset
 717+ $htmlOut .= Xml::closeElement( 'fieldset' );
 718+
 719+ return $wgOut->addHtml( $htmlOut );
 720+ }
 721+
 722+ /**
 723+ * Add or update a message
 724+ */
 725+ private function updateMessage( $text, $translation, $lang ) {
 726+ $title = Title::newFromText(
 727+ ( $lang == 'en' ) ? "Centralnotice-{$text}" : "Centralnotice-{$text}/{$lang}",
 728+ NS_MEDIAWIKI
 729+ );
 730+ $article = new Article( $title );
 731+ $article->doEdit( $translation, '', EDIT_FORCE_BOT );
 732+ }
 733+
 734+ private function getTemplateId ( $templateName ) {
 735+ $dbr = wfGetDB( DB_SLAVE );
 736+ $res = $dbr->select( 'cn_templates', 'tmp_id',
 737+ array( 'tmp_name' => $templateName ),
 738+ __METHOD__
 739+ );
 740+
 741+ $row = $dbr->fetchObject( $res );
 742+ if ( $row ) {
 743+ return $row->tmp_id;
 744+ }
 745+ return null;
 746+ }
 747+
 748+ private function removeTemplate ( $name ) {
 749+ $id = $this->getTemplateId( $name );
 750+ $dbr = wfGetDB( DB_SLAVE );
 751+ $res = $dbr->select( 'cn_assignments', 'asn_id', array( 'tmp_id' => $id ), __METHOD__ );
 752+
 753+ if ( $dbr->numRows( $res ) > 0 ) {
 754+ $this->showError( 'centralnotice-template-still-bound' );
 755+ return;
 756+ } else {
 757+ $dbw = wfGetDB( DB_MASTER );
 758+ $dbw->begin();
 759+ $dbw->delete( 'cn_templates',
 760+ array( 'tmp_id' => $id ),
 761+ __METHOD__
 762+ );
 763+ $dbw->commit();
 764+
 765+ $article = new Article(
 766+ Title::newFromText( "centralnotice-template-{$name}", NS_MEDIAWIKI )
 767+ );
 768+ $article->doDeleteArticle( 'CentralNotice Automated Removal' );
 769+ }
 770+ }
 771+
 772+ /**
 773+ * Create a new banner
 774+ */
 775+ private function addTemplate( $name, $body, $displayAnon, $displayAccount, $fundraising,
 776+ $landingPages ) {
 777+
 778+ if ( $body == '' || $name == '' ) {
 779+ $this->showError( 'centralnotice-null-string' );
 780+ return;
 781+ }
 782+
 783+ // Format name so there are only letters, numbers, and underscores
 784+ $name = preg_replace( '/[^A-Za-z0-9_]/', '', $name );
 785+
 786+ $dbr = wfGetDB( DB_SLAVE );
 787+ $res = $dbr->select(
 788+ 'cn_templates',
 789+ 'tmp_name',
 790+ array( 'tmp_name' => $name ),
 791+ __METHOD__
 792+ );
 793+
 794+ if ( $dbr->numRows( $res ) > 0 ) {
 795+ $this->showError( 'centralnotice-template-exists' );
 796+ return false;
 797+ } else {
 798+ $dbw = wfGetDB( DB_MASTER );
 799+ $res = $dbw->insert( 'cn_templates',
 800+ array(
 801+ 'tmp_name' => $name,
 802+ 'tmp_display_anon' => $displayAnon,
 803+ 'tmp_display_account' => $displayAccount,
 804+ 'tmp_fundraising' => $fundraising,
 805+ 'tmp_landing_pages' => $landingPages
 806+ ),
 807+ __METHOD__
 808+ );
 809+
 810+ // Perhaps these should move into the db as blobs instead of being stored as articles
 811+ $article = new Article(
 812+ Title::newFromText( "centralnotice-template-{$name}", NS_MEDIAWIKI )
 813+ );
 814+ $article->doEdit( $body, '', EDIT_FORCE_BOT );
 815+ return true;
 816+ }
 817+ }
 818+
 819+ /**
 820+ * Update a banner
 821+ */
 822+ private function editTemplate( $name, $body, $displayAnon, $displayAccount, $fundraising,
 823+ $landingPages ) {
 824+
 825+ if ( $body == '' || $name == '' ) {
 826+ $this->showError( 'centralnotice-null-string' );
 827+ return;
 828+ }
 829+
 830+ $dbr = wfGetDB( DB_SLAVE );
 831+ $res = $dbr->select( 'cn_templates', 'tmp_name',
 832+ array( 'tmp_name' => $name ),
 833+ __METHOD__
 834+ );
 835+
 836+ if ( $dbr->numRows( $res ) == 1 ) {
 837+ $dbw = wfGetDB( DB_MASTER );
 838+ $res = $dbw->update( 'cn_templates',
 839+ array(
 840+ 'tmp_display_anon' => $displayAnon,
 841+ 'tmp_display_account' => $displayAccount,
 842+ 'tmp_fundraising' => $fundraising,
 843+ 'tmp_landing_pages' => $landingPages
 844+ ),
 845+ array( 'tmp_name' => $name )
 846+ );
 847+
 848+ // Perhaps these should move into the db as blob
 849+ $article = new Article(
 850+ Title::newFromText( "centralnotice-template-{$name}", NS_MEDIAWIKI )
 851+ );
 852+ $article->doEdit( $body, '', EDIT_FORCE_BOT );
 853+ return;
 854+ }
 855+ }
 856+
 857+ /**
 858+ * Copy all the data from one banner to another
 859+ */
 860+ public function cloneTemplate( $source, $dest ) {
 861+
 862+ // Reset the timer as updates on meta take a long time
 863+ set_time_limit( 300 );
 864+
 865+ // Pull all possible langs
 866+ $langs = $this->getTranslations( $source );
 867+
 868+ // Normalize name
 869+ $dest = preg_replace( '/[^A-Za-z0-9_]/', '', $dest );
 870+
 871+ // Pull banner settings from database
 872+ $dbr = wfGetDB( DB_SLAVE );
 873+ $row = $dbr->selectRow( 'cn_templates',
 874+ array(
 875+ 'tmp_display_anon',
 876+ 'tmp_display_account',
 877+ 'tmp_fundraising',
 878+ 'tmp_landing_pages'
 879+ ),
 880+ array( 'tmp_name' => $source ),
 881+ __METHOD__
 882+ );
 883+ $displayAnon = $row->tmp_display_anon;
 884+ $displayAccount = $row->tmp_display_account;
 885+ $fundraising = $row->tmp_fundraising;
 886+ $landingPages = $row->tmp_landing_pages;
 887+
 888+ // Pull banner text and respect any inc: markup
 889+ $bodyPage = Title::newFromText( "Centralnotice-template-{$source}", NS_MEDIAWIKI );
 890+ $template_body = Revision::newFromTitle( $bodyPage )->getText();
 891+
 892+ // Create new banner
 893+ if ( $this->addTemplate( $dest, $template_body, $displayAnon, $displayAccount, $fundraising,
 894+ $landingPages ) ) {
 895+
 896+ // Populate the fields
 897+ foreach ( $langs as $lang => $fields ) {
 898+ foreach ( $fields as $field => $text ) {
 899+ $this->updateMessage( "$dest-$field", $text, $lang );
 900+ }
 901+ }
 902+ return $dest;
 903+ }
 904+ }
 905+
 906+ /**
 907+ * Find all message fields set for a banner
 908+ */
 909+ private function findFields( $template ) {
 910+ $body = wfMsg( "Centralnotice-template-{$template}" );
 911+
 912+ // Generate list of message fields from parsing the body
 913+ $fields = array();
 914+ $allowedChars = Title::legalChars();
 915+ preg_match_all( "/\{\{\{([$allowedChars]+)\}\}\}/u", $body, $fields );
 916+
 917+ // Remove duplicates
 918+ $filteredFields = array();
 919+ foreach ( $fields[1] as $field ) {
 920+ $filteredFields[$field] = array_key_exists( $field, $filteredFields )
 921+ ? $filteredFields[$field] + 1
 922+ : 1;
 923+ }
 924+ return $filteredFields;
 925+ }
 926+
 927+ /**
 928+ * Get all the translations of all the messages for a banner
 929+ * @return a 2D array of every set message in every language for one banner
 930+ */
 931+ public function getTranslations( $template ) {
 932+ $translations = array();
 933+
 934+ // Pull all language codes to enumerate
 935+ $allLangs = array_keys( Language::getLanguageNames() );
 936+
 937+ // Lookup all the message fields for a banner
 938+ $fields = $this->findFields( $template );
 939+
 940+ // Iterate through all possible languages to find matches
 941+ foreach ( $allLangs as $lang ) {
 942+ // Iterate through all possible message fields
 943+ foreach ( $fields as $field => $count ) {
 944+ // Put all message fields together for a lookup
 945+ $message = ( $lang == 'en' )
 946+ ? "Centralnotice-{$template}-{$field}"
 947+ : "Centralnotice-{$template}-{$field}/{$lang}";
 948+ if ( Title::newFromText( $message, NS_MEDIAWIKI )->exists() ) {
 949+ $translations[$lang][$field] = wfMsgExt(
 950+ "Centralnotice-{$template}-{$field}",
 951+ array( 'language' => $lang )
 952+ );
 953+ }
 954+ }
 955+ }
 956+ return $translations;
 957+ }
 958+
 959+ function showError( $message ) {
 960+ global $wgOut;
 961+ $wgOut->wrapWikiMsg( "<div class='cn-error'>\n$1\n</div>", $message );
 962+ $this->centralNoticeError = true;
 963+ }
 964+
 965+}
Property changes on: trunk/extensions/CentralNotice/special/SpecialNoticeTemplate.php
___________________________________________________________________
Added: svn:eol-style
1966 + native
Index: trunk/extensions/CentralNotice/special/SpecialCentralNoticeLogs.php
@@ -0,0 +1,88 @@
 2+<?php
 3+
 4+if ( !defined( 'MEDIAWIKI' ) ) {
 5+ echo "CentralNotice extension\n";
 6+ exit( 1 );
 7+}
 8+
 9+class SpecialCentralNoticeLogs extends UnlistedSpecialPage {
 10+ public $logType = 'campaignsettings';
 11+
 12+ function __construct() {
 13+ // Register special page
 14+ parent::__construct( "CentralNoticeLogs" );
 15+ }
 16+
 17+ /**
 18+ * Handle different types of page requests
 19+ */
 20+ function execute( $sub ) {
 21+ global $wgOut, $wgRequest, $wgExtensionAssetsPath;
 22+
 23+ if ( $wgRequest->wasPosted() ) {
 24+ $this->logType = $wgRequest->getText( 'log', 'campaignsettings' );
 25+ }
 26+
 27+ // Begin output
 28+ $this->setHeaders();
 29+
 30+ // Add style file to the output headers
 31+ $wgOut->addExtensionStyle( "$wgExtensionAssetsPath/CentralNotice/centralnotice.css" );
 32+
 33+ // Add script file to the output headers
 34+ $wgOut->addScriptFile( "$wgExtensionAssetsPath/CentralNotice/centralnotice.js" );
 35+
 36+ // Initialize error variable
 37+ $this->centralNoticeError = false;
 38+
 39+ // Show summary
 40+ $wgOut->addWikiMsg( 'centralnotice-summary' );
 41+
 42+ // Show header
 43+ CentralNotice::printHeader();
 44+
 45+ // Begin Banners tab content
 46+ $wgOut->addHTML( Xml::openElement( 'div', array( 'id' => 'preferences' ) ) );
 47+
 48+ $htmlOut = '';
 49+
 50+ // Begin log selection fieldset
 51+ $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
 52+
 53+ $htmlOut .= Xml::openElement( 'form', array( 'method' => 'post' ) );
 54+ $htmlOut .= Xml::element( 'h2', null, wfMsg( 'centralnotice-view-logs' ) );
 55+ $htmlOut .= Xml::closeElement( 'form' );
 56+
 57+ // End log selection fieldset
 58+ $htmlOut .= Xml::closeElement( 'fieldset' );
 59+
 60+ $wgOut->addHTML( $htmlOut );
 61+
 62+ $this->showLog( $this->logType );
 63+
 64+ // End Banners tab content
 65+ $wgOut->addHTML( Xml::closeElement( 'div' ) );
 66+ }
 67+
 68+ /**
 69+ * Show a log.
 70+ */
 71+ function showLog( $logType ) {
 72+ global $wgOut;
 73+
 74+ $htmlOut = '';
 75+
 76+ // Begin log fieldset
 77+ $htmlOut .= Xml::openElement( 'fieldset', array( 'class' => 'prefsection' ) );
 78+
 79+ $htmlOut .= Xml::tags( 'p', null,
 80+ '<i>Coming soon...</i>'
 81+ );
 82+
 83+ // End log fieldset
 84+ $htmlOut .= Xml::closeElement( 'fieldset' );
 85+
 86+ $wgOut->addHTML( $htmlOut );
 87+ }
 88+
 89+}
Property changes on: trunk/extensions/CentralNotice/special/SpecialCentralNoticeLogs.php
___________________________________________________________________
Added: svn:eol-style
190 + native
Index: trunk/extensions/CentralNotice/special/SpecialHideBanners.php
@@ -0,0 +1,42 @@
 2+<?php
 3+if ( !defined( 'MEDIAWIKI' ) ) {
 4+ echo "CentralNotice extension\n";
 5+ exit( 1 );
 6+}
 7+
 8+/**
 9+ * Unlisted Special Page which sets a cookie for hiding banners across all languages of a project.
 10+ * This is typically used on donation thank-you pages so that users who have donated will no longer
 11+ * see fundrasing banners.
 12+ */
 13+class SpecialHideBanners extends UnlistedSpecialPage {
 14+ function __construct() {
 15+ parent::__construct( 'HideBanners' );
 16+ }
 17+
 18+ function execute( $par ) {
 19+ global $wgOut;
 20+
 21+ $this->setHideCookie();
 22+
 23+ $wgOut->disable();
 24+ wfResetOutputBuffers();
 25+
 26+ header( 'Content-Type: image/png' );
 27+ header( 'Cache-Control: no-cache' );
 28+
 29+ readfile( dirname( __FILE__ ) . '/1x1.png' );
 30+ }
 31+
 32+ function setHideCookie() {
 33+ global $wgNoticeCookieDomain, $wgCookieSecure;
 34+ $exp = time() + 86400 * 14; // Cookie expires after 2 weeks
 35+ if ( is_callable( array( 'CentralAuthUser', 'getCookieDomain' ) ) ) {
 36+ $cookieDomain = CentralAuthUser::getCookieDomain();
 37+ } else {
 38+ $cookieDomain = $wgNoticeCookieDomain;
 39+ }
 40+ // Hide banners for this domain
 41+ setcookie( 'hidesnmessage', '1', $exp, '/', $cookieDomain, $wgCookieSecure );
 42+ }
 43+}
Property changes on: trunk/extensions/CentralNotice/special/SpecialHideBanners.php
___________________________________________________________________
Added: svn:eol-style
144 + native

Status & tagging log