'GET',
'callback' => array($this, 'configure_api_callback'),
));
});
add_filter('the_posts', array($this, 'the_posts_filter_callback'));
add_filter('woocommerce_add_to_cart', array($this, 'woocommerce_add_to_cart_filter_callback'));
}
public static function get_instance() {
if (!self::$instance)
self::$instance = new self;
return self::$instance;
}
private function currency_format($amount) {
// Format value to two decimal places with no thousands separator
return number_format(floatval($amount), 2, '.', '');
}
private function get_return_url() {
// Get AdRoll return url
return self::ADROLL_BASE_URL . 'ecommerce/return';
}
private function get_register_url() {
// Build AdRoll register url, which will redirect to the AdRoll buyflow after account creation
$current_user = wp_get_current_user();
$location = wc_get_base_location();
$query_args = array(
'email' => $current_user->user_email,
'first_name' => $current_user->user_firstname,
'last_name' => $current_user->user_lastname,
'url' => get_site_url(),
'country_code' => $location['country'],
'next' => '/activate/getting-started/'
);
return add_query_arg($query_args, self::ADROLL_BASE_URL . 'activate/register/?experiment=woocommerce_register');
}
private function get_adroll_admin_url() {
// URL for this plugin's admin page
return admin_url('admin.php?page=wp_adroll');
}
private function adroll_get_id($object) {
// Backwards-compatible way fo getting woocommerce object id
return method_exists($object, 'get_id') ? $object->get_id() : $object->id;
}
private function adroll_get_product($product_id) {
// Backwards-compatible way of getting woocommerce product by id
return function_exists('wc_get_product') ? wc_get_product($product_id) : get_product($product_id);
}
private function get_category($product) {
// Backwards-compatible way of getting the first woocommerce product category assigned to a product that isn't 'uncategorized'
if (method_exists($product, 'get_category_ids')) {
foreach($product->get_category_ids() as $category_id) {
$category_name = strtolower(get_term_by('id', $category_id, 'product_cat')->name);
if ($category_name && $category_name !== 'uncategorized') {
return $category_name;
}
}
} else {
foreach(wp_get_post_terms($this->adroll_get_id($product), 'product_cat') as $term){
if($term) {
$category_name = strtolower($term->name);
return $category_name !== 'uncategorized' ? $category_name : '';
}
}
}
return '';
}
public function admin_menu_action_callback() {
// Register AdRoll admin page in the WordPress admin side menu
$adroll_icon_svg_b64 = "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzNDAuOTIgMzA5LjI3Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6IzBkYmRmZn08L3N0eWxlPjwvZGVmcz48dGl0bGU+QWRSb2xsIC0gTWFyayAtIFBvc2l0aXZlPC90aXRsZT48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0zNDAuOTIgMTU0LjY0QzM0MC45MiA2OS4yMyAyNzMuODYgMCAxODYuMjkgMGExNTQuNTggMTU0LjU4IDAgMCAwLTEzNSA3OS4yNmw3NS4zOCA3NS4zOGMwLTMzIDI3LjgyLTU5LjY3IDU5LjY3LTU5LjY3QzIyMC4wOCA5NSAyNDYgMTIxLjY4IDI0NiAxNTQuNjRhNTkuMjkgNTkuMjkgMCAwIDEtNTkuNjYgNTkuNjZINTcuNzRBNTcuNzUgNTcuNzUgMCAwIDAgMCAyNzIuMDV2MzcuMjJoMTg2LjI5Yzg2LjkxIDAgMTU0LjYzLTY5LjIzIDE1NC42My0xNTQuNjMiLz48L3N2Zz4=";
add_menu_page('AdRoll', 'AdRoll', 'manage_options', 'wp_adroll', array($this, 'render_adroll_admin_page'), $adroll_icon_svg_b64);
}
public function admin_init_action_callback() {
// If the plugin has just been configured, notify the AdRoll backend that the plugin has been activated,
// and then redirect users to AdRoll to complete installation flow
if ($this->just_configured()) {
$this->notify_plugin_activated();
$adv = get_option('adroll_adv_eid');
exit(wp_redirect($this->get_return_url()));
}
// Register AdRoll settings
register_setting('AdRoll', 'adroll_adv_eid');
register_setting('AdRoll', 'adroll_pixel_eid');
add_settings_section('adroll_settings_section', null, null, 'wp_adroll');
add_settings_field('adroll_adv_eid', null, array($this, 'adroll_adv_eid_callback'), 'wp_adroll', 'adroll_settings_section');
add_settings_field('adroll_pixel_eid', null, array($this, 'adroll_pixel_eid_callback'), 'wp_adroll', 'adroll_settings_section');
}
public function admin_notices_action_callback() {
// Show admin notice if the plugin has not been configured yet
if (!$this->is_configured() && !$this->is_configuring()) {
include_once('notice.php');
}
}
private function just_configured() {
// Determines if the AdRoll plugin configuration page was just submitted in this same request
return (
isset($_GET['page']) &&
$_GET['page'] === 'wp_adroll' &&
isset($_GET['settings-updated']) &&
$_GET['settings-updated'] == 'true'
);
}
public function adroll_adv_eid_callback() {
// Render input for the adroll advertisable eid field in the config page
$val = $_GET['adv'];
echo '';
}
public function adroll_pixel_eid_callback() {
// Render input for the adroll pixel eid field in the config page
$val = $_GET['pixel'];
echo '';
}
public function wp_footer_action_callback() {
// Render AdRoll pixel in every page footer
include_once('pixel.php');
}
public function woocommerce_after_checkout_form_action_callback($checkout) {
// Check if the checkout for is being rendered, and set a flag if so
$this->woocommerce_after_checkout_form_flag = true;
}
public function woocommerce_thankyou_action_callback($order_id) {
// Save id of the order being viewed so we can query for it by id later
$this->order_id = $order_id;
}
public function pre_get_posts_action_callback($query) {
// If a search query was made, save the search term for later use
if ($query->is_search) {
$this->search_query = $query->query['s'];
}
}
public function activated_plugin_action_callback($plugin) {
// When plugin gets activated, we redirect users to the plugin's settings page
if($plugin == plugin_basename(__FILE__)) {
if ($this->is_configured()) {
$this->notify_plugin_activated();
}
if(!isset($_GET['activate-multi'])) {
exit(wp_redirect($this->get_adroll_admin_url()));
}
}
}
public function deactivated_plugin_action_callback($plugin) {
// When plugin gets deactivated, we notify the AdRoll backend
if($plugin == plugin_basename(__FILE__) && $this->is_configured()) {
$this->notify_plugin_deactivated();
}
}
public function render_adroll_admin_page() {
// Renders a template depending on the installation flow step
if ($this->is_configuring()) {
$template = 'configure.php';
} else if ($this->is_configured()) {
$template = 'status.php';
} else {
$template = 'welcome.php';
}
include_once($template);
}
private function is_configuring() {
// Check if this request is
$adv = isset($_GET['adv']);
$pixel = isset($_GET['pixel']);
return $adv && $pixel;
}
private function is_configured() {
// Check if the plugin has already been configured (if the advertisable and pixel EIDs have been saved to the db)
$adv_eid = trim((string)get_option('adroll_adv_eid'));
$pixel_eid = trim((string)get_option('adroll_pixel_eid'));
return !empty($adv_eid) && !empty($pixel_eid);
}
public function configure_api_callback($request) {
// Public API handler that takes the necessary fields (adv, pixel) and redirects to a password-protected admin
// page in which those fields will be permanently saved to the db
$adv = $request->get_param('adv');
$pixel = $request->get_param('pixel');
$query_args = array('adv' => $adv, 'pixel' => $pixel);
$url = add_query_arg($query_args, $this->get_adroll_admin_url());
exit(wp_redirect($url));
}
public function the_posts_filter_callback($posts) {
// Filter search results based on type, save array for later use but return original, unedited search results
if (!empty($posts)) {
$product_ids = array();
foreach ($posts as $post) {
if ($post->post_type === 'product') {
$product_ids[] = $post->ID;
}
}
if (empty($this->search_results)) {
$this->search_results = $product_ids;
}
}
return $posts;
}
public function woocommerce_add_to_cart_filter_callback($added_item_key) {
// Listen for add-to-cart events (ajax or not) and saves the list of products added to the cart.
global $woocommerce;
$cart = $woocommerce->cart->get_cart();
$serialized_products = $this->serialize_cart_products($cart);
// Get the ID of the product that was just added to the cart
$added_product_id = null;
foreach ($cart as $item_key => $item) {
if ($item_key === $added_item_key) {
$added_product_id = $item['product_id'];
break;
}
}
// If we could not find the product id using the item key, abort
if ($added_product_id === null) {
return;
}
// Only keep the serialized product whose ID is equal to the added product id
$products_added_to_cart = array_values(array_filter($serialized_products, function($product) use ($added_product_id) {
return (string)$product['product_id'] === (string) $added_product_id;
}));
if (is_ajax()) {
// If this was an ajax add-to-cart, we save the products to the user session, since there will be no page
// load with this requset/response cycle.
$current_value = WC()->session->get(self::PRODUCTS_ADDED_TO_CART_SESSION_KEY, array());
$new_value = array_merge($current_value, $products_added_to_cart);
WC()->session->set(self::PRODUCTS_ADDED_TO_CART_SESSION_KEY, $new_value);
} else {
// If not an ajax add-to-cart, we save the products to the current object, which will be used in this same
// request/response cycle to output the list on the next page load.
$current_value = $this->products_added_to_cart;
$new_value = array_merge($current_value, $products_added_to_cart);
$this->products_added_to_cart = $new_value;
}
}
private function get_products_added_to_cart() {
// Gather all arrays of products added to the cart (from user sesion or otherwise)
$products_added_to_cart = array();
$products_added_to_cart = array_merge($products_added_to_cart, $this->products_added_to_cart);
$products_added_to_cart = array_merge($products_added_to_cart, WC()->session->get(self::PRODUCTS_ADDED_TO_CART_SESSION_KEY, array()));
WC()->session->set(self::PRODUCTS_ADDED_TO_CART_SESSION_KEY, array());
return $products_added_to_cart;
}
private function serialize_product($product) {
// Convert Woocommerce order object into array with only necessary product data
$serialized_product = array();
$serialized_product['product_id'] = (string)$this->adroll_get_id($product);
$serialized_product['price'] = $this->currency_format($product->get_price());
$serialized_product['category'] = $this->get_category($product);
return $serialized_product;
}
private function serialize_cart_products($cart) {
// Convert Woocommerce cart item objects into arrays of products with only necessary product data
$products = array();
foreach ($cart as $item) {
$product = $this->adroll_get_product($item['product_id']);
$serialized_product = $this->serialize_product($product);
$serialized_product['quantity'] = (string)$item['quantity'];
$products[] = $serialized_product;
}
return $products;
}
private function serialize_order_products($order) {
// Converto Woocommerce order object into arrays of products with only necessary product data
$products = array();
foreach ($order->get_items() as $item) {
$product = $this->adroll_get_product($item['product_id']);
$serialized_product = $this->serialize_product($product);
$quantity = floatval(array_key_exists('quantity', $item) ? $item['quantity'] : $item['qty']);
$line_total = floatval($item['line_total']);
$serialized_product['quantity'] = (string)$quantity;
$serialized_product['price'] = $this->currency_format($line_total / $quantity);
$products[] = $serialized_product;
}
return $products;
}
private function is_search_page() {
// Check if we are on a search result page by checking if the search query var has been set
return !empty($this->search_query);
}
private function is_checkout_page() {
// Check if we are on the checkout page
return is_checkout() && $this->woocommerce_after_checkout_form_flag;
}
private function is_conversion_page() {
// Check if we are on a conversion page, and that the corresponding order order was placed no more than five
// minutes ago. This is needed to reduce the possibility of tracking the same order more than once.
if (is_order_received_page() && !is_null($this->order_id)) {
$order = wc_get_order($this->order_id);
if (version_compare(WC_VERSION, '3.0', '<')) {
$date = new DateTime($order->order_date);
} else {
$date = new DateTime($order->get_date_created());
}
$now = new DateTime();
$interval = $date->diff($now);
$years = $interval->y;
$months = $interval->m;
$days = $interval->d;
$hours = $interval->h;
$minutes = $interval->i;
$minutes_since_conversion = ($years * 365 * 24 * 60) + ($months * 30 * 24 * 60) + ($days * 24 * 60) + ($hours * 60) + $minutes;
return $minutes_since_conversion < 5;
}
return false;
}
private function get_current_page() {
// Determine the current page that the user is on
if (is_front_page()) {
return 'home_page';
} else if (is_product()) {
return 'product_page';
} else if ($this->is_conversion_page()) {
return 'conversion_page';
} else if ($this->is_checkout_page()) {
return 'checkout_page';
} else if ($this->is_search_page()) {
return 'search_page';
} else if (is_cart()) {
return 'cart_page';
} else {
return 'other';
}
}
public function get_global_vars() {
// Create single array that contains every piece of data needed by the pixel to fire the corresponding event
// for the current page.
// We can't fire the pixel on ajax calls, so we skip all of this logic
if (is_ajax()) {
return;
}
$global_vars = array();
$global_vars['adroll_current_page'] = $this->get_current_page();
$global_vars['adroll_currency'] = get_woocommerce_currency();
$global_vars['adroll_language'] = get_locale();
$email = (string)wp_get_current_user()->user_email;
if (!empty($email)) {
$global_vars['adroll_email'] = md5($email);
}
$products_added_to_cart = $this->get_products_added_to_cart();
if (!empty($products_added_to_cart)) {
$global_vars['adroll_products_added_to_cart'] = $products_added_to_cart;
}
switch($this->get_current_page()) {
case 'search_page':
$global_vars['adroll_keywords'] = $this->search_query;
$global_vars['adroll_product_id'] = $this->search_results;
break;
case 'product_page':
$products = array();
global $product;
if ($product->is_type('grouped')) {
foreach ($product->get_children() as $id) {
$child = $this->adroll_get_product($id);
$products[] = $this->serialize_product($child);
}
} else {
$products[] = $this->serialize_product($product);
}
$global_vars['adroll_products'] = $products;
break;
case 'checkout_page':
case 'cart_page':
$global_vars['adroll_products'] = $this->serialize_cart_products(WC()->cart->get_cart());
break;
case 'conversion_page':
$order = wc_get_order($this->order_id);
$global_vars['adroll_products'] = $this->serialize_order_products($order);
$global_vars['adroll_conversion_value'] = $this->currency_format($order->get_total());
$global_vars['adroll_order_id'] = (string)$this->adroll_get_id($order);
break;
case 'home_page':
default:
// No special vars needed for homepage or other pages
break;
}
return $global_vars;
}
private function notify_plugin_activated() {
$plugin_activate_url = self::ADROLL_BASE_URL . 'woocommerce/plugin_activate/' . get_option('adroll_adv_eid');
wp_remote_get($plugin_activate_url);
}
private function notify_plugin_deactivated() {
$plugin_deactivate_url = self::ADROLL_BASE_URL . 'woocommerce/plugin_deactivate/' . get_option('adroll_adv_eid');
wp_remote_get($plugin_deactivate_url);
}
}
$AdRoll_For_WooCommerce = AdRoll_For_WooCommerce::get_instance();