Developed a custom module for injecting Prebid.JS and Amazon header bidding components to improve ad-based earnings from the Google Ad marketplace.
Completion Date
Platform(s)/Language(s)
Code Snippet
/** * @file * Sets up necessary config settings & scripts for Header Bidding. */ /** * Implements hook_preprocess_HOOK(). */ function mm_prebid_preprocess_html(&$vars) { if (!in_array('page-admin', $vars['classes_array'])) { // Only call for prebidding on public-facing pages. MmPrebid::preprocessHeaderBidding($vars['page']); } }
/** * @file * Classes for MM Prebid. */ /** * MM Prebid class. */ class MmPrebid { /** * Add in Prebid.js config code to all page headers. * * @param array $page * Associated array of page regions and content. */ public static function preprocessHeaderBidding(array $page) { /* Set `$pbjs_filename` to the compiled pbjs file downloaded from * http://prebid.org/download.html and saved to `js/prebid1.xx.y.js`. */ $pbjs_filename = 'prebid1.36.0.js'; $headerbid_elems = array(); // Get headerbid params to parse for Prebid.JS and Amazon. $headerbid_units = mm_api_get_siteconfig_value('headerbidding'); if (isset($headerbid_units['prebidjs'])) { // Loop thru the ad slots for PBJS & match them up with dfp id divs. foreach ($headerbid_units['prebidjs'] as $slot => $networks) { if ($slot == 'OoyalaPreRoll') { // If the slot is for Ooyala, then assign it a hardcoded id value. $dfp_id = 'jwplayer-container-1'; } else { $dfp_id = mm_api_get_siteconfig_value('dfp', $slot); } if (!empty($dfp_id) && MmPrebid::arrayValueSearch($page, $dfp_id, 0)) { // If the dfp id is present on the current page, then add it to // $headerbid_elems. $headerbid_elems['prebidjs'][] = array( 'dfpId' => $dfp_id, 'placementIds' => $networks, ); } } // If any PBJS ids were found on the current page, then load the Prebid.JS // files. if (isset($headerbid_elems['prebidjs'])) { // First, add additional config settings for PBJS to $headerbid_elems. $headerbid_elems['extra_config']['prebidjs'] = $headerbid_units['extra_config']['prebidjs'] ?? []; drupal_add_js(drupal_get_path('module', 'mm_prebid') . '/js/' . $pbjs_filename, [ 'weight' => -6, ]); drupal_add_js(drupal_get_path('module', 'mm_prebid') . '/js/prebid-config.js', [ 'weight' => -5, ]); } } if (isset($headerbid_units['amazon'])) { // Loop thru the ad slots for APS & match them up with dfp id divs. foreach ($headerbid_units['amazon'] as $slot) { if ($dfp_id = mm_api_get_siteconfig_value('dfp', $slot)) { // If the dfp id is present on the current page, then add it to // $headerbid_elems. $is_dfp_id_present = MmPrebid::arrayValueSearch($page, $dfp_id, 0); if ($is_dfp_id_present) { $headerbid_elems['amazon'][] = array( 'dfpId' => $dfp_id, ); } } } // If any Amazon ids were found on the current page, then load the APS JS // files. if (isset($headerbid_elems['amazon'])) { // First, add additional config settings for PBJS to $headerbid_elems. $headerbid_elems['extra_config']['amazon'] = $headerbid_units['extra_config']['amazon'] ?? []; drupal_add_js(drupal_get_path('module', 'mm_prebid') . '/js/apstag.js', [ 'weight' => -4, ]); drupal_add_js(drupal_get_path('module', 'mm_prebid') . '/js/apstag-config.js', [ 'weight' => -3, ]); } } // If any headerbid ids of any kind were found on the current page, the load // the generic headerbid config JS file. if (count($headerbid_elems)) { drupal_add_js(['mm_prebid' => ['headerbidElems' => $headerbid_elems]], [ 'type' => 'setting', 'weight' => -8, ]); drupal_add_js(drupal_get_path('module', 'mm_prebid') . '/js/headerbid-config.js', [ 'weight' => -7, ]); } } /** * Recursively search multidimensional array values for occurrence of a string. * * @param mixed $data * Multidimensional associated array to search. * @param string $search_string * Value for which to search. * @param int $max_nest_level * Maximum number of nested levels to recursively search an array. * @param int $nest_level * Current nesting level being searched. * @param string $result_key * Recursively-built array key index pointing to search result. * * @return string * Array key index if string is found. '' otherwise. */ public static function arrayValueSearch( $data, string $search_string, int $max_nest_level = 3, int $nest_level = 0, string $result_key = '' ) { if ($nest_level > $max_nest_level) { return ''; } else { foreach ($data as $key => $value) { if (is_array($value)) { $result_key .= MmPrebid::arrayValueSearch($value, $search_string, $max_nest_level, $nest_level + 1); } elseif (is_object($value)) { $result_key .= MmPrebid::arrayValueSearch($value, $search_string, $max_nest_level, $nest_level + 1); } elseif (strpos((string) $value, $search_string) !== FALSE) { $result_key .= '[' . $key . ']'; return $result_key; } if ($result_key !== '') { return '[' . $key . ']' . $result_key; } } return $result_key; } } }
/** * @file * Prebid.JS config code. * * Reference: http://jsfiddle.net/prebid/e48yrvby/1/ */ // Declare some global variables. var PREBID_TIMEOUT = 700; var pbjs = pbjs || {}; var pbjsUnits = pbjsUnits || []; var pbjsVideoUnits = pbjsVideoUnits || []; var googletag = googletag || {}; // Initialize Prebid.js array and prepare it for accepting bids. pbjs.que = pbjs.que || []; function sendAdserverRequest() { /* console.log("Invoked sendAdserverRequest(), in prebid-config.js."); */ if (pbjs.adserverRequestSent) { /* console.log(" pbjs.adserverRequestSent is already true. Calling refreshPubAds()."); */ refreshPubAds(); return; } else { /* console.log(" Sending prebid.js ad server request."); */ pbjs.adserverRequestSent = true; googletag.cmd.push(function () { pbjs.que.push(function () { pbjs.setTargetingForGPTAsync(); /* console.log(" Calling refreshPubAds() via googletag.cmd, from prebid-config.js."); */ refreshPubAds(); }); }); } /* console.log("Exiting sendAdserverRequest()."); */ } (function ($) { $(document).ready(function () { // Read in mm_dfp variables passed from php to js. var mmDfp = Drupal.settings.mm_dfp || []; var dfpSlots = mmDfp.slots || []; var dfpLen = dfpSlots.length || 0; /* ======== Prebid.JS Config Section START ======== */ // Read in mm_prebid variables passed from php to js. var pbjsElems = Drupal.settings.mm_prebid.headerbidElems.prebidjs || []; var pbjsLen = pbjsElems.length || 0; // Rubicon has additional config parameters. var rubiconConfig = Drupal.settings.mm_prebid.headerbidElems.extra_config.prebidjs.rubicon || []; // Loop thru each Prebid.JS id and grab slot properties from mm_dfp. for (var a = 0; a < pbjsLen; a++) { // If the dfp id is found in the DOM, then add the slot info to // the `pbjsUnits`. if (document.getElementById(pbjsElems[a].dfpId)) { // Search mm_dfp slots for an id that matches the prebid id. if (pbjsElems[a].dfpId == 'jwplayer-container-1') { /* console.log("Setting up videoAdUnit."); */ // The jwplayer id was found on the page, so prepare pbjsVideoUnits. // Configure autoplay based on shouldAutoplay() in jwplayer.tpl.php. if (shouldAutoplay()) { autoplayParam = 'auto_play_sound_on'; } else { autoplayParam = 'click_to_play'; } // Define the video ad unit parameters. videoAdUnit = { code: "/1006215/OoyalaPreRoll", mediaTypes: { video: { context: 'instream', playerSize: [640,480] } }, bids: [{ bidder: 'appnexus', params: { placementId: pbjsElems[a].placementIds.appnexus, video: { skippable: true, playback_methods: [autoplayParam] } } }] }; pbjsVideoUnits.push(videoAdUnit); } else { // Otherwise, loop thru traditional placement ids. for (var d = 0; d < dfpLen; d++) { if (dfpSlots[d].element === pbjsElems[a].dfpId) { var pbjsBids = []; // Loop thru the element's bidders to set up placement ids. for (var network in pbjsElems[a].placementIds) { if (pbjsElems[a].placementIds.hasOwnProperty(network)) { // This is javascript's way of doing "foreach()". if (network == 'ix') { // With IndexExchange, each ad size must be added as a unique // bidder entry. for (var s = 0; s < dfpSlots[d].sizes.length; s++) { var pbjsParams = { siteId: pbjsElems[a].placementIds[network], size: dfpSlots[d].sizes[s] } pbjsBids.push({ bidder: network, params: pbjsParams }); } } else { // Just add one bidder entry for other ad newtorks. if (network == 'appnexus') { var pbjsParams = { placementId: pbjsElems[a].placementIds[network] } } else if (network == 'rubicon') { var pbjsParams = { accountId: rubiconConfig.account_id, siteId: rubiconConfig.site_id, zoneId: pbjsElems[a].placementIds[network] } } pbjsBids.push({ bidder: network, params: pbjsParams }); } } } // Add slot info to `pbjsUnits` for Prebid.JS. pbjsUnits.push({ // Example: '12345/box-1'. code: dfpSlots[d].slot, mediaTypes: { banner: { // Example: [[300,250], [300,600]]. sizes: dfpSlots[d].sizes } }, bids: pbjsBids }); break; } } } } } setTimeout(function () { /* console.log("Prebid Timeout reached. Calling sendAdServerRequest()."); */ sendAdserverRequest(); }, PREBID_TIMEOUT); /* ======== Prebid.js (AppNexus) Config Section END ======== */ /* ======= Prebid.js Boilerplate Section START. No Need to Edit. ======= */ if (pbjsUnits.length > 0 || pbjsVideoUnits.length > 0) { // Only request bids if Prebid.JS units are present. /* console.log("PBJS slots found. Calling disableInitialLoading()."); */ disableInitialLoading(); // Special handling for jwplayer ad units. if (pbjsVideoUnits.length > 0) { pbjs.que.push(function () { /* console.log(" pbjsVideoUnits found. Adding pbjsVideoUnits to the pbjs que."); */ pbjs.setConfig({ cache: { url: 'https://prebid.adnxs.com/pbc/v1/cache' }, mediaTypePriceGranularity: { // Set a custom price granularity for video ads. video: { buckets: [{ precision: 2, min: 10, max: 40, increment: 0.1 }] } } }); /* console.log(" Calling pbjs.addAdUnits(pbjsVideoUnits)."); */ pbjs.addAdUnits(pbjsVideoUnits); /* console.log(" Calling pbjs.requestBids() with extra video params."); */ pbjs.requestBids({ bidsBackHandler: function (bids) { var videoUrlObj = { adUnit: videoAdUnit, url: vastTag }; /* console.log(" Setting videoUrl equal to pbjs.adServers.dfp.buildVideoUrl(videoUrlObj) via pbjs."); */ videoUrl = pbjs.adServers.dfp.buildVideoUrl(videoUrlObj); /* console.log(" videoUrl: " + videoUrl); */ } }); }); } else if (typeof invokeVideoPlayer === 'function') { /* console.log(" pbjsVideoUnits is empty, but jwplayer is still present."); */ /* console.log(" Calling invokeVideoplayer() with no videoUrl from prebid-config.js."); */ invokeVideoPlayer(); } if (pbjsUnits.length > 0) { pbjs.que.push(function () { /* console.log(" pbjsUnits found. Calling pbjs.addAdUnits(pbjsUnits)."); */ pbjs.addAdUnits(pbjsUnits); }); } // Send the ad server request at the very end. pbjs.que.push(function () { /* console.log(" Calling pbjs.requestBids() via pbjs.que, from prebid-config.js."); */ pbjs.requestBids({ bidsBackHandler: sendAdserverRequest }); }); } else { /* console.log("No PBJS units present. Just set pbjs.adserverRequestSent to true."); */ pbjs.adserverRequestSent = true; } /* ======== Prebid.js Boilerplate Section END ======== */ }); }(jQuery));