adsense_injector.module in Content Injector (formerly AdSense Injector) 7.3
Same filename and directory in other branches
Inject adsense ads into node content automatically.
File
adsense_injector.moduleView source
<?php
define('ADSENSE_INJECTOR_DEFAULT_MANUAL_INSERTION_TEMPLATE', '<div style="float: right; margin: 0; padding: 0 1em .25em 0;">[adsense:250x250:0123456789]</div>');
define('ADSENSE_INJECTOR_DEFAULT_AD_BODY_TEMPLATE', '<div style="float: right; margin: 0; padding: 0 1em .25em 0;">[adsense:250x250:0123456789]</div>');
define('ADSENSE_INJECTOR_MANUAL_INSERTION_TAG', '[ai:insertion:inline]');
define('ADSENSE_INJECTOR_ADSENSE_TAG_PREFIX', '[adsense:');
define('ADSENSE_INJECTOR_DEFAULT_XPATH_INLINE', '/html/body/p[3]');
define('ADSENSE_INJECTOR_DEFAULT_XPATH_FRONT', '!!FRONT');
define('ADSENSE_INJECTOR_DEFAULT_XPATH_BACK', '!!BACK');
define('ADSENSE_INJECTOR_NOINJECT', 'ai:noinject');
/**
* @file
* Inject adsense ads into node content automatically.
*
* @ingroup adsense_injector
*/
/**
* Process the body, handle adsense injector tags.
* Look for an insertion point based on paragraphs.
* @param $node_body The node body HTML to process.
* @param $node The node object (for field insertion)
* @param $skipList An array of insertions to skip.
* @return The processed HTML.
*/
function adsense_injector_process_body($node_body, &$node, $skipList = null) {
// Process adsense module tags in the template text, if enabled and possible.
// Determine the target paragraph # from tags.
// TODO: Remove dependency on adsense module. Only call _adsense_process_tags()
// if module is present.
$vars = _ai_get_body_insertions_vars();
$special = array();
foreach ($vars as $key => $insertion) {
//
// Skip any keys in skipList
if ($skipList && in_array($key, $skipList)) {
continue;
}
//
// Find and hold all non-xpath insertions for last.
// Non-xpath insertions start with !! (!!TOP, !!BOTTOM)
if (strpos($insertion['xpath'], '!!') === 0) {
// Add it to the list and continue.
$special[] = $insertion;
continue;
}
else {
// Process current insertion text.
$template = _adsense_injector_process_tags($insertion['text']);
//
// TODO: allow views, blocks render tags: [ci:block:xxx] and [ci:view:xyz]
// Given: body, list of xpath expressions and associated html snippet
// Produce: new body
$xpath_expr = $insertion['xpath'];
// TODO: Ignore empty paragraphs if possible.
// TODO: Smart location: Count words in each 'paragraph' element?
// TODO: This doesn't take into account the <br> stuff inserted by auto-line breaks
$node_body = _ai_injectXPath($node_body, $template, $xpath_expr);
}
}
foreach ($special as $specialInsertion) {
// Process current insertion text.
$template = _adsense_injector_process_tags($specialInsertion['text']);
switch ($specialInsertion['xpath']) {
case ADSENSE_INJECTOR_DEFAULT_XPATH_FRONT:
$node->content['adsense_injector_front'] = array(
'#markup' => $template,
'#weight' => variable_get('adsense_injector_front_weight', -6),
);
break;
case ADSENSE_INJECTOR_DEFAULT_XPATH_BACK:
$node->content['adsense_injector_back'] = array(
'#markup' => $template,
'#weight' => variable_get('adsense_injector_back_weight', 10),
);
break;
default:
watchdog('adsense_injector', t('Unknown special insertion type in adsense_injector_process_body'), WATCHDOG_ERROR);
}
}
return $node_body;
}
/**
* Handle 'manual' insertion at the specified position
* @param $node_body The node body text to process
* @param $at The insertion point of the '[ad_here]' marker.
*/
function _ai_insert_text_at($node_body, $at, $insertion) {
$front = substr($node_body, 0, $at);
$end = substr($node_body, $at + strlen(ADSENSE_INJECTOR_MANUAL_INSERTION_TAG));
return $front . _ai_html_comment('ManualInsertion', 'START') . $insertion . _ai_html_comment('ManualInsertion', 'END') . $end;
}
/**
* Should we inject now?
* @param object $node
* @return True if should inject an ad.
*/
function _ai_should_inject($node) {
$node_types = variable_get('adsense_injector_nodes', array());
if (empty($node_types[$node->type])) {
return false;
}
return true;
}
/**
* Implements hook_node_view().
*
* If rendering a full page, and the node type one of the configured types,
* inject configured adsense content using simple string concatenation.
*
* @todo: Evaluate efficiency of string concat vs. sprintf, other methods.
*/
function adsense_injector_node_view($node, $view_mode) {
//return;
// TODO Remaining code in this function needs to be moved to the appropriate new hook function.
// We only consider content types which are enabled for inline adsense.
if (!_ai_should_inject($node)) {
return;
}
// Insert an ad into the body.
if ($view_mode == 'full') {
//
// Any other reason not to inject?
if (_ai_noinject($node)) {
return;
}
//
// If there are any manual insertions, perform them and exit if
// desired.
if (_ai_manual_insertion($node)) {
return;
}
//
// Now do the auto insertions.
_ai_process_auto_insertions($node);
}
elseif ($view_mode == 'teaser' && variable_get('adsense_injector_list_view', FALSE)) {
// Process adsense module tags in the template text, if enabled and possible.
$template = variable_get('adsense_injector_list_view_template', '%teaser<br class="clear"/>[adsense:728x90:0123456789]');
if (function_exists('_adsense_process_tags')) {
$template = _adsense_process_tags($template, null);
}
$node->content['body'][0]['#markup'] = strtr($template, array(
'%teaser' => $node->content['body'][0]['#markup'],
));
}
}
/**
* Process automatic insertions.
* @param unknown_type $node The node to operate on.
*/
function _ai_process_auto_insertions(&$node, $skipList = null) {
//
// Get the minimum node body wordcount for insertion.
$minwords = _ai_node_body_view_minwords();
// Count words in a string.
// lifted from node.module node_validate() function.
$text = $node->content['body'][0]['#markup'];
$wordcount = count(explode(' ', $text, $minwords));
if ($wordcount >= $minwords) {
$node->content['body'][0]['#markup'] = adsense_injector_process_body($text, $node, $skipList);
}
else {
$node->content['body'][0]['#markup'] .= _ai_html_comment('AutoInsertion', "Body has {$wordcount} words ({$minwords} required), insertion skipped");
}
}
/**
* Perform manual insertions, if any.
* @param unknown_type $node The node to process.
* @return true if we performed manual insertions, false otherwise.
*/
function _ai_manual_insertion(&$node) {
//
// Check for manual insertion marker(s)
$text = $node->content['body'][0]['#markup'];
$at = strpos($text, ADSENSE_INJECTOR_MANUAL_INSERTION_TAG);
if (!($at === FALSE)) {
$insertion = _ai_get_insertion_text_for('inline');
// TODO: Grab item id from found tag.
$insertion = _adsense_injector_process_tags($insertion);
$node->content['body'][0]['#markup'] = _ai_insert_text_at($text, $at, $insertion);
//
// Now do auto insertions but skip the 'inline' insertion.
adsense_injector_process_body($text, $node, array(
'inline',
));
return true;
}
return false;
}
/**
* Determine whether there are any reasons not to inject ads.
*
* @param unknown_type $node The node to process.
* @return true if we should skip injection on this node.
*/
function _ai_noinject($node) {
// Bail if node has 'ai_noinject' tag in first N characters.
// TODO: make these regexp, configurable in admin.
$text = $node->content['body'][0]['#markup'];
$front = substr($text, 0, variable_get('adsense_injector_front_maxlen', 512));
$skip = !(strpos($front, ADSENSE_INJECTOR_NOINJECT) === FALSE);
/// Bail if we find [adsense: tags in body.
if (!$skip) {
$skip = !(strpos($text, ADSENSE_INJECTOR_ADSENSE_TAG_PREFIX) == FALSE);
}
return $skip;
}
/**
* Implements hook_menu().
*/
function adsense_injector_menu() {
// debug('here');
$items = array();
$items['admin/config/content/adsense_injector'] = array(
'title' => 'AdSense Injector',
'description' => 'Insert Google AdSense ads content automatically.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'adsense_injector_admin_settings',
),
'access arguments' => array(
'administer site configuration',
),
'file' => 'adsense_injector.admin.inc',
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* Inject an HTML fragment into the node body, using XPath expression.
* @param $body The node body to operate on.
* @param $fragment The HTML fragment to inject.
* @param $xpath_expression The XPath expression to use
* @param $encoding The character encoding to use. Defaults to 'UTF-8'.
* @todo Fix entity encoding problem!
* http://www.php.net/manual/en/domdocument.loadhtml.php#95251
* http://devzone.zend.com/article/8855
*
*/
function _ai_injectXPath($body, $fragment, $xpath_expression, $encoding = 'UTF-8') {
try {
$doc = new DomDocument();
// HACK: Fix entity encoding http://www.php.net/manual/en/domdocument.loadhtml.php#95251 et al.
$encoding_string = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset={$encoding}\">";
$prehtml = "<html><head>{$encoding_string}</head><body>";
$posthtml = "</body></html>";
@$doc
->loadHTML($prehtml . $body . $posthtml);
$xpath = new DOMXpath($doc);
$ent = $xpath
->query($xpath_expression);
if (!empty($ent) && $ent->length > 0) {
// http://stackoverflow.com/questions/2255158/how-do-i-insert-html-into-a-php-dom-object
$frag = $doc
->createDocumentFragment();
$frag
->appendXML($fragment);
$ent
->item(0)
->appendChild($frag);
// DOMDocument->saveHTML() doesn't accept a child element unless in PHP
// 5.3.6 or above. So we'll have to do this the ugly way (as described
// in http://php.net/manual/en/domdocument.savehtml.php#85165
// $html_fragment = $doc->saveHTML($doc->getElementsByTagName('body')->item(0));
//
// If needed, we can do a manual encoding fixup
// $doc = _ai_fixup_html_encoding($doc);
$html_fragment = preg_replace('/^<!DOCTYPE.+?>/', '', str_replace(array(
$encoding_string,
'<html>',
'</html>',
'<head>',
'</head>',
'<body>',
'</body>',
), '', $doc
->saveHTML()));
}
else {
$html_fragment = $body . _ai_html_comment('injectXPath', "XPath query '{$xpath_expression}' result empty; insertion skipped ");
}
} catch (Exception $e) {
$html_fragment = $body . _ai_html_comment('injectXPath error', $e
->getMessage());
}
return $html_fragment;
}
/**
* // From: http://devzone.zend.com/article/8855
* @param unknown_type $dom
*/
function _ai_fixup_html_encoding($dom) {
$xpath = new DOMXPath($dom);
$metaTags = $xpath
->query('/html/head/meta');
// Unfortunately DOMXPath supports only XPath 1.0 and we have to iterate
// through meta tags instead of selecting the node using
// '/html/head/meta[lower-case(@http-equiv)="content-type"]'
// (fn:lower-case() function came with XPath 2.0)
for ($count = 0; $count < $metaTags->length; $count++) {
$httpEquivAttribute = $metaTags
->item($count)->attributes
->getNamedItem('http-equiv');
if ($httpEquivAttribute !== null && strtolower($httpEquivAttribute->value) == 'content-type') {
$fragment = $dom
->createDocumentFragment();
$fragment
->appendXML('<meta http-equiv="Content-type" content="text/html; charset=UTF-8"/>');
$metaTags
->item($count)->parentNode
->replaceChild($fragment, $metaTags
->item($count));
break;
}
}
// Do nothing if meta tag is not found
return $dom;
}
/**
* Get the raw insertion text for the specified tag.
* @param string $tag The insertion 'tag' ([top, inline, bottom]).
* No validation is performed on this parameter. If the tag is unknown,
* the return value will be empty.
* @return The insertion text, if 'tag' is known, empty otherwise.
*/
function _ai_get_insertion_text_for($tag) {
$insertions = _ai_get_body_insertions_vars();
return $insertions[$tag]['text'];
}
/**
* Get the default body insertions variables.
* @return An array of default body insertion variables.
*/
function _ai_get_default_body_insertions_vars() {
return array(
'body_insertions' => array(
'top' => array(
'name' => 'Top',
'text' => '<!--FRONT-->',
'xpath' => ADSENSE_INJECTOR_DEFAULT_XPATH_FRONT,
'adminui' => false,
),
'inline' => array(
'name' => 'Inline',
'text' => ADSENSE_INJECTOR_DEFAULT_AD_BODY_TEMPLATE,
'xpath' => ADSENSE_INJECTOR_DEFAULT_XPATH_INLINE,
'adminui' => true,
),
'bottom' => array(
'name' => 'Bottom',
'text' => '<!--BACK-->',
'xpath' => ADSENSE_INJECTOR_DEFAULT_XPATH_BACK,
'adminui' => false,
),
),
);
}
/**
* Build list of body insertion variables with proper defaults.
* This is necessary because not all elements may be stored in the variable
* table.
* @return The body insertions variables array.
*/
function _ai_get_body_insertions_vars() {
/** List of default body insertion templates **/
$default_body_insertions = _ai_get_default_body_insertions_vars();
// Now build the list of insertions...
$body_view = variable_get('adsense_injector_body_view', $default_body_insertions);
$defitems = array(
'name',
'xpath',
'adminui',
);
foreach ($body_view['body_insertions'] as $key => $item) {
foreach ($defitems as $check) {
// Get defaults for items that might be missing from variables table...
if (!array_key_exists($check, $item)) {
$body_view['body_insertions'][$key][$check] = $default_body_insertions['body_insertions'][$key][$check];
}
}
}
return $body_view['body_insertions'];
}
/**
* Insert at front of body.
* @param string $node_body The node body to process.
* @param string $text The raw text to insert. This should already be
* processed for any macros/tags/tokens etc.
* @return The processed node body.
*/
function _adsense_injector_insert_front($node_body, $text) {
return $text . $node_body;
}
/**
* Insert at end of boddy.
* @param string $node_body The node body to process.
* @param string $text The raw text to insert. This should already be
* processed for any macros/tags/tokens etc.
* @return The processed node body.
*/
function _adsense_injector_insert_back($node_body, $text) {
return $node_body . $text;
}
/**
* Process template text tags. At present, we support only the adsense module
* tags. Later, we wish to support views and blocks rendering if current user
* has permissions.
* @param unknown_type $text
* @return The processed template text.
*/
function _adsense_injector_process_tags($text) {
if (function_exists('_adsense_process_tags')) {
$text = _adsense_process_tags($text, null);
}
return $text;
}
/**
* Helper: get min words variable.
*/
function _ai_node_body_view_minwords() {
return _ai_admin_nested_vget(variable_get('adsense_injector_body_view', array()), 'minwords', 75);
}
/**
* Helper: Get node body insertion enabled variable.
*/
function _ai_node_body_view_enabled() {
return _ai_admin_nested_vget(variable_get('adsense_injector_body_view', array()), 'enable', TRUE);
}
/**
* Get a nested variable from a #tree structured variable set in the
* admin pages for this module. Used in Admin pages but also needed by this
* module so we'll define it here.
* @param unknown_type $fields
* @param unknown_type $name
* @param unknown_type $def
*/
function _ai_admin_nested_vget($fields, $name, $def) {
return isset($fields[$name]) ? $fields[$name] : $def;
}
/**
* Generate a formatted HTML comment.
* @param unknown_type $id
* @param unknown_type $text
*/
function _ai_html_comment($id, $text) {
return '<!-- adsense_injector: (' . $id . ') ' . $text . '-->';
}
Functions
Name![]() |
Description |
---|---|
adsense_injector_menu | Implements hook_menu(). |
adsense_injector_node_view | Implements hook_node_view(). |
adsense_injector_process_body | Process the body, handle adsense injector tags. Look for an insertion point based on paragraphs. |
_adsense_injector_insert_back | Insert at end of boddy. |
_adsense_injector_insert_front | Insert at front of body. |
_adsense_injector_process_tags | Process template text tags. At present, we support only the adsense module tags. Later, we wish to support views and blocks rendering if current user has permissions. |
_ai_admin_nested_vget | Get a nested variable from a #tree structured variable set in the admin pages for this module. Used in Admin pages but also needed by this module so we'll define it here. |
_ai_fixup_html_encoding | // From: http://devzone.zend.com/article/8855 |
_ai_get_body_insertions_vars | Build list of body insertion variables with proper defaults. This is necessary because not all elements may be stored in the variable table. |
_ai_get_default_body_insertions_vars | Get the default body insertions variables. |
_ai_get_insertion_text_for | Get the raw insertion text for the specified tag. |
_ai_html_comment | Generate a formatted HTML comment. |
_ai_injectXPath | Inject an HTML fragment into the node body, using XPath expression. |
_ai_insert_text_at | Handle 'manual' insertion at the specified position |
_ai_manual_insertion | Perform manual insertions, if any. |
_ai_node_body_view_enabled | Helper: Get node body insertion enabled variable. |
_ai_node_body_view_minwords | Helper: get min words variable. |
_ai_noinject | Determine whether there are any reasons not to inject ads. |
_ai_process_auto_insertions | Process automatic insertions. |
_ai_should_inject | Should we inject now? |