jenkins-bot has submitted this change and it was merged.
Change subject: Add BannerChoiceDataProvider
......................................................................
Add BannerChoiceDataProvider
Add the BannerChoiceDataProvider class, part of a series of changes
to shift banner selection partly to the client. This change itself
does not modify CN functionality.
Adds the global variable $wgCentralNoticeInfrastructureId for
setting the wiki ID of the infrastructure wiki so that its DB may be
queried directly.
Change-Id: Ib005d154841019050f1a228bced7e97691bb3413
---
M CentralNotice.hooks.php
M CentralNotice.php
A includes/BannerChoiceDataProvider.php
M includes/CNDatabase.php
4 files changed, 315 insertions(+), 4 deletions(-)
Approvals:
Awight: Looks good to me, approved
jenkins-bot: Verified
diff --git a/CentralNotice.hooks.php b/CentralNotice.hooks.php
index b006246..df9dce8 100644
--- a/CentralNotice.hooks.php
+++ b/CentralNotice.hooks.php
@@ -69,6 +69,7 @@
$wgAutoloadClasses[ 'BannerMessage' ] = $includeDir .
'BannerMessage.php';
$wgAutoloadClasses[ 'BannerChooser' ] = $includeDir .
'BannerChooser.php';
$wgAutoloadClasses[ 'BannerRenderer' ] = $includeDir .
'BannerRenderer.php';
+ $wgAutoloadClasses[ 'BannerChoiceDataProvider' ] = $includeDir .
'BannerChoiceDataProvider.php';
$wgAutoloadClasses[ 'Campaign' ] = $includeDir . 'Campaign.php';
$wgAutoloadClasses[ 'CampaignLog' ] = $includeDir . 'CampaignLog.php';
$wgAutoloadClasses[ 'GeoTarget' ] = $includeDir . 'GeoTarget.php';
diff --git a/CentralNotice.php b/CentralNotice.php
index 76e240d..100dfb0 100644
--- a/CentralNotice.php
+++ b/CentralNotice.php
@@ -85,6 +85,10 @@
// For example 'http://meta.wikimedia.org/w/index.php'
$wgCentralPagePath = false;
+// The wiki ID for direct database queries on the infrastructure wiki database.
+// Leave this set to false to use the Web API instead.
+$wgCentralNoticeInfrastructureId = false;
+
// Enable the loader itself
// Allows to control the loader visibility, without destroying infrastructure
// for cached content
diff --git a/includes/BannerChoiceDataProvider.php
b/includes/BannerChoiceDataProvider.php
new file mode 100644
index 0000000..23df8a5
--- /dev/null
+++ b/includes/BannerChoiceDataProvider.php
@@ -0,0 +1,301 @@
+<?php
+
+/***
+ * Provides a set of campaign and banner choices based on allocations for a
+ * given project, language and anonymous/logged-in status.
+ */
+class BannerChoiceDataProvider {
+
+ /**
+ * Query the default DB.
+ */
+ const USE_DEFAULT_DB = 0;
+
+ /**
+ * Query the infrastructure DB using the wiki ID in
+ * $wgCentralNoticeInfrastructureId
+ */
+ const USE_INFRASTRUCTURE_DB = 1;
+
+ const LOGGED_IN = 0;
+ const ANONYMOUS = 1;
+
+ protected $project;
+ protected $language;
+ protected $status;
+ protected $whichDb;
+
+ /**
+ * @param string $project The project to get choices for
+ * @param string $language The language to get choices for
+ * @param int $status Anonymous/logged-in status to get choices for.
Can be
+ * BannerChoiceDataProvider::LOGGED_IN or
+ * BannerChoiceDataProvider::ANONYMOUS.
+ */
+ public function __construct( $project, $language, $status,
+ $whichDb=self::USE_DEFAULT_DB ) {
+
+ $this->project = $project;
+ $this->language = $language;
+ $this->status = $status;
+ $this->whichDb = $whichDb;
+ }
+
+ /**
+ * Get a data structure with the allocation choices.
+ *
+ * @return array A structure of arrays. The outer array contains
associative
+ * arrays that represent campaigns. One campaign property is
'banners',
+ * which has as its value an array of asociative arrays that represent
+ * banners. Note that only some properties of campaigns and banners
+ * are provided.
+ */
+ public function getChoices() {
+ global $wgCentralNoticeInfrastructureId;
+
+ // For speed, we'll do our own queries instead of using methods
in
+ // Campaign and Banner.
+
+ switch ( $this->whichDb ) {
+ case self::USE_DEFAULT_DB:
+ $wikiId = false;
+ break;
+
+ case self::USE_INFRASTRUCTURE_DB:
+ $wikiId = $wgCentralNoticeInfrastructureId;
+ break;
+
+ default:
+ throw new MWException( $this->whichDb . 'is not
a valid constant '
+ . 'for selecting a DB for
BannerChoiceDataProvider.' );
+ }
+
+ // Note: CNDatabase can't guarantee that we get the slave
connection
+ $dbr = wfGetDB( DB_SLAVE, $wikiId );
+
+ // Set up conditions
+ $quotedNow = $dbr->addQuotes( $dbr->timestamp() );
+ $conds = array(
+ 'cn_notices.not_start <= ' . $quotedNow,
+ 'cn_notices.not_end >= ' . $quotedNow,
+ 'cn_notices.not_enabled' => 1,
+ 'cn_notices.not_archived' => 0,
+ 'cn_notice_projects.np_project' => $this->project,
+ 'cn_notice_languages.nl_language' => $this->language
+ );
+
+ // Set the user status condition
+ switch ( $this->status ) {
+ case self::LOGGED_IN:
+ $conds['cn_templates.tmp_display_account'] = 1;
+ break;
+
+ case self::ANONYMOUS:
+ $conds['cn_templates.tmp_display_anon'] = 1;
+ break;
+
+ default:
+ throw new MWException( $this->status . 'is not
a valid status '
+ . 'for BannerChoiceDataProvider.' );
+ }
+
+ // Query campaigns and banners at once
+ $dbRows = $dbr->select(
+ array(
+ 'cn_notices',
+ 'cn_assignments',
+ 'cn_templates',
+ 'cn_notice_projects',
+ 'cn_notice_languages'
+ ),
+ array(
+ 'cn_notices.not_id',
+ 'cn_notices.not_name',
+ 'cn_notices.not_start',
+ 'cn_notices.not_end',
+ 'cn_notices.not_preferred',
+ 'cn_notices.not_throttle',
+ 'cn_notices.not_geo',
+ 'cn_notices.not_buckets',
+ 'cn_assignments.tmp_weight',
+ 'cn_assignments.asn_bucket',
+ 'cn_templates.tmp_id',
+ 'cn_templates.tmp_name',
+ 'cn_templates.tmp_category'
+ ),
+ $conds,
+ __METHOD__,
+ array(),
+ array(
+ 'cn_assignments' => array(
+ 'INNER JOIN',
'cn_notices.not_id = cn_assignments.not_id'
+ ),
+ 'cn_templates' => array(
+ 'INNER JOIN',
'cn_assignments.tmp_id = cn_templates.tmp_id'
+ ),
+ 'cn_notice_projects' => array(
+ 'INNER JOIN',
'cn_notices.not_id = cn_notice_projects.np_notice_id'
+ ),
+ 'cn_notice_languages' => array(
+ 'INNER JOIN',
'cn_notices.not_id = cn_notice_languages.nl_notice_id'
+ )
+ )
+ );
+
+ // Pare it down into a nicer data structure and prepare the
next queries.
+ // We'll create a structure with keys that are useful for
piecing the
+ // data together. But before returning it, we'll change
associative
+ // arrays to indexed ones at levels where the keys are not
needed by the
+ // client.
+ $choices = array();
+ $bannerIds = array();
+ $assignmentKeysByBannerIdAndCampaignId = array();
+
+ foreach ( $dbRows as $dbRow ) {
+
+ $campaignId = $dbRow->not_id;
+ $bannerId = $dbRow->tmp_id;
+ $bucket = $dbRow->asn_bucket;
+
+ // The first time we see any campaign, create the
corresponding
+ // outer K/V entry. The campaign-specific properties
should be
+ // repeated on every row for any campaign. Note that
these
+ // keys don't make it into data structure we return.
+ if ( !isset ( $choices[$campaignId] ) ) {
+ $choices[$campaignId] = array(
+ 'name' => $dbRow->not_name,
+ 'start' => $dbRow->not_start,
+ 'end' => $dbRow->not_end,
+ 'preferred' => intval(
$dbRow->not_preferred ),
+ 'throttle' => intval(
$dbRow->not_throttle ),
+ 'bucket_count' => intval(
$dbRow->not_buckets ),
+ 'geotargetted' => (bool)
$dbRow->not_geo,
+ 'banners' => array()
+ );
+ }
+
+ // A temporary assignment key so we can get back to
this part of the
+ // data structure quickly and add in devices.
+ $assignmentKey = $bannerId . ':' . $bucket;
+
+ $choices[$campaignId]['banners'][$assignmentKey] =
array(
+ 'name' => $dbRow->tmp_name,
+ 'bucket' => intval( $bucket ),
+ 'weight' => intval( $dbRow->tmp_weight ),
+ 'category' => $dbRow->tmp_category,
+ 'devices' => array() // To be filled by the
last query
+ );
+
+ $bannerIds[] = $bannerId;
+
+ // Add to the index so we can get back here.
+ // Note that PHP creates arrays here as needed.
+
$assignmentKeysByBannerIdAndCampaignId[$bannerId][$campaignId][] =
+ $assignmentKey;
+ }
+
+ // If there's nothing, return the empty array now
+ if ( count ( $choices ) === 0 ) {
+ return $choices;
+ }
+
+ // Fetch countries.
+ // We have to eliminate notices that are not geotargetted,
since they
+ // may have residual data in the cn_notice_countries table.
+ $dbRows = $dbr->select(
+ array(
+ 'cn_notices',
+ 'cn_notice_countries'
+ ),
+ array(
+ 'cn_notices.not_id',
+ 'cn_notice_countries.nc_country'
+ ),
+ array (
+ 'cn_notices.not_geo' => 1,
+ 'cn_notices.not_id' => array_keys( $choices )
+ ),
+ __METHOD__,
+ array(),
+ array(
+ 'cn_notice_countries' => array(
+ 'INNER JOIN', 'cn_notices.not_id =
cn_notice_countries.nc_notice_id'
+ )
+ )
+ );
+
+ // Add countries to our data structure.
+ // Note that PHP creates an empty array for countries as needed.
+ foreach ( $dbRows as $dbRow ) {
+ $choices[$dbRow->not_id]['countries'][] =
$dbRow->nc_country;
+ }
+
+ // Fetch the devices
+ $dbRows = $dbr->select(
+ array(
+ 'cn_template_devices',
+ 'cn_known_devices'
+ ),
+ array(
+ 'cn_template_devices.tmp_id',
+ 'cn_known_devices.dev_name'
+ ),
+ array(
+ 'cn_template_devices.tmp_id' => $bannerIds
+ ),
+ __METHOD__,
+ array(),
+ array(
+ 'cn_known_devices' => array(
+ 'INNER JOIN',
'cn_template_devices.dev_id = cn_known_devices.dev_id'
+ )
+ )
+ );
+
+ // Add devices to the data structure.
+ foreach ( $dbRows as $dbRow ) {
+
+ $bannerId = $dbRow->tmp_id;
+
+ // Traverse the data structure to add in devices
+
+ $assignmentKeysByCampaignId =
+
$assignmentKeysByBannerIdAndCampaignId[$bannerId];
+
+ foreach ( $assignmentKeysByCampaignId
+ as $campaignId => $assignmentKeys ) {
+
+ foreach ( $assignmentKeys as $assignmentKey ) {
+
$choices[$campaignId]['banners'][$assignmentKey]['devices'][] =
+ $dbRow->dev_name;
+ }
+ }
+ }
+
+ // Make arrays that are associative into plain indexed ones,
since the
+ // keys aren't used by the clients.
+ // Also make very sure we don't have duplicate devices or
countries.
+
+ $choices = array_values( $choices );
+
+ $uniqueDevFn = function ( $b ) {
+ $b['devices'] = array_unique( $b['devices'] );
+ return $b;
+ };
+
+ $fixCampaignPropsFn = function ( $c ) use ( $uniqueDevFn ) {
+
+ $c['banners'] = array_map( $uniqueDevFn, array_values(
$c['banners'] ) );
+
+ if ( $c['geotargetted'] ) {
+ $c['countries'] = array_unique( $c['countries']
);
+ }
+
+ return $c;
+ };
+
+ $choices = array_map( $fixCampaignPropsFn, $choices );
+
+ return $choices;
+ }
+}
\ No newline at end of file
diff --git a/includes/CNDatabase.php b/includes/CNDatabase.php
index 9e5e905..0b8292d 100644
--- a/includes/CNDatabase.php
+++ b/includes/CNDatabase.php
@@ -5,11 +5,16 @@
*/
class CNDatabase {
/**
- * Gets a database object. Will be the master if the user is logged in.
+ * Gets a database object. Will be the DB_MASTER if the user has the
+ * centralnotice-admin right. NOTE: $force is ignored for such users.
*
- * @param int|bool $force If false will return a DB master/slave
based on users permissions.
- * Set to DB_MASTER or DB_SLAVE to force
that type.
- * @param string|bool $wiki Wiki database to connect to, if false
will be the Infrastructure DB
+ * @param int|bool $force If false will return a DB master/slave
based
+ * on users permissions. Set to DB_MASTER or
+ * DB_SLAVE to force that type for users
that
+ * don't have the centralnotice-admin right.
+ *
+ * @param string|bool $wiki Wiki database to connect to, if false
will be
+ * the infrastructure DB.
*
* @return DatabaseBase
*/
--
To view, visit https://gerrit.wikimedia.org/r/170843
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ib005d154841019050f1a228bced7e97691bb3413
Gerrit-PatchSet: 10
Gerrit-Project: mediawiki/extensions/CentralNotice
Gerrit-Branch: master
Gerrit-Owner: AndyRussG <[email protected]>
Gerrit-Reviewer: AndyRussG <[email protected]>
Gerrit-Reviewer: Awight <[email protected]>
Gerrit-Reviewer: Ejegg <[email protected]>
Gerrit-Reviewer: Mwalker <[email protected]>
Gerrit-Reviewer: Ssmith <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits