dfp.module 30.8 KB
Newer Older
bleen's avatar
bleen committed
1 2
<?php

bleen's avatar
bleen committed
3
define('DFP_GOOGLE_TAG_SERVICES_URL', 'www.googletagservices.com/tag/js/gpt.js');
bleen's avatar
bleen committed
4
define('DFP_GOOGLE_SHORT_TAG_SERVICES_URL', 'pubads.g.doubleclick.net/gampad');
5
define('DFP_TOKEN_CACHE', 'dfp:tag_token_results');
bleen's avatar
bleen committed
6

bleen's avatar
bleen committed
7 8 9 10 11 12 13 14 15 16 17 18
/**
 * Implements hook_help().
 */
function dfp_help($path, $arg) {
  switch ($path) {
    case 'admin/help#dfp':
      $output = '<p>' . t('The Doubleclick For Publishers (DFP) module allows you to integrate Google Publisher Tags onto your site.') . '</p>';
      $output .= '<p>' . t('This module provides you with a general settings form as well as the ability to create a tag (with all its associated data) in the database. You can display your ads as blocks, or add a simple bit of php to your tpl.php file(s) within your theme to indicate where specific tags should be displayed.') . '</p>';
      return $output;
  }
}

bleen's avatar
bleen committed
19 20 21 22 23
/**
 * Implements hook_permission().
 */
function dfp_permission() {
  return array(
bleen's avatar
bleen committed
24 25
    'administer DFP' => array(
      'title' => t('Administer Doubleclick for Publisher ads'),
bleen's avatar
bleen committed
26 27 28 29
      'description' => t('Users can create, edit, and delete Doubleclick for Publishers (dfp) ad tags and configure how and when they should be displayed.'),
    ),
  );
}
bleen's avatar
bleen committed
30 31 32 33

/**
 * Implements hook_theme().
 */
34
function dfp_theme($existing, $type, $theme, $path) {
bleen's avatar
bleen committed
35
  $theme_hooks = array(
36 37 38
    'dfp_tag' => array(
      'variables' => array(
        'tag' => NULL,
39
        'slug' => NULL,
40 41 42
      ),
      'template' => 'theme/dfp_tag',
    ),
bleen's avatar
bleen committed
43 44 45 46 47 48
    'dfp_short_tag' => array(
      'variables' => array(
        'tag' => NULL,
      ),
      'template' => 'theme/dfp_short_tag',
    ),
49 50 51 52
    'dfp_target_settings' => array(
      'render element' => 'form',
      'file' => 'dfp.admin.inc',
    ),
53 54 55 56
    'dfp_breakpoint_settings' => array(
      'render element' => 'form',
      'file' => 'dfp.admin.inc',
    ),
57
    'dfp_adsense_color_settings' => array(
bleen's avatar
bleen committed
58 59 60
      'render element' => 'form',
      'file' => 'dfp.admin.inc',
    ),
61 62 63 64
    'dfp_size_settings' => array(
      'render element' => 'form',
      'file' => 'dfp.admin.inc',
    ),
bleen's avatar
bleen committed
65 66 67 68 69
  );

  return $theme_hooks;
}

bleen's avatar
bleen committed
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
/**
 * Implements hook_menu().
 */
function dfp_menu() {
  $items = array();

  $items['admin/structure/dfp_ads/settings'] = array(
    'title' => 'Global DFP Settings',
    'type' => MENU_LOCAL_TASK,
    'description' => "Configure your site-wide DFP settings.",
    'page callback' => 'drupal_get_form',
    'page arguments' => array('dfp_admin_settings'),
    'access arguments' => array('administer DFP'),
    'file' => 'dfp.admin.inc',
    'weight' => 5,
  );
  $items['admin/structure/dfp_ads/test_page'] = array(
    'title' => 'DFP Test Page',
    'type' => MENU_LOCAL_TASK,
    'description' => "View all your DFP tags on a single page",
90
    'page callback' => 'dfp_adtest_page',
bleen's avatar
bleen committed
91
    'access arguments' => array('administer DFP'),
92
    'file' => 'dfp.adtest.inc',
bleen's avatar
bleen committed
93 94 95 96 97 98
    'weight' => 10,
  );

  return $items;
}

99 100 101 102 103 104 105 106
/**
 * Implements hook_menu_alter().
 */
function dfp_menu_alter(&$items) {
  $items['admin/structure/dfp_ads/list/%ctools_export_ui/edit']['context'] = MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE;
  $items['admin/structure/dfp_ads/list/%ctools_export_ui/edit']['title'] = t('Edit DFP ad tag');
}

107 108 109 110
/**
 * Implements hook_block_info().
 */
function dfp_block_info() {
111
  $tags = dfp_tag_load_all();
112
  $blocks = array();
113 114
  $hashes = array();

115
  foreach ($tags as $tag) {
116 117 118 119 120 121 122 123 124 125
    if ($tag->block) {
      // The block table chokes when the delta is more than 32 characters. To
      // solve this we create a hash of the machine name when needed.
      if (drupal_strlen($tag->machinename) >= 32) {
        $delta = md5($tag->machinename);
        $hashes[$delta] = $tag->machinename;
      }
      else {
        $delta = $tag->machinename;
      }
126

127
      $blocks[$delta]['info'] = t('DFP tag: @slotname', array('@slotname' => $tag->slot));
128 129
      $blocks[$delta]['cache'] = DRUPAL_CACHE_PER_PAGE;
    }
130
  }
131 132 133 134 135 136 137

  // Only save hashes if they have changed.
  $old_hashes = variable_get('dfp_block_hashes', array());
  if ($hashes != $old_hashes) {
    variable_set('dfp_block_hashes', $hashes);
  }

138 139 140 141 142 143 144
  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function dfp_block_view($delta) {
145
  $block = array();
146 147

  // If this is 32, this should be an md5 hash.
148
  if (drupal_strlen($delta) == 32) {
149 150 151 152 153 154
    $hashes = variable_get('dfp_block_hashes', array());
    if (!empty($hashes[$delta])) {
      $delta = $hashes[$delta];
    }
  }

155 156
  $tag = dfp_tag_load($delta);

157
  if (empty($tag->disabled)) {
158
    $block['content'] = dfp_tag($delta);
159 160 161
    $block['content']['#contextual_links'] = array(
      'dfp' => array('admin/structure/dfp_ads/list', array($delta, 'edit'))
    );
162
  }
163 164 165 166

  return $block;
}

167 168 169
/**
 * Implements hook_entity_view().
 */
170
function dfp_entity_view($entity, $type, $view_mode, $langcode) {
171
  $dfp_targeting_terms = &drupal_static('dfp_entity_targeting_terms', array());
172

173
  if (variable_get('dfp_enable_ad_categories', 0) && $view_mode == 'full') {
174 175 176 177 178 179 180 181 182
    // If this entity is itself a taxonomy term add it to the
    // dfp_targetting_terms array. Check it to see if a DFP Ad Category
    // has been assigned to it. If so, add that term to the array instead. Note
    // that we check all types of entities here because any fieldable entity can
    // have a taxonomy term reference field attached to it.
    if ($type == 'taxonomy_term') {
      $dfp_targeting_terms[] = _dfp_get_ad_category($entity, TRUE);
    }

183
    // Find all taxonomy terms attached to the given entity and add them to the
184 185
    // dfp_targeting_terms array. Check each term to see if a DFP Ad Category
    // has been assigned to it. If so, add that term to the array instead.
186
    foreach (element_children($entity->content) as $key) {
187
      if (isset($entity->content[$key]['#field_type']) && $entity->content[$key]['#field_type'] == 'taxonomy_term_reference') {
188
        $terms = field_view_field($type, $entity, $key);
189 190 191 192 193
        if (isset($terms['#items']) && is_array($terms['#items'])) {
          foreach ($terms['#items'] as $item) {
            if (array_key_exists('taxonomy_term', $item)) {
              $dfp_targeting_terms[] = _dfp_get_ad_category($item['taxonomy_term'], TRUE);
            }
194
          }
195 196 197
        }
      }
    }
198 199

    $dfp_targeting_terms = array_unique($dfp_targeting_terms);
200 201 202
  }
}

bleen's avatar
bleen committed
203 204 205 206 207
/**
 * Implements hook_ctools_plugin_directory().
 */
function dfp_ctools_plugin_directory($module, $type) {
  // Load the export_ui plugin.
208 209
  if ($type == 'export_ui' || $type == 'content_types') {
    return 'plugins/' . $type;
bleen's avatar
bleen committed
210 211 212
  }
}

213 214 215 216 217 218 219
/**
 * Implements hook_context_registry().
 */
function dfp_context_registry() {
  return array(
    'reactions' => array(
      'dfp_tags' => array(
220
        'title' => t('DFP Tags'),
221 222
        'plugin' => 'dfp_context_reaction_tags',
      ),
223 224 225 226
      'dfp_outofpage' => array(
        'title' => t('DFP Out of page'),
        'plugin' => 'dfp_context_reaction_outofpage',
      ),
227
      'dfp_settings' => array(
228
        'title' => t('DFP Variables'),
229 230
        'plugin' => 'dfp_context_reaction_settings',
      ),
231 232 233 234
      'dfp_adunit' => array(
        'title' => t('DFP AdUnit'),
        'plugin' => 'dfp_context_reaction_adunit',
      ),
235 236 237 238
      'dfp_adsize' => array(
        'title' => t('DFP Sizes'),
        'plugin' => 'dfp_context_reaction_sizes',
      ),
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
    ),
  );
}

/**
 * Implements hook_context_plugins().
 */
function dfp_context_plugins() {
  $plugins = array();
  $plugins['dfp_context_reaction_tags'] = array(
    'handler' => array(
      'path' => drupal_get_path('module', 'dfp') . '/plugins/contexts',
      'file' => 'dfp_context_reaction_tags.inc',
      'class' => 'dfp_context_reaction_tags',
      'parent' => 'context_reaction',
    ),
  );
256 257 258 259 260 261 262 263
  $plugins['dfp_context_reaction_outofpage'] = array(
    'handler' => array(
      'path' => drupal_get_path('module', 'dfp') . '/plugins/contexts',
      'file' => 'dfp_context_reaction_outofpage.inc',
      'class' => 'dfp_context_reaction_outofpage',
      'parent' => 'context_reaction',
    ),
  );
264 265 266 267 268 269 270 271
  $plugins['dfp_context_reaction_settings'] = array(
    'handler' => array(
      'path' => drupal_get_path('module', 'dfp') . '/plugins/contexts',
      'file' => 'dfp_context_reaction_settings.inc',
      'class' => 'dfp_context_reaction_settings',
      'parent' => 'context_reaction',
    ),
  );
272 273 274 275 276 277 278 279
  $plugins['dfp_context_reaction_adunit'] = array(
    'handler' => array(
      'path' => drupal_get_path('module', 'dfp') . '/plugins/contexts',
      'file' => 'dfp_context_reaction_adunit.inc',
      'class' => 'dfp_context_reaction_adunit',
      'parent' => 'context_reaction',
    ),
  );
280 281 282 283 284 285 286 287
  $plugins['dfp_context_reaction_sizes'] = array(
    'handler' => array(
      'path' => drupal_get_path('module', 'dfp') . '/plugins/contexts',
      'file' => 'dfp_context_reaction_sizes.inc',
      'class' => 'dfp_context_reaction_sizes',
      'parent' => 'context_reaction',
    ),
  );
288 289 290
  return $plugins;
}

bleen's avatar
bleen committed
291
/**
292
 * Implements hook_token_info().
bleen's avatar
bleen committed
293 294 295 296
 */
function dfp_token_info() {
  $type = array(
    'name' => t('DFP Ad Tags'),
297
    'description' => t('Tokens related to a given DFP ad tag.'),
bleen's avatar
bleen committed
298 299 300
    'needs-data' => 'tag',
  );

301
  $tag['slot'] = array(
bleen's avatar
bleen committed
302 303 304
    'name' => t('Slot Name'),
    'description' => t("The name of the ad slot defined by this tag."),
  );
305
  $tag['network_id'] = array(
bleen's avatar
bleen committed
306 307 308
    'name' => t("Network ID"),
    'description' => t("The unique ID provided by Google."),
  );
309
  $tag['ad_categories'] = array(
310 311 312
    'name' => t("DFP Ad Categories"),
    'description' => t("The DFP Ad Categories or uncategorized taxonomy terms attached to the entities currently being displayed to the user."),
  );
313
  $tag['url_parts'] = array(
bleen's avatar
bleen committed
314
    'name' => t("URL Parts (n)"),
315
    'description' => t('**Deprecated. See <a href="http://drupal.org/node/1812372" target="_blank">this issue<a> for alternitives.'),
bleen's avatar
bleen committed
316 317 318 319 320 321 322 323 324
  );

  return array(
    'types' => array('dfp_tag' => $type),
    'tokens' => array('dfp_tag' => $tag),
  );
}

/**
325
 * Implements hook_tokens().
bleen's avatar
bleen committed
326 327 328 329
 */
function dfp_tokens($type, $tokens, array $data = array(), array $options = array()) {
  $replacements = array();

330
  if ($type == 'dfp_tag') {
bleen's avatar
bleen committed
331 332
    foreach ($tokens as $name => $original) {
      switch ($name) {
333
        case 'slot':
334 335 336
          if (!empty($data['tag'])) {
            $replacements[$original] = check_plain($data['tag']->slot);
          }
bleen's avatar
bleen committed
337 338
          break;

339
        case 'network_id':
bleen's avatar
bleen committed
340 341 342
          $replacements[$original] = check_plain(variable_get('dfp_network_id', ''));
          break;

343
        case 'ad_categories':
344
          $term_names = &drupal_static('dfp_entity_targeting_terms', array());
345 346
          $replacements[$original] = implode(',', $term_names);
          break;
bleen's avatar
bleen committed
347 348 349
      }
    }

350
    if ($created_tokens = token_find_with_prefix($tokens, 'url_parts')) {
bleen's avatar
bleen committed
351 352 353 354 355 356 357 358 359 360
      foreach ($created_tokens as $name => $original) {
        $url_parts = explode('/', $_GET['q']);
        $replacements[$original] = implode('/', array_slice($url_parts, 0, $name));
      }
    }
  }

  return $replacements;
}

361
/**
362
 * Implements preprocess_html().
363
 */
364
function dfp_preprocess_html($variables) {
365 366 367
  // Add the header js here so that enough information has been loaded for
  // tokens to work properly.
  _dfp_js_global_settings();
368 369 370
}


bleen's avatar
bleen committed
371 372 373 374 375 376 377
/**
 * Return a render array for the tag specified by machinename.
 */
function dfp_tag($machinename) {
  $tag = dfp_tag_load($machinename);
  $render_array = array();

378
  if (!$tag) {
379
    watchdog('dfp', 'Unknown ad tag %machinename passed to dfp_tag().', array('%machinename' => $machinename), WATCHDOG_WARNING);
380 381
  }
  else {
382
    $tag->slug = dfp_format_slug($tag->slug);
383
    $slug_placement = variable_get('dfp_slug_placement', 0);
384 385 386 387 388 389 390 391 392 393

    if (!empty($tag)) {
      $render_array = array(
        'dfp_wrapper' => array(
          '#type' => 'container',
          '#attributes' => array(
            'id' => $tag->wrapper_id,
            'class' => array(
              'dfp-tag-wrapper',
            ),
bleen's avatar
bleen committed
394
          ),
395 396 397
          'tag' => array(
            '#theme' => $tag->short_tag ? 'dfp_short_tag' : 'dfp_tag',
            '#tag' => $tag,
bleen's avatar
bleen committed
398 399 400
          ),
        ),
      );
401
      if (!empty($tag->slug) && $slug_placement == 0) {
402 403 404 405 406 407 408 409 410 411 412 413 414
        $render_array['dfp_wrapper']['slug_wrapper'] = array(
          '#type' => 'container',
          '#attributes' => array(
            'class' => array(
              'slug',
            ),
          ),
          'slug' => array(
            '#markup' => $tag->slug,
          ),
          '#weight' => -1,
        );
      }
bleen's avatar
bleen committed
415 416 417 418 419 420
    }
  }

  return $render_array;
}

bleen's avatar
bleen committed
421 422
/**
 * Load function.
bleen's avatar
bleen committed
423
 *
bleen's avatar
bleen committed
424
 * @param string $machinename
bleen's avatar
bleen committed
425
 *
bleen's avatar
bleen committed
426
 * @return object
bleen's avatar
bleen committed
427
 *
bleen's avatar
bleen committed
428
 */
bleen's avatar
bleen committed
429
function dfp_tag_load($machinename) {
bleen's avatar
bleen committed
430 431
  ctools_include('export');

432
  $tags = &drupal_static(__FUNCTION__, array());
bleen's avatar
bleen committed
433

bleen's avatar
bleen committed
434 435 436 437 438 439 440 441 442 443 444 445
  if (!isset($tags[$machinename])) {
    // Load the tag.
    $result = ctools_export_load_object('dfp_tags', 'names', array($machinename));
    if (isset($result[$machinename])) {
      $tag = $result[$machinename];
    }
    else {
      return NULL;
    }

    // Store the original tag. This is used by the tag edit form.
    $tag->raw = clone $tag;
bleen's avatar
bleen committed
446

bleen's avatar
bleen committed
447
    // Allow modules to alter the raw tag object.
448 449
    drupal_alter('dfp_tag_load', $tag);

450 451 452 453 454
    // Move the settings out of the settings array.
    foreach ($tag->settings as $key => $val) {
      $tag->{$key} = $val;
    }

455
    // Configure this tag based on the defined settings.
456 457
    $tag->wrapper_id = 'dfp-ad-' . $tag->machinename . '-wrapper';
    $tag->placeholder_id = 'dfp-ad-' . $tag->machinename;
458

459
    // Allow modules to alter the fully-loaded tag object.
460
    drupal_alter('dfp_tag', $tag);
461

bleen's avatar
bleen committed
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
    // Statically cache the fully loaded tag.
    $tags[$machinename] = $tag;
  }
  else {
    // Use the statically cached tag object.
    $tag = $tags[$machinename];
  }

  return $tag;
}

/**
 * Load all dfp ad tags.
 *
 * @param boolean $include_disabled
 *
 * @return array of tags.
 */
function dfp_tag_load_all($include_disabled = FALSE) {
481 482
  ctools_include('export');

bleen's avatar
bleen committed
483 484
  $tags = ctools_export_crud_load_all('dfp_tags');
  foreach ($tags as $key => $tag) {
bleen's avatar
bleen committed
485
    if (!$include_disabled && isset($tag->disabled) && $tag->disabled) {
bleen's avatar
bleen committed
486 487
      unset($tags[$key]);
    }
bleen's avatar
bleen committed
488
  }
bleen's avatar
bleen committed
489

bleen's avatar
bleen committed
490
  return $tags;
bleen's avatar
bleen committed
491 492
}

bleen's avatar
bleen committed
493 494 495 496 497
/**
 * Save a single tag.
 */
function dfp_tag_save(&$tag) {
  $update = (isset($tag->adid) && is_numeric($tag->adid)) ? array('adid') : array();
498
  return drupal_write_record('dfp_tags', $tag, $update);
bleen's avatar
bleen committed
499 500
}

501 502 503
/**
 * Alter a dfp tag object to integrate with the contexts module.
 */
504
function dfp_dfp_tag_load_alter(&$tag) {
505
  // Execute context reactions for each plugin.
506
  if (module_exists('context')) {
507 508 509 510 511
    $contexts = dfp_context_registry();
    foreach ($contexts['reactions'] as $key => $val) {
      if ($plugin = context_get_plugin('reaction', $key)) {
        $plugin->execute($tag);
      }
512
    }
513
  }
514 515 516 517

  // Handle ad testing.
  module_load_include('inc', 'dfp', 'dfp.adtest');
  dfp_adtest_alter_tag($tag);
518 519
}

520 521 522 523 524 525 526
/**
 * Alter the vertical tabs group in which the exportable scheduler form should
 * live.
 */
function dfp_exportable_scheduler_form_group_alter(&$group) {
  $group = 'settings';
}
527 528 529 530 531 532

/**
 * Form alter for the ctools_export_ui_edit_item_form.
 */
function dfp_form_ctools_export_ui_edit_item_form_alter(&$form, &$form_state) {
  if (arg(2) == 'context') {
bleen's avatar
bleen committed
533
    // Make sure that dfp.admin.inc is included on the context ui form to avoid
534 535
    // errors when doing an ajax submit.
    form_load_include($form_state, 'inc', 'dfp', 'dfp.admin');
536
    //array_unshift($form['#submit'], 'dps_targeting_form_submit');
537 538 539
  }
}

540 541 542 543 544 545 546 547 548 549 550
/**
 * Format the given array of values to be displayed as part of a javascript.
 *
 * @param array $variables
 *  'values' => A simple array of targeting values. Ex. val1, val2, val3
 *
 * @return string
 *   If $values had one item, then a string in the format ["val1"]. If $values
 *   has more than one item, then a string in the format ["val1","val2","val3"].
 *
 */
551
function dfp_format_targeting($targeting, $tag = '') {
552
  foreach ($targeting as $key => &$target) {
bleen's avatar
bleen committed
553
    $target['target'] = '"' . check_plain($target['target']) . '"';
554
    $target['value'] = dfp_token_replace(check_plain($target['value']), $tag, array('sanitize' => TRUE, 'clear' => TRUE));
555

556 557 558
    // The target value could be blank if tokens are used. If so, removed it.
    if (empty($target['value'])) {
      unset($targeting[$key]);
559
      continue;
560 561
    }

562 563 564
    // Allow other modules to alter the target.
    drupal_alter('dfp_target', $target);

565
    // Convert the values into an array and trim the whitespace from each value.
566
    $values = explode(',', $target['value']);
bleen's avatar
bleen committed
567
    $values = array_map('trim', $values);
568

569
    if (count($values) == 1) {
bleen's avatar
bleen committed
570
      $target['value'] = '"' . $values[0] . '"';
571 572
    }
    elseif (count($values) > 1) {
bleen's avatar
bleen committed
573
      $target['value'] = '["' . implode('","', $values) . '"]';
574
    }
575 576
  }

577
  return $targeting;
578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
}

/**
 * Format the the size of an ad tag.
 *
 * @param array $variables
 *   'size' => A string in the format 123x321,987x789.
 *
 * @return string
 *   A string in the format [456, 654] or [[123, 321], [987, 789]].
 *
 */
function dfp_format_size($size) {
  $formatted_sizes = array();

593
  $sizes = explode(',', check_plain($size));
594 595
  foreach ($sizes as $size) {
    $formatted_size = explode('x', trim($size));
596
    $formatted_sizes[] = '[' . implode(', ', $formatted_size) . ']';
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614
  }

  return count($formatted_sizes) == 1 ? $formatted_sizes[0] : '[' . implode(', ', $formatted_sizes) . ']';
}

/**
 * Format the the size of an ad tag.
 *
 * @param array $variables
 *   'slug' => A label for for this particular ad tag.
 *
 * @return string
 *   If $slug is none, an empty string will be returned; if $slug is a non-empty
 *   string then it will be returned unchanged; if $slug is empty, then the
 *   default slug will be returned.
 *
 */
function dfp_format_slug($slug) {
615
  $formatted_slug = t(check_plain(variable_get('dfp_default_slug', '')));
616 617 618 619 620 621 622 623 624 625 626

  if ($slug == '<none>') {
    $formatted_slug = "";
  }
  elseif (!empty($slug)) {
    $formatted_slug = $slug;
  }

  return $formatted_slug;
}

627 628 629 630 631 632 633
/**
 * Return the term object to use as the DFP Ad Category given a specific term.
 *
 * @param object $term
 *   The term object to analyze. If it is tagged with a DFP Ad Cateegory, then
 *   that term is returned, otherwise the original term is returned unchanged.
 *
634 635 636 637
 * @param boolean $clean_string
 *   If true, use ctools_cleanstring. In future versions, this should default to
 *   TRUE, but for now it defaults to FALSE.
 *
638 639 640
 * @return string
 *   The term name to be included in an ad tag.
 */
641
function _dfp_get_ad_category($term, $clean_string = FALSE) {
642 643 644
  if (!empty($term->field_dfp_ad_categories)) {
    $term = taxonomy_term_load($term->field_dfp_ad_categories[LANGUAGE_NONE][0]['tid']);
  }
645 646 647 648 649 650 651 652 653

  $term_name = $term->name;
  if ($clean_string) {
    ctools_include('cleanstring');
    $term_name = ctools_cleanstring($term_name, array(
      'lower_case' => TRUE,
    ));
  }

654 655 656
  return $term->name;
}

bleen's avatar
bleen committed
657
/**
658 659
 * Helper function to include javascript variables, etc in the header above all
 * slot definitions.
bleen's avatar
bleen committed
660
 */
661 662 663 664 665 666
function _dfp_js_global_settings() {
  // Initialize the google varibales and inject user-defined javascript.
  $js = 'var googletag = googletag || {};' . "\n";
  $js .= 'googletag.cmd = googletag.cmd || [];';
  $js .= variable_get('dfp_injected_js', '') . "\n";

667 668 669
  // Add a place to store the slot name variable
  $js .= 'googletag.slots = googletag.slots || {};';

bleen's avatar
bleen committed
670
  $options = array(
671
    'type' => 'inline',
bleen's avatar
bleen committed
672 673
    'group' => JS_LIBRARY,
    'every_page' => TRUE,
674
    'weight' => -10,
675
    'force header' => TRUE,
bleen's avatar
bleen committed
676
  );
677
  drupal_add_js($js, $options);
bleen's avatar
bleen committed
678

bleen's avatar
bleen committed
679
  // Include a script tag for the Google Tag Services.
680 681 682 683 684
  // @todo: One day this should include an async=true attribute. See #1140356.
  //        However, there is a good chance this might break <= IE8.
  $options['type'] = 'external';
  $options['weight']++;
  drupal_add_js(($GLOBALS['is_https'] ? "https://" : "http://") . DFP_GOOGLE_TAG_SERVICES_URL, $options);
685

686 687
  // Add global settings with a heavy weight so that they appear after all the
  // defineSlot() calls otherwise IE8 and Opera fail to display ads properly.
688
  $js = 'googletag.cmd.push(function() {' . "\n";
689 690 691
  if (variable_get('dfp_async_rendering', 1)) {
    $js .= '  googletag.pubads().enableAsyncRendering();' . "\n";
  }
692 693 694
  else {
     $js .= '  googletag.pubads().enableSyncRendering();' . "\n";
  }
695 696 697
  if (variable_get('dfp_single_request', 1)) {
    $js .= '  googletag.pubads().enableSingleRequest();' . "\n";
  }
698 699 700 701 702 703 704
  switch (variable_get('dfp_collapse_empty_divs', 1)) {
    case 1:
      $js .= '  googletag.pubads().collapseEmptyDivs();' . "\n";
      break;
    case 2:
      $js .= '  googletag.pubads().collapseEmptyDivs(true);' . "\n";
      break;
705
  }
706 707 708
  if (variable_get('dfp_disable_init_load', 0)) {
    $js .= '  googletag.pubads().disableInitialLoad();' . "\n";
  }
bleen's avatar
bleen committed
709 710

  // Set global targeting values for this page.
711
  $targeting = variable_get('dfp_targeting', array());
712
  drupal_alter('dfp_global_targeting', $targeting);
bleen's avatar
bleen committed
713 714
  $targeting = dfp_format_targeting($targeting);
  foreach ($targeting as $key => $target) {
715 716 717
    if (!empty($target['target']) && !empty($target['value'])) {
      $js .= '  googletag.pubads().setTargeting(' . $target['target'] . ', ' . $target['value'] . ');' . "\n";
    }
bleen's avatar
bleen committed
718 719
  }

720
  $js .= '});' . "\n";
721
  $js .= variable_get('dfp_injected_js2', '') . "\n";
bleen's avatar
bleen committed
722
  $js .= 'googletag.enableServices();';
bleen's avatar
bleen committed
723 724 725

  $options = array(
    'type' => 'inline',
726
    'group' => JS_DEFAULT,
bleen's avatar
bleen committed
727
    'every_page' => TRUE,
728
    'weight' => 10,
729
    'force header' => TRUE,
bleen's avatar
bleen committed
730
  );
731
  drupal_add_js($js, $options);
732 733
}

734
/**
bleen's avatar
bleen committed
735 736 737 738 739
 * Helper function to build the javascript needed to define an ad slot and add
 * it to the head tag.
 */
function _dfp_js_slot_definition($tag) {
  // Add the js needed to define this adSlot to <head>.
740 741 742 743 744 745 746 747 748 749
  $js = '';
  // Start by defining breakpoints for this ad.
  if (!empty($tag->breakpoints)) {
    $breakpoints = $tag->breakpoints;
    $js .= 'var mapping = googletag.sizeMapping()' . "\n";
    foreach ($breakpoints as $breakpoint) {
      $js .= '  .addSize(' . dfp_format_size($breakpoint['browser_size']) . ', ' . dfp_format_size($breakpoint['ad_sizes']) . ')' . "\n";
    }
    $js .= '  .build();' . "\n";
  }
750 751 752 753 754 755
  if (!empty($tag->settings['out_of_page'])) {
    $js .= 'googletag.slots["' . $tag->machinename . '"] = googletag.defineOutOfPageSlot("' . $tag->adunit . '", "' . $tag->placeholder_id . '")' . "\n";
  }
  else {
    $js .= 'googletag.slots["' . $tag->machinename . '"] = googletag.defineSlot("' . $tag->adunit . '", ' . $tag->size . ', "' . $tag->placeholder_id . '")' . "\n";
  }
756 757 758 759 760 761 762 763
  $click_url = variable_get('dfp_click_url', '');
  if (!empty($click_url)) {
    if (!preg_match("/^https?:\/\//", $click_url)) {
      // relative URL; prepend site domain
      $click_url = (($GLOBALS['is_https']) ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . "/" . $click_url;
    }
    $js .= '  .setClickUrl("' . $click_url . '")' . "\n";
  }
bleen's avatar
bleen committed
764
  $js .= '  .addService(googletag.pubads())' . "\n";
765

bleen's avatar
bleen committed
766 767
  if (!empty($tag->adsense_ad_types)) {
    $js .= '  .set("adsense_ad_types", "' . $tag->adsense_ad_types . '")' . "\n";
bleen's avatar
bleen committed
768
  }
769 770 771
  if (!empty($tag->adsense_channel_ids)) {
    $js .= '  .set("adsense_channel_ids", "' . $tag->adsense_channel_ids . '")' . "\n";
  }
bleen's avatar
bleen committed
772
  foreach ($tag->adsense_colors as $key => $val) {
773
    if (!empty($val)) {
bleen's avatar
bleen committed
774
      $key = 'adsense_' . $key . '_color';
775
      $val = '#' . drupal_strtoupper($val);
bleen's avatar
bleen committed
776 777 778
      $js .= '  .set("' . $key . '", "' . $val . '")' . "\n";
    }
  }
779
  $targeting = dfp_format_targeting($tag->targeting, $tag);
780
  foreach ($targeting as $target) {
781
    $js .= '  .setTargeting(' . $target['target'] . ', ' . $target['value'] . ')' . "\n";
bleen's avatar
bleen committed
782
  }
783 784 785 786
  // Apply size mapping when there are breakpoints.
  if (!empty($tag->breakpoints)) {
    $js .= '  .defineSizeMapping(mapping)' . "\n";
  }
bleen's avatar
bleen committed
787
  $js = rtrim($js, "\n") . ';';
bleen's avatar
bleen committed
788 789 790 791

  $options = array(
    'type' => 'inline',
    'group' => JS_LIBRARY,
792
    'weight' => 0,
793
    'force header' => TRUE,
bleen's avatar
bleen committed
794 795 796 797
  );
  drupal_add_js($js, $options);
}

798 799 800 801 802 803 804 805 806 807 808 809
/**
 * Prepare token replacement values.
 *
 * @param  (optional) object $tag
 * @return array
 */
function _dfp_prepare_tokens($tag = NULL) {
  global $user;

  $data = array();
  $data['user'] = $user;
  $data['node'] = menu_get_object();
810 811 812 813 814

  // We cannot use menu_get_object because the views version of the taxonomy
  // term pages will not work properly.
  $data['term'] = (arg(0) == 'taxonomy' && arg(1) == 'term' && is_numeric(arg(2))) ? taxonomy_term_load(arg(2)) : NULL;

815 816 817 818 819 820 821
  if (!empty($tag)) {
    $data['tag'] = $tag;
  }

  return $data;
}

822 823 824 825 826 827 828 829 830 831 832 833 834 835 836
/**
 * Check adunit has a value and/or if the global should be used.
 *
 * @param  object $tag
 * @return object $tag
 */
function _dfp_prepare_adunit($tag) {
  $global_adunit = variable_get('dfp_default_adunit', '');
  if (empty($tag->adunit) && !empty($global_adunit)) {
    $tag->adunit = $global_adunit;
  }

  return $tag;
}

837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925
/**
 * Replaces all tokens in a given string with appropriate values, with caching.
 *
 * This function is a memoizing wrapper for token_replace(), which is quite slow
 * and inefficient. It takes advantage of specific knowledge about how DFP works
 * to cache the result of token replacement. It is not a fully general solution
 * but works for this module.
 *
 * @param string $text
 *   A string potentially containing replaceable tokens.
 * @param stdClass $tag
 *   The tag for which we are encoding a string. This value will be provided
 *   as additional context to token_replace().
 * @param array $options
 *   An array of options to pass to token_replace().  See that function for
 *   further documentation.
 * @return string
 *   Text with tokens replaced.
 *
 * @see token_replace()
 */
function dfp_token_replace($text, $tag = NULL, array $options = array()) {
  $processed_strings =& drupal_static(__FUNCTION__, NULL);

  // Short-circuit the degenerate case, just like token_replace() does.
  $text_tokens = token_scan($text);
  if (empty($text_tokens)) {
    return $text;
  }

  // Get the possible replacement sources.
  // @todo This doesn't vary, so we could probably refactor it to be cached,
  // too.
  $data = _dfp_prepare_tokens($tag);

  // Determine the cache key for this text string. That way we can cache
  // reliably.
  $key = _dfp_token_replace_make_key($text, $data);

  $cache_item = DFP_TOKEN_CACHE . ':' . current_path();

  // Lookup any already-cached token replacements.
  if (is_null($processed_strings)) {
    $cache = cache_get($cache_item, 'cache');
    $processed_strings = $cache
      ? $cache->data
      : array();
  }

  // If the processed string we're looking for isn't already in the cache,
  // then, and only then, do we call the expensive token_replace() (and cache
  // the result).
  if (!isset($processed_strings[$key]) || is_null($processed_strings[$key])) {
    // Regenerate this particular replacement.
    $processed_strings[$key] = token_replace($text, $data, $options);
    $lifetime = variable_get('dfp_token_cache_lifetime', 0);
    $expire_at = ($lifetime == 0) ? CACHE_TEMPORARY : (REQUEST_TIME + $lifetime);
    cache_set($cache_item, $processed_strings, 'cache', $expire_at);
  }

  return $processed_strings[$key];
}

/**
 * Generates an identifying key for the lookup to be processed.
 *
 * @param string $text
 *   The text to be processed.
 * @param array $data
 *   The array of data parameters that will be passed to token_generate().
 *   We'll use knowledge of what is expected in that array to build a
 *   meaningful lookup key.
 * @return string
 *   The key in the lookup array that corresponds to this tokenization request.
 */
function _dfp_token_replace_make_key($text, array $data) {
  // $text may be arbitrarily long, which can slow-down lookups. Hashing it
  // keeps uniqueness but guarantees a manageable size. Since this value won't
  // be used as the cache key itself we're not limited to 255 characters but
  // it will be nicer on array lookups in PHP.
  $keys[] = sha1($text);
  $keys[] = isset($data['node']->nid) ? $data['node']->nid . '-' . entity_modified_last('node', $data['node'])  : NULL;
  $keys[] = isset($data['user']->uid) ? $data['user']->uid . '-' . entity_modified_last('user', $data['user']) : NULL;
  $keys[] = isset($data['term']->tid) ? $data['term']->tid . '-' . entity_modified_last('taxonomy_term', $data['term']) : NULL;
  $keys[] = isset($data['tag']->machinename) ? $data['tag']->machinename . '-' . entity_modified_last('tag', $data['tag']) : NULL;

  return implode('|', array_filter($keys));
}

926 927 928 929 930
/**
 * Preprocess function for DFP tags.
 */
function template_preprocess_dfp_tag(&$variables) {
  $tag = $variables['tag'];
931
  $tag = _dfp_prepare_adunit($tag);
932
  $slug_placement = variable_get('dfp_slug_placement', 0);
933

934
  // Format certain tag properties for display.
935
  $tag->adunit = dfp_token_replace('[dfp_tag:network_id]/' . $tag->adunit, $tag, array('sanitize' => TRUE, 'clear' => TRUE));
936 937 938
  $tag->size = dfp_format_size($tag->size);
  $tag->slug = dfp_format_slug($tag->slug);

939 940 941 942 943 944 945
  // Create the attributes for the wrapper div and placeholder div.
  $variables['placeholder_attributes'] = array(
    'id' => $tag->placeholder_id,
    'class' => array(
      'dfp-tag-wrapper',
    ),
  );
946

947 948 949 950 951 952 953 954 955 956 957 958 959 960 961
  if (!empty($tag->slug) && $slug_placement == 1) {
    $variables['slug'] = array(
      '#type' => 'container',
      '#attributes' => array(
        'class' => array(
          'slug',
        ),
      ),
      'slug' => array(
        '#markup' => $tag->slug,
      ),
      '#weight' => -1,
    );
  }

962
  // Define a javascript ad slot for this tag.
bleen's avatar
bleen committed
963 964 965 966 967 968 969 970 971
  _dfp_js_slot_definition($tag);
}

/**
 * Preprocess function for DFP tags.
 */
function template_preprocess_dfp_short_tag(&$variables) {
  static $tile = 0;
  $tag = $variables['tag'];
972
  $tag = _dfp_prepare_adunit($tag);
bleen's avatar
bleen committed
973 974 975

  // Build a key|vals array and allow third party modules to modify it.
  $keyvals = array();
976
  $keyvals['iu'] = dfp_token_replace('/[dfp_tag:network_id]/' . $tag->adunit, $tag, array('sanitize' => TRUE, 'clear' => TRUE));
977
  $keyvals['sz'] = str_replace(',', '|', check_plain($tag->raw->size));
978
  $keyvals['c'] = rand(10000, 99999);
bleen's avatar
bleen committed
979 980 981

  $targets = array();
  foreach ($tag->targeting as $data) {
982
    $targets[] = check_plain($data['target']) . '=' . check_plain($data['value']);
983
  }
bleen's avatar
bleen committed
984 985
  if (!empty($targets)) {
    $keyvals['t'] = implode('&', $targets);
986
  }
bleen's avatar
bleen committed
987
  drupal_alter('dfp_short_tag_keyvals', $keyvals);
988

989 990
  $variables['url_jump'] = 'http://' . DFP_GOOGLE_SHORT_TAG_SERVICES_URL . '/jump?' . drupal_http_build_query($keyvals);
  $variables['url_ad'] = 'http://' . DFP_GOOGLE_SHORT_TAG_SERVICES_URL . '/ad?' . drupal_http_build_query($keyvals);
991
}
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026

/**
 * Implements of hook_page_build().
 */
function dfp_page_build(&$page) {
  $html = '';

  $tags = dfp_tag_load_all();
  foreach ($tags as $tag) {
    if (isset($tag->settings['out_of_page']) && $tag->settings['out_of_page'] == TRUE) {
      drupal_alter('dfp_tag_load', $tag);
    }
  }

  if (module_exists('context')) {
    if ($plugin = context_get_plugin('reaction', 'dfp_outofpage')) {
      if (isset($plugin->out_of_page_tags)) {
        foreach ($plugin->out_of_page_tags as $key => $machinename) {
          $tag = dfp_tag_load($machinename);
          if (empty($tag->disabled)) {
            $dfp_tag = dfp_tag($machinename);
            $dfp_tag['dfp_wrapper']['#attributes']['class'][] = 'element-hidden';
            $html .= render($dfp_tag);
          }
        }
      }
    }
  }

  // Add Out Of Page slots on the top of the page.
  $page['page_top']['dfp_out_of_page'] = array(
    '#weight' => -1000,
    '#markup' => $html,
  );
}