Adamw has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/55012


Change subject: Display allocations in a compact form
......................................................................

Display allocations in a compact form

Find all groups of criteria sharing the same allocations, and partition into
Cartesian products for display.

The allocations page can now display all global allocations on one screen.

TODO:
* make the algorithm more readable
* date filter and history reconstruction

Change-Id: Id10b191bd46ce8c6776506644e4e20d983a6380e
---
M CentralNotice.i18n.php
M includes/Campaign.php
M special/SpecialBannerAllocation.php
M special/SpecialCentralNotice.php
4 files changed, 313 insertions(+), 117 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/CentralNotice 
refs/changes/12/55012/1

diff --git a/CentralNotice.i18n.php b/CentralNotice.i18n.php
index 2e03396..20e3aca 100644
--- a/CentralNotice.i18n.php
+++ b/CentralNotice.i18n.php
@@ -139,7 +139,7 @@
        'centralnotice-projects' => 'Projects',
        'centralnotice-country' => 'Country',
        'centralnotice-no-allocation' => 'No banners allocated.',
-       'centralnotice-allocation-description' => 'Banner allocation for $1.$2 
in $3:',
+       'centralnotice-allocation-description' => 'Banner allocation for 
Language: $1, Project: $2, in Country: $3:',
        'centralnotice-percentage' => 'Percentage',
        'centralnotice-documentwrite-error' => "document.write() cannot be used 
within a banner.\nSee http://meta.wikimedia.org/wiki/Help:CentralNotice for 
more information.",
        'centralnotice-preferred' => 'Priority',
@@ -195,6 +195,9 @@
        'centralnotice-bucket-letter' => 'Bucket "$1"',
        'centralnotice-all' => 'All',
        'centralnotice-all-except' => 'All except $1',
+       'centralnotice-user-role' => 'User role',
+       'centralnotice-user-role-anonymous' => 'Anonymous',
+       'centralnotice-user-role-logged-in' => 'Logged-in',
 );
 
 /** Message documentation (Message documentation)
@@ -438,6 +441,9 @@
        'centralnotice-all' => 'Used when a list contains all possible 
elements.',
        'centralnotice-all-except' => 'Describe a list which nearly contains 
every possible element, by only listing what is excluded. Parameters:
 * $1 - a comma-separated list of excluded elements',
+       'centralnotice-user-role' => 'Label for a logged-in / anonymous user 
column',
+       'centralnotice-user-role-anonymous' => 'Label for the anonymous user 
role',
+       'centralnotice-user-role-logged-in' => 'Label for the logged-in user 
role',
 );
 
 /** Afrikaans (Afrikaans)
diff --git a/includes/Campaign.php b/includes/Campaign.php
index b0755bd..2bf32b8 100644
--- a/includes/Campaign.php
+++ b/includes/Campaign.php
@@ -79,11 +79,15 @@
                        $conds[ 'nl_language' ] = $language;
                }
 
+               if ( $location ) {
+                       $conds['not_geo'] = 0;
+               }
+
                // Pull the notice IDs of the non geotargeted campaigns
                $res = $dbr->select(
                        $tables,
                        'not_id',
-                       array_merge( $conds, array( 'not_geo' => 0 ) ),
+                       $conds,
                        __METHOD__
                );
                foreach ( $res as $row ) {
diff --git a/special/SpecialBannerAllocation.php 
b/special/SpecialBannerAllocation.php
index 239d80b..ea86df3 100644
--- a/special/SpecialBannerAllocation.php
+++ b/special/SpecialBannerAllocation.php
@@ -29,7 +29,7 @@
         *
         * @var string $project
         */
-       public $project = 'wikipedia';
+       public $project = null;
 
        /**
         * The language being used for banner allocation
@@ -38,7 +38,7 @@
         *
         * @var string $project
         */
-       public $language = 'en';
+       public $language = null;
 
        /**
         * The location being used for banner allocation.
@@ -47,7 +47,7 @@
         *
         * @var string $location
         */
-       public $location = 'US';
+       public $location = null;
 
        /**
         * Constructor
@@ -69,15 +69,9 @@
                $out = $this->getOutput();
                $request = $this->getRequest();
 
-               $this->project = $request->getText( 'project', $wgNoticeProject 
);
-               $this->language = $request->getText( 'language', 
$wgLanguageCode );
-
-               // If the form has been submitted, the country code should be 
passed along.
-               $locationSubmitted = $request->getVal( 'country' );
-               $this->location = $locationSubmitted ? $locationSubmitted : 
$this->location;
-
-               // Convert submitted location to boolean value. If it true, 
showList() will be called.
-               $locationSubmitted = (boolean) $locationSubmitted;
+               $this->project = $request->getText( 'project', $this->project );
+               $this->language = $request->getText( 'language', 
$this->language );
+               $this->location = $request->getVal( 'country', $this->location 
);
 
                // Begin output
                $this->setHeaders();
@@ -117,6 +111,7 @@
                $htmlOut .= Html::openElement( 'td' );
                $htmlOut .= Html::openElement( 'select', array( 'name' => 
'project' ) );
 
+               $htmlOut .= Xml::option( $this->msg( 'centralnotice-all' ), '', 
'' === $this->project );
                foreach ( $wgNoticeProjects as $value ) {
                        $htmlOut .= Xml::option( $value, $value, $value === 
$this->project );
                }
@@ -142,6 +137,7 @@
 
                $htmlOut .= Html::openElement( 'select', array( 'name' => 
'language' ) );
 
+               $htmlOut .= Xml::option( $this->msg( 'centralnotice-all' ), '', 
'' === $this->language );
                foreach( $languages as $code => $name ) {
                        $htmlOut .= Xml::option(
                                $this->msg( 'centralnotice-language-listing', 
$code, $name )->text(),
@@ -160,6 +156,7 @@
 
                $htmlOut .= Html::openElement( 'select', array( 'name' => 
'country' ) );
 
+               $htmlOut .= Xml::option( $this->msg( 'centralnotice-all' ), '', 
'' === $this->location );
                foreach( $countries as $code => $name ) {
                        $htmlOut .= Xml::option( $name, $code, $code === 
$this->location );
                }
@@ -171,7 +168,7 @@
 
                $htmlOut .= Xml::tags( 'div',
                        array( 'class' => 'cn-buttons' ),
-                       Xml::submitButton( $this->msg( 'centralnotice-view' 
)->text() )
+                       Xml::submitButton( $this->msg( 
'centralnotice-apply-filters' )->text() )
                );
                $htmlOut .= Html::closeElement( 'form' );
 
@@ -180,10 +177,7 @@
 
                $out->addHTML( $htmlOut );
 
-               // Handle form submissions
-               if ( $locationSubmitted ) {
-                       $this->showList();
-               }
+               $this->showList();
 
                // End Banners tab content
                $out->addHTML( Html::closeElement( 'div' ) );
@@ -195,61 +189,110 @@
        public function showList() {
         global $wgNoticeNumberOfBuckets;
 
-               // Obtain all banners & campaigns
-               $request = $this->getRequest();
-               $project = $request->getText('project');
-               $country = $request->getText('country');
-               $language = $request->getText('language');
-
                // Begin building HTML
                $htmlOut = '';
 
                // Begin Allocation list fieldset
                $htmlOut .= Html::openElement( 'fieldset', array( 'class' => 
'prefsection' ) );
 
+               $languageLabel = $this->language ?
+                       htmlspecialchars( $this->language ) : $this->msg( 
'centralnotice-all' );
+               $projectLabel = $this->language ?
+                       htmlspecialchars( $this->project ) : $this->msg( 
'centralnotice-all' );
+               $countryLabel = $this->location ?
+                       htmlspecialchars( $this->location ) : $this->msg( 
'centralnotice-all' );
+
                $htmlOut .= Xml::tags( 'p', null,
                        $this->msg(
                                'centralnotice-allocation-description',
-                               htmlspecialchars( $language ),
-                               htmlspecialchars( $project ),
-                               htmlspecialchars( $country )
+                               $languageLabel,
+                               $projectLabel,
+                               $countryLabel
                        )->text()
                );
 
-               // FIXME bannerstats is toast
-               // Build campaign list for bannerstats.js
-               //$campaignsUsed = array_keys($anonCampaigns + 
$accountCampaigns);
-               //
-               //$campaignList = FormatJson::encode( $campaignsUsed );
-               //$js = "wgCentralNoticeAllocationCampaigns = $campaignList;";
-               //$htmlOut .= Html::inlineScript( $js );
+               $activeCampaigns = Campaign::getCampaigns( $this->project, 
$this->language, $this->location );
+               $campaigns = array();
+               foreach ( $activeCampaigns as $campaignId ) {
+                       $campaignName = Campaign::getNoticeName( $campaignId );
 
-               // FIXME matrix is chosen dynamically based on more UI inputs
-        $matrix = array();
-        for ( $i = 0; $i < $wgNoticeNumberOfBuckets; $i++ ) {
-            $matrix[] = array( 'anonymous' => 'true', 'bucket' => "$i" );
-        }
-        for ( $i = 0; $i < $wgNoticeNumberOfBuckets; $i++ ) {
-            $matrix[] = array( 'anonymous' => 'false', 'bucket' => "$i" );
-        }
+                       //FIXME: get campaign settings as of $date
+                       $settings = Campaign::getCampaignSettings( 
$campaignName, true );
 
-               foreach ( $matrix as $target ) {
-                       $banners = 
ApiCentralNoticeAllocations::getAllocationInformation(
-                               $project,
-                               $country,
-                               $language,
-                               $target['anonymous'],
-                               $target['bucket']
-                       );
-                       if ( $target['anonymous'] === 'true' ) {
-                               $label = $this->msg( 
'centralnotice-banner-anonymous' )->text();
+                       // omg.  implode explode fail
+                       if ( $settings['geo'] ) {
+                               $settings['countries'] = explode( ", ", 
$settings['countries'] );
                        } else {
-                               $label = $this->msg( 
'centralnotice-banner-logged-in' )->text();
+                               $settings['countries'] = array_keys( 
GeoTarget::getCountriesList( 'en' ) );
                        }
-                       $label .= ' -- ' . $this->msg( 
'centralnotice-bucket-letter' )->
-                               rawParams( chr( $target['bucket'] + 65 ) 
)->text();
+                       $settings['projects'] = explode( ", ", 
$settings['projects'] );
+                       $settings['languages'] = explode( ", ", 
$settings['languages'] );
 
-                       $htmlOut .= $this->getTable( $label, $banners );
+                       $campaigns[$campaignName] = $settings;
+               }
+
+               $campaignCriteria = array(
+                       'projects', 'languages', 'countries',
+               );
+               $groupings = $this->analyzeGroupings( $campaigns, 
$campaignCriteria );
+
+               /*
+                * TODO: need to compare a sample of actual allocations within 
each grouping,
+                * because opaque-ish factors like priority might cause some 
groupings to be
+                * functionally identical.  Merge these together.
+                */
+
+               foreach ( $groupings as $label => $rows ) {
+                       $label = 
$this->getContext()->getLanguage()->listToText( explode( " + ", $label ) );
+
+                       $htmlOut .= Html::element( 'h2', array(), $this->msg( 
'centralnotice-notice-heading', $label )->text() );
+
+                       if ( !$rows ) {
+                               continue;
+                       }
+
+                       $htmlOut .= Html::openElement( 'table',
+                               array ( 'cellpadding' => 9, 'class' => 
'wikitable', 'style' => 'margin: 1em 0;' )
+                       );
+                       $htmlOut .= Html::openElement( 'tr' );
+                       $htmlOut .= Html::element( 'th', array( 'width' => 
'30%' ),
+                               $this->msg( 'centralnotice-projects' )->text() 
);
+                       $htmlOut .= Html::element( 'th', array( 'width' => 
'30%' ),
+                               $this->msg( 'centralnotice-countries' )->text() 
);
+                       $htmlOut .= Html::element( 'th', array( 'width' => 
'30%' ),
+                               $this->msg( 'centralnotice-languages' )->text() 
);
+                       $htmlOut .= Html::closeElement( 'tr' );
+
+                       foreach ( $rows as $row ) {
+                               $htmlOut .= Html::openElement( 'tr', array( 
'class'=>'mw-sp-centralnotice-allocationrow' ) );
+
+                               $htmlOut .= Html::openElement( 'td' );
+                               $htmlOut .= $this->listProjects( 
$row['projects'] );
+                               $htmlOut .= Html::closeElement( 'td' );
+
+                               $htmlOut .= Html::openElement( 'td' );
+                               $htmlOut .= $this->listCountries( 
$row['countries'] );
+                               $htmlOut .= Html::closeElement( 'td' );
+
+                               $htmlOut .= Html::openElement( 'td' );
+                               $htmlOut .= $this->listLanguages( 
$row['languages'] );
+                               $htmlOut .= Html::closeElement( 'td' );
+
+                               $htmlOut .= Html::closeElement( 'tr' );
+                       }
+                       $htmlOut .= Html::closeElement( 'table' );
+
+                       $htmlOut .= $this->getBannerAllocationsTable(
+                               end( $rows[0]['projects'] ),
+                               end( $rows[0]['countries'] ),
+                               end( $rows[0]['languages'] ),
+                               $rows[0]['buckets']
+                       );
+               }
+
+               if ( !$groupings ) {
+                       $htmlOut .= Html::element( 'p', null,
+                               $this->msg( 'centralnotice-no-allocation' 
)->text() );
                }
 
                // End Allocation list fieldset
@@ -259,81 +302,224 @@
        }
 
        /**
-        * Generate the HTML for an allocation table
-        * @param $type string The title for the table
-        * @param $banners array The banners to list
+        * Find the groupings in which allocation is uniform, by finding the
+        * disjoint union of campaign criteria.  Partition each grouping and
+        * express it as a sum of cartesian products for display in a table.
+        *
+        * Take campaigns C and D, with criteria from x and y: Cx,y = ({a, b}, 
{1, 2}),
+        * and Dx,y = ({a}, {1, 3}).  The intersection will be CDx,y = ({a}, 
{1}),
+        * and to represent the remaining grouping C-CD will take two 
cross-product
+        * rows: Cx,y = ({a}, {2}) + ({b}, {1, 2}).
+        */
+       protected function analyzeGroupings( $campaigns, $criteria ) {
+               // start simple.  build single-campaign groups without worrying 
about overlap
+               $groupings = array();
+               foreach ( $campaigns as $name => $settings ) {
+                       $groupings[$name] = array( $campaigns[$name] );
+               }
+
+               $otherGroupings = $groupings;
+
+               // find intersections
+               // we're going to mutate during iteration ;)
+               $groupingsObj = new ArrayObject( $groupings );
+               foreach ( $groupingsObj as $name => $rows ) {
+                       unset( $otherGroupings[$name] );
+                       foreach ( $otherGroupings as $otherName => $otherRows ) 
{
+                               foreach ( $rows as $settings ) {
+                                       foreach ( $otherRows as $otherSettings 
) {
+                                               foreach ( $criteria as 
$property ) {
+                                                       
$intersection[$property] = array_intersect( $settings[$property], 
$otherSettings[$property] );
+                                                       // When any criteria 
are perpendicular, there cannot be an intersection
+                                                       if ( 
!$intersection[$property] ) {
+                                                               break 2;
+                                                       }
+                                               }
+                                               $intersection['buckets'] = max( 
$settings['buckets'], $otherSettings['buckets'] );
+
+                                               // partition each grouping row 
contributing to the intersection
+                                               foreach ( array( $name, 
$otherName ) as $groupingName ) {
+                                                       $newRows = array();
+                                                       foreach ( 
$groupingsObj[$groupingName] as $index => &$row ) {
+                                                               foreach ( 
$criteria as $property ) {
+                                                                       
$difference = $row;
+                                                                       
$difference[$property] = array_diff( $row[$property], $intersection[$property] 
);
+
+                                                                       // This 
criterion is fully intersected
+                                                                       if ( 
!$difference[$property] ) {
+                                                                               
continue;
+                                                                       }
+
+                                                                       
$newRows[] = $difference;
+                                                               }
+                                                       }
+                                                       
$groupingsObj[$groupingName] = $newRows;
+                                               }
+
+                                               // intersections are always a 
single cartesian product
+                                               $intersection_label = "$name + 
$otherName";
+                                               
$groupingsObj[$intersection_label] = $otherGroupings[$intersection_label] = 
array( $intersection );
+                                       }
+                               }
+                       }
+               }
+
+               return $groupingsObj->getArrayCopy();
+       }
+
+       /**
+        * Generate the HTML for an allocation table.
+        *
+        * Given a specific campaign-level criteria, display all unique 
allocations
+        * variations caused by banner displayAnon, etc. criteria.
+        *
+        * @param $project string Use these campaign-level criteria...
+        * @param $country string
+        * @param $language string
+        * @param $numBuckets array Check allocations in this many buckets
         * @return HTML for the table
         */
-       public function getTable( $type, $banners ) {
-               $viewBanner = $this->getTitleFor( 'NoticeTemplate', 'view' );
-               $viewCampaign = $this->getTitleFor( 'CentralNotice' );
+       protected function getBannerAllocationsTable( $project, $country, 
$language, $numBuckets ) {
+               // This is annoying.  Within the campaign, banners usually vary 
by user
+               // logged-in status, and bucket.  Determine the allocations and
+               // collapse any dimensions which do not vary.
+               foreach ( array( true, false ) as $isAnon ) {
+                       for ( $bucket = 0; $bucket < $numBuckets; $bucket++ ) {
+                               $variations[$isAnon][$bucket] = 
ApiCentralNoticeAllocations::getAllocationInformation(
+                                       $project, $country, $language,
+                                       $isAnon ? 'true' : 'false',
+                                       $bucket
+                               );
+                               $allocSignatures = array();
+                               foreach ( $variations[$isAnon][$bucket] as 
$banner ) {
+                                       $allocSignatures[] = 
"{$banner['name']}:{$banner['allocation']}";
+                               }
+                               $hashes[$isAnon][$bucket] = sha1( implode( ";", 
$allocSignatures ) );
+                       }
+               }
+
+               $variesAnon = false;
+               foreach ( range( 0, $numBuckets - 1 ) as $bucket ) {
+                       if ( $hashes[0][$bucket] != $hashes[1][$bucket] ) {
+                               $variesAnon = true;
+                               break;
+                       }
+               }
+               if ( !$variesAnon ) {
+                       unset( $variations[1] );
+               }
+
+               $variesBucket = ( $numBuckets > 1 );
 
                $htmlOut = Html::openElement( 'table',
                        array ( 'cellpadding' => 9, 'class' => 'wikitable 
sortable', 'style' => 'margin: 1em 0;' )
                );
-               $htmlOut .= Html::element( 'caption', array( 'style' => 
'font-size: 1.2em;' ), $type );
+               //$htmlOut .= Html::element( 'caption', array( 'style' => 
'font-size: 1.2em;' ), $caption );
 
-               if (count($banners) > 0) {
-
-                       $htmlOut .= Html::openElement( 'tr' );
+               $htmlOut .= Html::openElement( 'tr' );
+               $htmlOut .= Html::element( 'th', array( 'width' => '5%' ),
+                       $this->msg( 'centralnotice-user-role' )->text() );
+               if ( $variesBucket ) {
                        $htmlOut .= Html::element( 'th', array( 'width' => '5%' 
),
-                               $this->msg( 'centralnotice-percentage' 
)->text() );
-                       $htmlOut .= Html::element( 'th', array( 'width' => 
'30%' ),
-                               $this->msg( 'centralnotice-banner' )->text() );
-                       $htmlOut .= Html::element( 'th', array( 'width' => 
'30%' ),
-                               $this->msg( 'centralnotice-notice' )->text() );
-                       $htmlOut .= Html::closeElement( 'tr' );
+                               $this->msg( 'centralnotice-bucket' )->text() );
+               }
+               $htmlOut .= Html::element( 'th', array( 'width' => '5%' ),
+                       $this->msg( 'centralnotice-percentage' )->text() );
+               $htmlOut .= Html::element( 'th', array( 'width' => '30%' ),
+                       $this->msg( 'centralnotice-banner' )->text() );
+               $htmlOut .= Html::element( 'th', array( 'width' => '30%' ),
+                       $this->msg( 'centralnotice-notice' )->text() );
+               $htmlOut .= Html::closeElement( 'tr' );
 
-                       foreach ( $banners as $banner ) {
-
-                               $percentage = round( $banner['allocation'] * 
100, 2 );
-
-                               // Row begin
-                               $htmlOut .= Html::openElement( 'tr', array( 
'class'=>'mw-sp-centralnotice-allocationrow' ) );
-
-                               // Percentage
-                               $htmlOut .= Html::openElement( 'td' );
-                               $htmlOut .= $this->msg( 'percent' )->numParams( 
$percentage )->escaped();
-                               $htmlOut .= Html::closeElement( 'td' );
-
-                               // Banner name
-                               $htmlOut .= Xml::openElement( 'td', array( 
'valign' => 'top' ) );
-                               // The span class is used by bannerstats.js to 
find where to insert the stats
-                               $htmlOut .= Html::openElement( 'span',
-                                       array( 'class' => 
'cn-'.$banner['campaign'].'-'.$banner['name'] ) );
-                               $htmlOut .= Linker::link(
-                                       $viewBanner,
-                                       htmlspecialchars( $banner['name'] ),
-                                       array(),
-                                       array( 'template' => $banner['name'] )
-                               );
-                               $htmlOut .= Html::closeElement( 'span' );
-                               $htmlOut .= Html::closeElement( 'td' );
-
-                               // Campaign name
-                               $htmlOut .= Xml::tags( 'td', array( 'valign' => 
'top' ),
-                                       Linker::link(
-                                               $viewCampaign,
-                                               htmlspecialchars( 
$banner['campaign'] ),
-                                               array(),
-                                               array(
-                                                       'method' => 
'listNoticeDetail',
-                                                       'notice' => 
$banner['campaign']
-                                               )
-                                       )
-                               );
-
-                               // Row end
-                               $htmlOut .= Html::closeElement( 'tr' );
+               foreach ( $variations as $isAnon => $bucketVariations ) {
+                       foreach ( $bucketVariations as $bucket => $banners ) {
+                               foreach ( $banners as $banner ) {
+                                       $htmlOut .= 
$this->getBannerAllocationsVariantRow( $banner, $variesAnon, $variesBucket, 
$isAnon, $bucket );
+                               }
+                               if ( !count( $banners ) ) {
+                                       $htmlOut .= 
$this->getBannerAllocationsVariantRow( null, $variesAnon, $variesBucket, 
$isAnon, $bucket );
+                               }
                        }
+               }
 
+               $htmlOut .= Html::closeElement( 'table' );
+
+               return $htmlOut;
+       }
+
+       /**
+        * Print one line of banner allocations.
+        */
+       function getBannerAllocationsVariantRow( $banner, $variesAnon, 
$variesBucket, $isAnon, $bucket ) {
+               $htmlOut = '';
+
+               $viewBanner = $this->getTitleFor( 'NoticeTemplate', 'view' );
+               $viewCampaign = $this->getTitleFor( 'CentralNotice' );
+
+               // Row begin
+               $htmlOut .= Html::openElement( 'tr', array( 
'class'=>'mw-sp-centralnotice-allocationrow' ) );
+
+               if ( !$variesAnon ) {
+                       $anonLabel = $this->msg( 'centralnotice-all' )->text();
+               } elseif ( $isAnon ) {
+                       $anonLabel = $this->msg( 
'centralnotice-user-role-anonymous' )->text();
                } else {
-                       $htmlOut .= Html::openElement('tr');
+                       $anonLabel = $this->msg( 
'centralnotice-user-role-logged-in' )->text();
+               }
+
+               $htmlOut .= Html::openElement( 'td' );
+               $htmlOut .= $anonLabel;
+               $htmlOut .= Html::closeElement( 'td' );
+
+               if ( $variesBucket ) {
+                       $bucketLabel = chr( $bucket + 65 );
+
+                       $htmlOut .= Html::openElement( 'td' );
+                       $htmlOut .= $bucketLabel;
+                       $htmlOut .= Html::closeElement( 'td' );
+               }
+
+               if ( $banner ) {
+                       // Percentage
+                       $percentage = round( $banner['allocation'] * 100, 2 );
+
+                       $htmlOut .= Html::openElement( 'td' );
+                       $htmlOut .= $this->msg( 'percent' )->numParams( 
$percentage )->escaped();
+                       $htmlOut .= Html::closeElement( 'td' );
+
+                       // Banner name
+                       $htmlOut .= Xml::openElement( 'td', array( 'valign' => 
'top' ) );
+                       // The span class is used by bannerstats.js to find 
where to insert the stats
+                       $htmlOut .= Html::openElement( 'span',
+                               array( 'class' => 
'cn-'.$banner['campaign'].'-'.$banner['name'] ) );
+                       $htmlOut .= Linker::link(
+                               $viewBanner,
+                               htmlspecialchars( $banner['name'] ),
+                               array(),
+                               array( 'template' => $banner['name'] )
+                       );
+                       $htmlOut .= Html::closeElement( 'span' );
+                       $htmlOut .= Html::closeElement( 'td' );
+
+                       // Campaign name
+                       $htmlOut .= Xml::tags( 'td', array( 'valign' => 'top' ),
+                               Linker::link(
+                                       $viewCampaign,
+                                       htmlspecialchars( $banner['campaign'] ),
+                                       array(),
+                                       array(
+                                               'method' => 'listNoticeDetail',
+                                               'notice' => $banner['campaign']
+                                       )
+                               )
+                       );
+               } else {
                        $htmlOut .= Html::openElement('td');
                        $htmlOut .= Xml::tags( 'p', null, $this->msg( 
'centralnotice-no-allocation' )->text() );
                }
 
-               $htmlOut .= Html::closeElement( 'table' );
+               // Row end
+               $htmlOut .= Html::closeElement( 'tr' );
 
                return $htmlOut;
        }
diff --git a/special/SpecialCentralNotice.php b/special/SpecialCentralNotice.php
index 241ca92..3f6cbba 100644
--- a/special/SpecialCentralNotice.php
+++ b/special/SpecialCentralNotice.php
@@ -1440,6 +1440,6 @@
                        $txt = $this->getContext()->getLanguage()->listToText( 
$inverse );
                        return $this->getContext()->msg( 
'centralnotice-all-except', $txt )->text();
                }
-               return $this->getContext()->getLanguage()->listToText( $list );
+               return $this->getContext()->getLanguage()->listToText( 
array_values( $list ) );
        }
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/55012
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Id10b191bd46ce8c6776506644e4e20d983a6380e
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/CentralNotice
Gerrit-Branch: master
Gerrit-Owner: Adamw <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to