Drupal Commerce AdWords-module
In 2013/2014 is module uc_adword
geïmplementeerd: Dynamische conversion-tracking voor Übercart 2/Drupal 6. Dit is geen 'complete' module en bevat zelfs geen grafische interface, maar hij doet precies waar-ie voor bedoeld is. De module is vrijgegeven onder de GPL V2.
Eind 2016 was er geen ontkomen meer aan: De module moest omgezet worden naar Drupal 7/Drupal Commerce 1.x, onder de naam commerce_adwords
.
uc_adwords: Hoe het werkt
De module declareert twee functies die dankzij de juiste hooks rondom de dankjewel-pagina hun ding doen. Je hoeft dus geen code in te voegen in blocks of pagina's. In de code kun parameter PRODUCTS_ONLY
gebruiken om te bepalen dat het orderbedrag in- of exclusief vervoerskosten overgenomen moet worden. Iets meer in detail:
uc_adwords_order()
detecteert nieuwe orders, en slaat de order-id op in een custom sessie-variableeuc_adwords_footer()
detecteert de dankjewel-pagina. Haalt de sessie-variabele op, roept de tag-functie aan, en output de tag naar de footeradd_adwords_js()
Genereer en retourneer AdWords-tag.
Welk bedrag precies?
Orderbedrag minus vrachtkosten minus BTW:
Drupal 7: Overzicht bestaande modules
Ik geloof niet dat er een module bestaat die dit dynamisch doet:
- Google AdWords-module [1], [2] - Geen dynamische tracking, maar wel aardig begin
- Commerce Checkout AdWords - Experimenteel. Interessant
Debugging
Event registration
Gebruik drush fn-hook
om een overzicht te krijgen van geregistreerde hooks. Bv.:
drush fn-hook commerce_checkout_complete Enter the number of the hook implementation you wish to view. [0] : Cancel [1] : commerce_adwords [2] : commerce_cart 1 // file: /var/www/kbo215.dvb/sites/all/modules/commerce_adwords/commerce_adwords.module, lines 7-10 function commerce_adwords_commerce_checkout_complete($order) { print phpinfo(); }
Event-code uitvoeren
Hier komt de truuk: print phpinfo();
werkt niet! Blijkbaar zijn er events waarbij je niet op deze manier uitvoer naar het scherm kunt forceren! Een voorbeeld van wat wel werkt:
// commerce_checkout_complete ///////////////////////////////////////////// // function commerce_adwords_commerce_checkout_complete($order) { dpm($order); // print "<br><br><br> commerce_checkout_complete"; // print phpinfo(); }
Tag invoegen - hook_init
Dit werkt:
<?php ///////////////////////////////////////////// // hook - commerce_checkout_complete($order) ///////////////////////////////////////////// // // Werkt niet. Zie http://wiki.devliegendebrigade.nl/Drupal_Commerce_AdWords-module // // function commerce_adwords_commerce_checkout_complete($order) // { // print phpinfo(); // } ///////////////////////////////////////////// // hook_init() ///////////////////////////////////////////// // // Dit is de basis // // * hook_init() is run on every page load except for cached pages, // so depending on your use case that might be an option. // * (http://drupal.stackexchange.com/questions/33789/how-to-run-check-on-each-page-load) // // function commerce_adwords_init() // { // print "commerce_adwords_init"; // } ///////////////////////////////////////////// // commerce_adwords_init() ///////////////////////////////////////////// // function commerce_adwords_init() { if (arg(0)=='checkout' && arg(2)=='complete') { print phpinfo(); } }
Nieuwe order onderscheppen
Dit zijn zoal de hooks die in aanmerking leken te komen om een nieuwe order te onderscheppen. Dit ruime lijstje ontstond, omdat ik niet effectief debugde (zie hierboven):
hook_commerce_checkout_complete
- Lijkt niet te werken. Zie hieronderhook_entity_insert
[3]hook_entity_update
hook_commerce_order_presave()
hook_commerce_order_status_update
[4]hook_commerce_order_state_info
[5] - Waarschijnlijk niethook_commerce_order_status_info
[6] - Actief op elke pagina. Bruikbaar?
commerce_order_checkout_complete is het juiste event. Dat zie je ook terug in de eerste regels van deze code-export van het checkout_completetion_email_send-event:
{ "commerce_checkout_order_email" : { "LABEL" : "Send an order notification e-mail", "PLUGIN" : "reaction rule", "WEIGHT" : "4", "OWNER" : "rules", "TAGS" : [ "Commerce Checkout" ], "REQUIRES" : [ "rules", "commerce_checkout" ], "ON" : { "commerce_checkout_complete" : [] }, ...
Zie de appendix voor alle hooks bij elkaar.
order_id of order_number?
Drupal Commerce kent een order_id en een order_number. Geen idee wat het verschil is. Ik houd het erop, dat ik deze eerste nodig heb.
Order_number opslaan
Tjakka:
///////////////////////////////////////////// // Niewe order onderscheppen ///////////////////////////////////////////// // function commerce_adwords_commerce_checkout_complete($order) { $_SESSION['adwords_commerce_order_number'] = $order->order_number; dpm($order); dpm($order->order_number); dpm($_SESSION['adwords_commerce_order_number']); }
In de schermafdruk hiernaast zie je dat de onderste twee dpm-commando's functioneren, want het order_number is inderdaad 27763.
Orderbedrag uitlezen
Übercart heeft de functie uc_order_get_total($order, TRUE);
om ordergegevens boven water te toveren als je het $order-object opgeeft. Hoe doe je dat in Drupal Commerce?
Uit het order-object peuteren
Yep: Ik kan het uit het order-object peuteren, maar da's nogal lastig + instabiel. Dat kan vast beter.
API-calls?
Voorbeelden van mogelijk relevante functies:
https://drupalcommerce.org/discussions/397/get-order-total:
$order = commerce_order_load($order_id); $wrapper = entity_metadata_wrapper('commerce_order', $order); $total = $wrapper->commerce_order_total->amount->value(); $currency_code = $wrapper->commerce_order_total->currency_code->value();
Output tag to page
Nu nog die tag op de betreffende pagina serveren. Dat heeft een paar onderdelen, die vaak met elkaar verweven zijn:
- Hook om een pagina aan te passen
- Selectiemechanisme om de dankjewel-pagina te selecteren
- Plaatsingsmechanisme om de tag te plaatsen
- Executie: Dit betreft een JavaScript-tag, en dat ding moet uitgevoerd worden, en niet bv. afgebeeld worden alsof het gewoon tekst is.
In uc_adwords
werd hook_footer
gebruikt om het script te incorporeren op een pagina. [7]:
Insert closing HTML. This hook enables modules to insert HTML just before the \</body\> closing tag of web pages. This is useful for adding JavaScript code to the footer and for outputting debug information. It is not possible to add JavaScript to the header at this point, and developers wishing to do so should use hook_init() instead.
Aanroep is verbluffend eenvoudig, bv.:
mijnmodule_footer { ... return $mijncode; }
In Drupal 7 bestaat hook_footer niet meer [8].
drupal_add_js
- Met drupal_add_js kun je JavaScript-code toevoegen aan een pagina
- Het is de vraag of dit is wat we zoeken: Eigenlijk willen we gewoon HTML invoegen (waar toevallig JavaScript in zit) in een pagina, en dat die HTML vervolgens wordt uitgevoerd.
Syntaxis
Het werkt met vijf parameters. Documentatie vermeld alleen de eerste twee parameters:
- Code: De eerste parameter is een bestandsnaam, een array met instellingen, of direct JavaScript-code → Ik kies deze laatste
- Bron: Keuze uit 'module' (standaard) 'setting', 'theme' or 'online' → Deze laatste gebruiken
Voorbeeld
drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });', 'inline');
Met aangepaste formattering:
drupal_add_js ( ' jQuery(document).ready ( function () { alert("Hello!"); } ); ' , 'inline' );
hook_page_build
In Drupal 7 is hook_page_build()
de opvolger van hook_footer
. Dit event vindt plaats bij elke pagina-aanroep.
Syntaxis
hook_page_build(&$page)
- Hierbij is
&$page
een array van weer te geven elementen, dat tezamen de pagina vorm - Het input-argument is een render array.
Voorbeeld
Zo simpel kan het al, dus zonder de page-variabele:
// ---------------------------------------------------------------------------- // Test - hook_page_build // ---------------------------------------------------------------------------- // function commerce_adwords_page_build() { dpm("hook_page_build"); }
hook_page_alter
hook_page_alter() is het broertje van hook_page_build(), en is bedoeld om content die van een andere module komt, aan te passen of te verwijderen [9]. Dat is waarschijnlijk niet wat ik zoek.
$page_bottom, $page_top
$page_bottom
, $page_top
, hook_page_build(), hook_page_alter()
- https://www.drupal.org/node/224333#hook_footer
Render arrays
In Drupal 7 zijn render arrays iets nieuws: Als je met blokken html-code moet schuiven, moet je dat doen in de vorm van zo'n render arrary. Dat zijn (mogelijk geneste) arrays met data en aanwijzingen tav. de weergave daarvan. Het bestaat uit paren van property-labels en de bijbehorende waardes.
Voorbeelden
Zie ook de voorbeelden verderop in de artikel!
$page = array( '#show_messages' => TRUE, '#theme' => 'page', '#type' => 'page', 'content' => array( 'system_main' => array(...), 'another_block' => array(...), '#sorted' => TRUE, ), 'sidebar_first' => array( ... ), 'footer' => array( ... ), ... );
function mymodule_page_alter(&$page) { // Move search form into the footer. // $page['footer']['search_form'] = $page['sidebar_first']['search_form']; unset($page['sidebar_first']['search_form']); // Remove the "powered by Drupal" block // unset($page['footer']['system_powered-by']); }
hook_page_build + render array
Dit werkt:
// ---------------------------------------------------------------------------- // Test - hook_page_build + render array // ---------------------------------------------------------------------------- // function commerce_adwords_page_build(&$page) { $page['footer']['testblock']=array( '#type' => 'markup', '#markup' => '<p>Hello, world!</p>' ); }
Dit werkt ook:
$page['page_bottom']['ga_adwords'] = array( '#weight' => 25, '#markup' => ' <!-- Start Google Code for commerce_adwords Conversion Page --> <script type="text/javascript"> /* <![CDATA[ */ var google_conversion_id = ... ... ', );
hook_page_build + render array + executie
En weer een stap verder: HTML-code die via hook_page_build
wordt ingevoerd, wordt daadwerkelijk geëxecuteerd:
function commerce_adwords_page_build(&$page) { $tag="Hello, <h1>World!</h1>"; // Place tag // ----------------------------------------------------------------------- // $page['footer']['adwords_conversion_tag']=array( '#type' => 'markup', '#markup' => $tag ); }
Appendix: Broncode uc_adwords
uc_adwords.info
name = uc_adwords description = Implements dynamic conversion tracking for adwords in ubercart version = 6.x-0.1 core = 6.x package = Custom files[] = uc_adwords.module
uc_adwords.install
Er is geen installatie-procedure:
<?php
uc_adwords.module - 2014
<?php define('PRODUCTS_ONLY', FALSE); // If set to TRUE google_conversion_value will be products only (without shipping costs) function uc_adwords_footer () { // Check to see if we are at the order completion page. if (arg(0) == 'cart' && arg(1) == 'checkout' && arg(2) == 'complete') { // If we can load the order... if ($order = uc_order_load($_SESSION['uc_adwords_order_id'])) { $output = add_adwords_js($order); } // Clean out the session variable. unset($_SESSION['uc_adwords_order_id']); } return $output; } /** * Implementation of hook_order(). */ function uc_adwords_order($op, &$arg1, $arg2) { switch ($op) { case 'new': // Store the order ID for later use. $_SESSION['uc_adwords_order_id'] = $arg1->order_id; break; } } function add_adwords_js ($order) { //global $_google_adwords_footer_script; $script = ''; // google adwords parameters // $google_conversion_id = 986212345; $google_conversion_language = "en"; $google_conversion_format = "2"; $google_conversion_color = "ffffff"; $google_conversion_label = "FM3zCM-12345qc2m1gM"; $google_remarketing_only = false; // conversion value // ================ // // Value PRODUCTS_ONLY (boolean) // // true : Only value of products incorporated, ex. transportation cost // false: Complete order amount incorporated // $PRODUCTS_ONLY = True; if (PRODUCTS_ONLY) $google_conversion_value = uc_order_get_total($order, TRUE); else { $google_conversion_value = $order->order_total; } $google_conversion_value = uc_currency_format($google_conversion_value, FALSE, TRUE, '.'); // google adwords script $script = ''; $script .= "\n" . '<!-- Google Code for Order Complete scherm Conversion Page -->' . "\n"; $script .= '<script type="text/javascript">' . "\n"; //$script .= '<!--' . "\n"; $script .= ' /* <![CDATA[ */' . "\n"; $script .= ' var google_conversion_id = '. $google_conversion_id .';' . "\n"; $script .= ' var google_conversion_language = "'. $google_conversion_language .'";' . "\n"; $script .= ' var google_conversion_format = "'. $google_conversion_format .'";' . "\n"; $script .= ' var google_conversion_color = "'. $google_conversion_color .'";' . "\n"; $script .= ' var google_conversion_label = "'. $google_conversion_label .'";' . "\n"; $script .= ' var google_conversion_value = "'. $google_conversion_value .'";' . "\n"; $script .= ' var google_remarketing_only = "'. $google_remarketing_only .'";' . "\n"; $script .= ' /* ]]> */ ' . "\n"; $script .= '</script>' . "\n"; $script .= '<script type="text/javascript" src="//www.googleadservices.com/pagead/conversion.js">' . "\n"; $script .= '</script>' . "\n"; $script .= '<noscript>' . "\n"; $script .= '<div style="display:inline;">' . "\n"; $script .= '<img height="1" width="1" border="0" src="https://www.googleadservices.com/pagead/conversion/' . $google_conversion_id . '?value='. $google_conversion_value .'&label='. $google_conversion_label .'&guid=ON&script=0" alt=""/>' . "\n"; $script .= '</div>' . "\n"; $script .= '</noscript>' . "\n"; return $script; }
uc_adwords.module - 2015
Wijzigingen:
- Google Tag Assistant suggereerde dat geldbedragen numerieke waardes dienen te zijn, en geen strings
- Voorzien van commentaar
<?php define('PRODUCTS_ONLY', FALSE); // If set to TRUE google_conversion_value will be products only (without shipping costs) ///////////////////////////////////////////////////////////////////////// // uc_adwords_footer() ///////////////////////////////////////////////////////////////////////// // // Output the AdWords Conversion-tag at the checkout completion page // // * The hook is 'footer' // * Additionally, check that we're on the checkout completion page // * Additionally, check that we can load the current order // * Call function add_adwords_js($order) // function uc_adwords_footer () { if (arg(0) == 'cart' && arg(1) == 'checkout' && arg(2) == 'complete') { // If we can load the order... if ($order = uc_order_load($_SESSION['uc_adwords_order_id'])) { $output = add_adwords_js($order); } // Clean out the session variable. unset($_SESSION['uc_adwords_order_id']); } return $output; } ///////////////////////////////// // uc_adwords_order() ///////////////////////////////// // // Intercept any new order and store order-ID in a custom session variable // // * Hook = "order": http://www.ubercart.nl/docs/api/hook_order // * $op: De action that is being performed // * $arg1: Order-object // * $arg2: Eventueel additioneel argument bij $arg1 function uc_adwords_order($op, &$arg1, $arg2) { switch ($op) { case 'new': // Store the order ID in a session variable for later use // $_SESSION['uc_adwords_order_id'] = $arg1->order_id; break; } } ///////////////////////////////// // add_adwords_js() ///////////////////////////////// // // Generate tag // function add_adwords_js ($order) { //global $_google_adwords_footer_script; $script = ''; // google adwords parameters // $google_conversion_id = 986212345; $google_conversion_language = "en"; $google_conversion_format = "2"; $google_conversion_color = "ffffff"; $google_conversion_label = "FM3zCM-12345qc2m1gM"; $google_remarketing_only = false; // conversion value // ================ // // Value PRODUCTS_ONLY (boolean) // // true : Only value of products incorporated, ex. transportation cost // false: Complete order amount incorporated // $PRODUCTS_ONLY = True; if (PRODUCTS_ONLY) $google_conversion_value = uc_order_get_total($order, TRUE); else { $google_conversion_value = $order->order_total; } $google_conversion_value = uc_currency_format($google_conversion_value, FALSE, TRUE, '.'); // Assemble Adwords tag // ==================== // $script = ''; $script .= "\n" . '<!-- Google Code for Order Complete scherm Conversion Page -->' . "\n"; $script .= '<script type="text/javascript">' . "\n"; //$script .= '<!--' . "\n"; $script .= ' /* <![CDATA[ */' . "\n"; $script .= ' var google_conversion_id = '. $google_conversion_id .';' . "\n"; $script .= ' var google_conversion_language = "'. $google_conversion_language .'";' . "\n"; $script .= ' var google_conversion_format = "'. $google_conversion_format .'";' . "\n"; $script .= ' var google_conversion_color = "'. $google_conversion_color .'";' . "\n"; $script .= ' var google_conversion_label = "'. $google_conversion_label .'";' . "\n"; $script .= ' var google_conversion_value = '. $google_conversion_value .';' . "\n"; $script .= ' var google_remarketing_only = "'. $google_remarketing_only .'";' . "\n"; $script .= ' /* ]]> */ ' . "\n"; $script .= '</script>' . "\n"; $script .= '<script type="text/javascript" src="//www.googleadservices.com/pagead/conversion.js">' . "\n"; $script .= '</script>' . "\n"; $script .= '<noscript>' . "\n"; $script .= '<div style="display:inline;">' . "\n"; $script .= '<img height="1" width="1" border="0" src="https://www.googleadservices.com/pagead/conversion/' . $google_conversion_id . '?value='. $google_conversion_value .'&label='. $google_conversion_label .'&guid=ON&script=0" alt=""/>' . "\n"; $script .= '</div>' . "\n"; $script .= '</noscript>' . "\n"; return $script; }
Appendix: Nieuwe order onderscheppen (Drupal 7)
Let op: dpm($order)
werkt. Print-opdrachten werken vaak niet!
<?php ///////////////////////////////////////////// // Niewe order onderscheppen ///////////////////////////////////////////// // entity_insert ///////////////////////////////////////////// // function commerce_adwords_entity_insert($entity, $type) { // dpm($order); // print "<br><br><br> entity_insert"; // print phpinfo(); } // entity_presave ///////////////////////////////////////////// // function commerce_adwords_entity_presave() { // dpm(); print "<br><br><br> entity_presave"; // print phpinfo(); } // entity_update ///////////////////////////////////////////// // function commerce_adwords_entity_update($entity, $type) { // dpm(); print "<br><br><br> entity_update"; // print phpinfo(); } // commerce_checkout_complete ///////////////////////////////////////////// // function commerce_adwords_commerce_checkout_complete($order) { dpm($order); // print "<br><br><br> commerce_checkout_complete"; // print phpinfo(); } // Overige events ///////////////////////////////////////////// // function commerce_adwords_commerce_order_presave($order) { dpm($order); // print "<br><br><br> commerce_order_presave"; // print phpinfo(); } function commerce_adwords_order_state_info() { dpm($order); // print "<br><br><br> commerce_adwords_commerce_order_presave"; // print phpinfo(); } function commerce_adwords_commerce_order_status_update() { dpm($order); // print "<br><br><br> commerce_order_status_update"; // print phpinfo(); } function commerce_adwords_commerce_order_status_info() { // dpm($order); print "<br><br><br> commerce_order_status_info"; // print phpinfo(); } function commerce_adwords_init() { if (arg(0)=='checkout' && arg(2)=='complete') { print "<br><br><br> commerce_adwords_init - checkout-completed-page<br>"; } }
Appendix: Broncode Drupal Commerce AdWords
<?php // ---------------------------------------------------------------------------- // Store ID of newly created order in a session variable // ---------------------------------------------------------------------------- // function commerce_adwords_commerce_checkout_complete($order) { $_SESSION['adwords_commerce_order_number'] = $order->order_number; // dpm($order); // dpm($order->order_number); // dpm($_SESSION['adwords_commerce_order_number']); } // --------------------------------------------------------------------------------- // Output tag to completed-page // --------------------------------------------------------------------------------- // function commerce_adwords_page_alter(&$page) { if (substr(current_path(),0,8)=="checkout" and substr(current_path(),-8)=="complete") { $order = commerce_order_load($_SESSION['adwords_commerce_order_number']); $tag=create_tag($order); dpm ( $order, "Function commerce_adwords_page_alter" ); $page['footer']['block_3'] = array ( '#markup' => $tag ); } } // ---------------------------------------------------------------------------- // Create tag ($order) // ---------------------------------------------------------------------------- // // Input: Order-object for the given order_number // function create_tag ($order) { // Reset script // ------------------------------ // $script = ''; // Set AdWords parameters // --------------------------------- // $google_conversion_id = 981232345; $google_conversion_language = "en"; $google_conversion_format = "2"; $google_conversion_color = "ffffff"; $google_conversion_label = "F12345-12345qc2m1gM"; $google_remarketing_only = false; // Set conversion value // ---------------------------------- // $wrapper = entity_metadata_wrapper('commerce_order', $order); $order_total = $wrapper->commerce_order_total->amount->value(); $google_conversion_value = intval(($order_total/1.21-326))/100; // dpm($order_total); // Correct // dpm($google_conversion_value); // Correct, behalve afrondfout // Assemble Adwords tag // ------------------------------ // $script = ''; $script .= "\n" . '<!-- Drupal Commerce AdWords conversion tag -->' . "\n"; $script .= '<script type="text/javascript">' . "\n"; //$script .= '<!--' . "\n"; $script .= ' /* <![CDATA[ */' . "\n"; $script .= ' var google_conversion_id = '. $google_conversion_id .';' . "\n"; $script .= ' var google_conversion_language = "'. $google_conversion_language .'";' . "\n"; $script .= ' var google_conversion_format = "'. $google_conversion_format .'";' . "\n"; $script .= ' var google_conversion_color = "'. $google_conversion_color .'";' . "\n"; $script .= ' var google_conversion_label = "'. $google_conversion_label .'";' . "\n"; $script .= ' var google_conversion_value = '. $google_conversion_value .';' . "\n"; $script .= ' var google_remarketing_only = "'. $google_remarketing_only .'";' . "\n"; $script .= ' /* ]]> */ ' . "\n"; $script .= '</script>' . "\n"; $script .= '<script type="text/javascript" src="//www.googleadservices.com/pagead/conversion.js">' . "\n"; $script .= '</script>' . "\n"; $script .= '<noscript>' . "\n"; $script .= '<div style="display:inline;">' . "\n"; $script .= '<img height="1" width="1" border="0" src="https://www.googleadservices.com/pagead/conversion/' . $google_conversion_id . '?value='. $google_conversion_value .'&label='. $google_conversion_label .'&guid=ON&script=0" alt=""/>' . "\n"; $script .= '</div>' . "\n"; $script .= '</noscript>' . "\n"; // Return script // ------------------ // return $script; }
Zie ook
Bronnen
hook_commerce_checkout_complete
- http://www.drupalcontrib.org/api/drupal/contributions!commerce!modules!checkout!commerce_checkout.module/function/commerce_checkout_complete/7
- https://www.drupal.org/node/2108801 - Calling hook_commerce_checkout_complete from paypal_wps? » Verhelderend!
- https://www.drupal.org/node/1460964 - "When completing the checkout process" never fires if a rule sets order complete on IPN » Verhelderend
- https://drupalcommerce.org/node/1150 - RESOLVED - Completing the checkout process not firing
Order uitlezen
- http://drupal.stackexchange.com/questions/71743/how-can-i-get-the-order-id-from-commerce-using-rules
Order uitlezen » hook_entity_insert & hook_entity_update
- http://drupal.stackexchange.com/questions/91637/what-is-the-hook-when-a-commerce-order-is-updated
- https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_entity_insert/7.x
Order uitlezen '
- https://www.drupal.org/node/2430505 - Get Order Total Drupal Commerce
drupal_add_js
- https://www.drupal.org/docs/7/api/javascript-api/adding-javascript-to-your-theme-or-module
- https://api.drupal.org/api/drupal/includes%21common.inc/function/drupal_add_js/7.x
HTML invoegen op een pagina
hook_page_build()
- http://drupal.stackexchange.com/questions/19132/how-do-i-insert-html-just-before-the-body-closing-tag
- https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_page_build/7.x
Render arrays