class AcquiaLiftAgent in Acquia Lift Connector 7
Hierarchy
- class \AcquiaLiftAgent extends \PersonalizeAgentBase implements \PersonalizeAgentGoalInterface, \PersonalizeExplicitTargetingInterface, \PersonalizeAutoTargetingInterface, \PersonalizeAgentReportInterface, AcquiaLiftAgentInterface
Expanded class hierarchy of AcquiaLiftAgent
1 string reference to 'AcquiaLiftAgent'
- acquia_lift_get_agent_types in ./
acquia_lift.module - Returns the agent types this module provides.
File
- plugins/
agent_types/ AcquiaLiftAgent.inc, line 159 - Provides an agent type for Acquia Lift
View source
class AcquiaLiftAgent extends PersonalizeAgentBase implements PersonalizeAgentGoalInterface, AcquiaLiftAgentInterface, PersonalizeExplicitTargetingInterface, PersonalizeAutoTargetingInterface, PersonalizeAgentReportInterface {
/**
* An object containing the agent data.
*
* @var stdClass
*/
protected $agent;
/**
* An instance of AcquiaLiftAPI.
*
* @var AcquiaLiftAPI
*/
protected $liftAPI;
/**
* An instance of PersonalizeAgentReportInterface.
*
* The agent acts as a facade to the reporting class for reporting requests.
*
* @var PersonalizeAgentReportInterface
*/
protected $reporting;
/**
* An instance of DrupalQueueInterface
*
* @var DrupalQueueInterface
*/
protected $queue;
protected $globalConfig;
/**
* Implements PersonalizeAgentInterface::create().
*/
public static function create($agent_data) {
try {
$acquia_lift_api = AcquiaLiftAPI::getInstance(variable_get('acquia_lift_account_info', array()));
$status = personalize_agent_get_status($agent_data->machine_name);
$config = array(
'confidence_measure' => variable_get('acquia_lift_confidence_measure', 95),
'minimum_runtime' => acquia_lift_config_min_runtime(),
'minimum_decisions' => variable_get('acquia_lift_min_decisions', 1000),
);
return new static($agent_data->machine_name, $agent_data->label, $agent_data->data, $status, !empty($agent_data->started) ? $agent_data->started : NULL, $acquia_lift_api, $config);
} catch (AcquiaLiftException $e) {
watchdog('Acquia Lift', 'Unable to instantiate Acquia Lift Agent');
return NULL;
}
}
/**
* Constructs an Acquia Lift agent.
*
* @param stdClass $agent_data
* An object containing the agent data.
* @param $acquia_lift_api
* An instance of the AcquiaLiftAPI class.
*/
public function __construct($machine_name, $title, $data, $status, $started, AcquiaLiftAPI $acquia_lift_api, $global_config) {
parent::__construct($machine_name, $title, $data, $status, $started);
$this->liftAPI = $acquia_lift_api;
$this->globalConfig = $global_config;
}
/**
* Implements PersonalizeAgentInterface::getType();
*/
public function getType() {
return 'acquia_lift';
}
/**
* Implements PersonalizeAgentInterface::getAssets().
*/
public function getAssets() {
$path = drupal_get_path('module', 'acquia_lift');
return array(
'js' => array(
array(
'type' => 'setting',
'data' => array(
'acquia_lift' => array(
'apiKey' => $this->liftAPI
->getApiKey(),
'owner' => $this->liftAPI
->getOwnerCode(),
'baseUrl' => $this->liftAPI
->getApiUrl(),
'featureStringReplacePattern' => AcquiaLiftAPI::FEATURE_STRING_REPLACE_PATTERN,
'featureStringMaxLength' => AcquiaLiftAPI::FEATURE_STRING_MAX_LENGTH,
// @todo Add support for mutually exclusive values.
'featureStringSeparator' => AcquiaLiftAPI::FEATURE_STRING_SEPARATOR_NONMUTEX,
'batchMode' => variable_get('acquia_lift_batch_decisions', FALSE),
),
),
),
// Add the Acquia Lift API js wrapper and the Acquia Lift integration js.
$path . '/js/acquia_lift.js' => array(
'type' => 'file',
'scope' => 'footer',
'defer' => TRUE,
),
),
'library' => array(
array(
'acquia_lift',
'acquia_lift.agent_api',
),
),
);
}
/**
* Implements PersonalizeAgentGoalInterface::useClientSideGoalDelivery().
*/
public function useClientSideGoalDelivery() {
return variable_get('acquia_lift_client_side_goals', TRUE);
}
/**
* Implements PersonalizeAgentGoalInterface::sendGoal().
*/
public function sendGoal($goal_name, $value = NULL) {
// @todo Implement server-side goal delivery.
}
/**
* Implements PersonalizeAgentInterface::optionsForm().
*/
public static function optionsForm($agent_data, $option_parents = array()) {
return self::buildOptionsForm($agent_data, FALSE, $option_parents);
}
/**
* Builds the options to display for a campaign form.
*
* @param $agent_data
* The existing campaign data.
* @param bool $simplified
* Indicates if the form should be shown in simplified format or with all
* advanced options.
*
* @return array
* The form render array.
*/
protected static function buildOptionsForm($agent_data, $simplified = FALSE, $option_parents = array()) {
$account_info = variable_get('acquia_lift_account_info', array());
if (empty($account_info)) {
drupal_set_message(t('Your Acquia Lift account info has not been configured. Any Acquia Lift campaigns you create here will not work until you configure your account info !here', array(
'!here' => l('here', 'admin/config/content/personalize/acquia_lift'),
)), 'error');
}
$form = array();
$form['#attached'] = array(
'css' => array(
drupal_get_path('module', 'acquia_lift') . '/css/personalize_acquia_lift_admin.css',
drupal_get_path('module', 'acquia_lift') . '/css/acquia_lift.admin.css',
),
'js' => array(
drupal_get_path('module', 'acquia_lift') . '/js/acquia_lift.agent.admin.js',
),
);
if (empty($option_parents)) {
$option_parents = array(
'agent_basic_info',
'options',
'acquia_lift',
);
}
$control_rate = isset($agent_data->data['control_rate']) ? $agent_data->data['control_rate'] : 10;
if ($simplified) {
$form['control_rate'] = array(
'#type' => 'value',
'#value' => $control_rate,
);
}
else {
$form['control'] = array(
'#type' => 'fieldset',
'#tree' => FALSE,
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#title' => t('Control (@control_rate%)', array(
'@control_rate' => $control_rate,
)),
);
$control_rate_parents = $option_parents;
$control_rate_parents[] = 'control_rate';
$form['control']['control_rate'] = array(
'#type' => 'acquia_lift_percentage',
'#parents' => $control_rate_parents,
'#title' => t('Control Group'),
'#field_suffix' => '%',
'#size' => 3,
'#description' => t('A fixed baseline variation will be shown, by default the first variation in the set.'),
'#default_value' => $control_rate,
'#rest_title' => t('Test Group'),
'#rest_description' => t('Personalized variations will be shown.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
}
$decision_style = isset($agent_data->data['decision_style']) ? $agent_data->data['decision_style'] : 'adaptive';
if ($simplified) {
$form['decision_style'] = array(
'#type' => 'value',
'#value' => $decision_style,
);
}
else {
$form['decision_style'] = array(
'#type' => 'radios',
'#title' => t('Decision Style'),
'#options' => array(
'adaptive' => t('Auto-personalize'),
'random' => t('Test only'),
),
'#default_value' => $decision_style,
'#title_display' => 'invisible',
);
$form['decision_style']['adaptive'] = array(
'#description' => t('Adapts to users and chooses the best option over time.'),
);
$form['decision_style']['random'] = array(
'#description' => t('Tests variations and reports results.'),
);
}
$explore_rate = isset($agent_data->data['explore_rate']) ? $agent_data->data['explore_rate'] : 20;
if ($simplified) {
$form['distribution'] = array(
'#type' => 'value',
'#value' => $explore_rate,
);
}
else {
$decision_style_parents = $option_parents;
$decision_style_parents[] = 'decision_style';
$decision_style_form_element = '';
foreach ($decision_style_parents as $i => $parent_name) {
$decision_style_form_element .= $i ? '[' . $parent_name . ']' : $parent_name;
}
$form['distribution'] = array(
'#type' => 'fieldset',
'#tree' => FALSE,
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#title' => t('Distribution (@explore_rate/@rest)', array(
'@explore_rate' => $explore_rate,
'@rest' => 100 - $explore_rate,
)),
'#states' => array(
'visible' => array(
':input[name="' . $decision_style_form_element . '"]' => array(
'value' => 'adaptive',
),
),
),
);
$explore_rate_parents = $option_parents;
$explore_rate_parents[] = 'explore_rate';
$form['distribution']['explore_rate'] = array(
'#type' => 'acquia_lift_percentage',
'#parents' => $explore_rate_parents,
'#title' => t('Random Group'),
'#field_suffix' => '%',
'#description' => t('Variations will be shown randomly and tracked to adjust for false positives.'),
'#size' => 3,
'#default_value' => isset($agent_data->data['explore_rate']) ? $agent_data->data['explore_rate'] : 20,
'#rest_title' => t('Personalized Group'),
'#rest_description' => t('The "best" variation will be shown for each visitor based on our algorithm.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
}
// This will get overridden by the 'campaign_end' dropdown.
$form['auto_stop'] = array(
'#type' => 'value',
'#value' => 0,
);
return $form;
}
/**
* Implements PersonalizeAgentInterface::optionsFormValidate().
*/
public static function optionsFormValidate($form, &$form_state, $option_parents = array()) {
$values =& $form_state['values'];
foreach ($option_parents as $parent) {
$values =& $values[$parent];
}
$error_parents = implode('][', $option_parents);
if (isset($values['control_rate'])) {
$rate = $values['control_rate'];
if (!is_numeric($rate) || !($rate >= 0 && $rate <= 100)) {
$error_element = empty($error_parents) ? 'control_rate' : $error_parents . '][contral_rate';
form_set_error($error_element, t('Invalid percent to test specified'));
}
}
if (isset($values['explore_rate'])) {
$rate = $values['explore_rate'];
if (!is_numeric($rate) || !($rate >= 0 && $rate <= 100)) {
$error_element = empty($error_parents) ? 'explore_rate' : $error_parents . '][explore_rate';
form_set_error($error_element, t('Invalid percent to test specified'));
}
}
// If "Run until a winner is found" was selected, we need to add this to the agent's
// data property.
if (isset($form_state['values']['campaign_end']) && $form_state['values']['campaign_end'] == 'auto') {
$values['auto_stop'] = 1;
}
}
/**
* Implements PersonalizeAgentInterface::postSave().
*/
public function postSave($old_data) {
$items = $this
->getAgentSyncOperations(isset($old_data->data['visitor_context']['acquia_lift_context']));
$this
->queueItems($items);
}
/**
* Returns the operations needed to sync an agent to Acquia Lift.
*
* @param $targeting_rule_exists
* Whether the existing agent in Lift has a targeting rule set up that may
* need to be deleted.
* @return array
* An array of items representing API calls to be made to Acquia Lift.
*/
public function getAgentSyncOperations($targeting_rule_exists = FALSE) {
$items = array();
$acquia_lift_control_rate = 0.1;
$acquia_lift_explore_rate = 0.2;
if (isset($this->data['control_rate'])) {
// Acquia Lift takes the control rate as a number between 0 and 1.
$acquia_lift_control_rate = $this->data['control_rate'] / 100;
}
if (isset($this->data['explore_rate']) && isset($this->data['decision_style'])) {
if ($this->data['decision_style'] === 'adaptive') {
// Acquia Lift takes the explore rate as a number between 0 and 1.
$acquia_lift_explore_rate = $this->data['explore_rate'] / 100;
}
else {
// If the decision style is to only test, then the explore rate is the
// full group.
$acquia_lift_explore_rate = 1;
}
}
// Add an item for saving the agent to Acquia Lift.
$items[] = array(
'method' => 'saveAgent',
'args' => array(
$this->machineName,
$this->title,
$this->data['decision_style'],
$this->status,
$acquia_lift_control_rate,
$acquia_lift_explore_rate,
isset($this->data['cache_decisions']) && $this->data['cache_decisions'],
),
);
$acquia_lift_context_needs_deleting = $targeting_rule_exists;
if (isset($this->data['visitor_context']['acquia_lift_context'])) {
// Whereas non-Acquia Lift visitor_context plugins operate by adding extra
// info at the time of getting a decision, Acquia Lift targeting needs to
// be set up on the Acquia Lift side before any decisions are made.
$auto_targeting = array_filter($this->data['visitor_context']['acquia_lift_context']);
if (!empty($auto_targeting)) {
$acquia_lift_context_needs_deleting = FALSE;
// Add an item for saving the targeting rule.
$items[] = array(
'method' => 'saveAutoTargetingRule',
'args' => array(
$this->machineName,
array_keys($auto_targeting),
),
);
}
}
if ($acquia_lift_context_needs_deleting) {
// Acquia Lift may have a targeting rule for this agent, so we need
// to delete it.
$items[] = array(
'method' => 'deleteAutoTargetingRule',
'args' => array(
$this->machineName,
),
);
}
return $items;
}
/**
* Implements PersonalizeExplicitTargetingInterface::explicitTargetingSupportMultiple().
*/
public static function explicitTargetingSupportMultiple() {
return PersonalizeExplicitTargetingInterface::EXPLICIT_TARGETING_MULTIPLE_BOTH;
}
/**
* Implements PersonalizeAutoTargetingInterface::constrainExplicitTargetingContexts().
*/
public static function constrainExplicitTargetingContexts() {
return TRUE;
}
/**
* Implements PersonalizeAgentInterface::convertContextToFeatureString().
*
* This is essentially a PHP version of the js in the convertContextToFeatureString
* function in acquia_lift.js
*/
public static function convertContextToFeatureString($name, $value, $is_mutex = FALSE) {
$separator = $is_mutex ? AcquiaLiftAPI::FEATURE_STRING_SEPARATOR_MUTEX : AcquiaLiftAPI::FEATURE_STRING_SEPARATOR_NONMUTEX;
$prefix_max_length = floor((AcquiaLiftAPI::FEATURE_STRING_MAX_LENGTH - strlen($separator)) / 2);
$prefix = AcquiaLiftAPI::cleanFeatureString($name);
$value = AcquiaLiftAPI::cleanFeatureString($value);
// Make a string of the visitor context item in the format Acquia Lift can
// consume.
$feature_string = $prefix . $separator . $value;
while (strlen($feature_string) > AcquiaLiftAPI::FEATURE_STRING_MAX_LENGTH) {
// Acquia Lift has a hard character limit for feature strings.
if (strlen($prefix) > $prefix_max_length) {
// Start by truncating the prefix down to half the max length.
$prefix = substr($prefix, 0, $prefix_max_length);
$feature_string = $prefix . $separator . $value;
}
else {
// Otherwise just truncate the whole thing down to the max length.
$feature_string = substr($feature_string, 0, AcquiaLiftAPI::FEATURE_STRING_MAX_LENGTH);
}
}
return $feature_string;
}
/**
* Get a reference to this agent's reporting class.
*
* Allows for delayed instantiation of reporting class.
*/
protected function getReporting() {
if (empty($this->reporting)) {
$this->reporting = AcquiaLiftReportFactory::create($this, $this->liftAPI);
}
return $this->reporting;
}
/**
* Implements PersonalizeAgentReportInterface::renderStatsForOptionSet().
*
* A facade to the reporting class.
*/
public function renderStatsForOptionSet($option_set, $date_from, $date_to = NULL) {
return $this
->getReporting()
->renderStatsForOptionSet($option_set, $date_from, $date_to);
}
/**
* Implements PersonalizeAgentReportInterface::buildCampaignReports().
*
* A facade to the reporting class.
*/
public function buildCampaignReports($options) {
return $this
->getReporting()
->buildCampaignReports($options);
}
/**
* Implements AcquiaLiftReportInterface()::buildConversionReport().
*/
public function buildConversionReport($options) {
return $this
->getReporting()
->buildConversionReport($options);
}
/**
* Implements AcquiaLiftAgent::convertOptionSetsToDecisions().
*/
public static function convertOptionSetsToDecisions($option_sets) {
$points = array();
foreach ($option_sets as $option_set) {
// If for some reason one of our option sets is missing a point name or
// decision name, throw an exception as we cannot proceed.
if (!isset($option_set->decision_point) || !isset($option_set->decision_name)) {
throw new AcquiaLiftException('Cannot convert option sets to a structured decision hierarchy without decision points and decision names');
}
$points[$option_set->decision_point] = isset($points[$option_set->decision_point]) ? $points[$option_set->decision_point] : array();
$points[$option_set->decision_point][$option_set->decision_name] = isset($points[$option_set->decision_point][$option_set->decision_name]) ? $points[$option_set->decision_point][$option_set->decision_name] : array();
foreach ($option_set->options as $option) {
$points[$option_set->decision_point][$option_set->decision_name][] = $option['option_id'];
}
}
return $points;
}
/**
* Implements PersonalizeAgentInterface::errors().
*/
public function errors() {
$errors = array();
try {
$acquia_lift_agent = $this->liftAPI
->getAgent($this->machineName);
} catch (AcquiaLiftException $e) {
return $this
->convertAgentExceptionToErrors($e, $errors);
}
if ($acquia_lift_agent['status'] === AcquiaLiftAPI::PROVISIONAL_STATUS) {
$errors[] = t('The status of the Acquia Lift agent is @status', array(
'@status' => $acquia_lift_agent['status'],
));
}
// Make sure Acquia Lift knows about the agent's goals.
$goals = personalize_goal_load_by_conditions(array(
'agent' => $this->machineName,
));
$discrepancies = FALSE;
if (empty($goals)) {
// Acquia Lift agents need goals.
$errors[] = t('No goals have been set up for this agent');
}
try {
$acquia_lift_goals = $this->liftAPI
->getGoalsForAgent($this->machineName);
} catch (AcquiaLiftException $e) {
return $this
->convertAgentExceptionToErrors($e, $errors);
}
foreach ($goals as $goal) {
if (!in_array($goal->action, $acquia_lift_goals)) {
$errors[] = t('Goal @goal has not been sync\'d to the Acquia Lift agent.', array(
'@goal' => $goal->action,
));
$discrepancies = TRUE;
}
}
// Make sure all decision points are known by Acquia Lift.
$option_sets = personalize_option_set_load_by_agent($this->machineName);
if (empty($option_sets)) {
// Acquia Lift agents need option sets.
$errors[] = t('No variation sets have been set up for this agent');
}
$decision_tree = self::convertOptionSetsToDecisions($option_sets);
try {
$acquia_lift_points = $this->liftAPI
->getPointsForAgent($this->machineName);
} catch (AcquiaLiftException $e) {
return $this
->convertAgentExceptionToErrors($e, $errors);
}
foreach ($decision_tree as $point => $decisions) {
if (!in_array($point, $acquia_lift_points)) {
$errors[] = t('Point @point has not been sync\'d to the Acquia Lift agent.', array(
'@point' => $point,
));
$discrepancies = TRUE;
continue;
}
try {
$acquia_lift_decisions = $this->liftAPI
->getDecisionsForPoint($this->machineName, $point);
} catch (AcquiaLiftException $e) {
return $this
->convertAgentExceptionToErrors($e, $errors);
}
foreach ($decisions as $decision_name => $options) {
if (!in_array($decision_name, $acquia_lift_decisions)) {
$errors[] = t('Decision @decision has not been sync\'d to the Acquia Lift agent.', array(
'@decision' => $decision_name,
));
$discrepancies = TRUE;
}
try {
$acquia_lift_choices = $this->liftAPI
->getChoicesForDecision($this->machineName, $point, $decision_name);
} catch (AcquiaLiftException $e) {
return $this
->convertAgentExceptionToErrors($e, $errors);
}
foreach ($options as $option) {
if (!in_array($option, $acquia_lift_choices)) {
$errors[] = t('Option @choice has not been sync\'d to the Acquia Lift agent.', array(
'@choice' => $option,
));
$discrepancies = TRUE;
}
}
}
// @todo Check the fixed targeting for each decision point.
}
if ($discrepancies) {
// Add a general message about how to resolve discrepancies.
$message = t('To resolve the discrepancies between your agent configuration here and what has been sync\'d to the Acquia Lift service, try saving your campaign and its variation sets and goals again.');
if (user_access('administer site configuration')) {
$message .= t(' If that still does not resolve it, try <a href="@cron">running cron</a>.', array(
'@cron' => url('admin/reports/status/run-cron'),
));
}
$errors[] = $message;
}
return $errors;
}
/**
* Implements PersonalizeAgentInterface::stopNow().
*/
public function stopNow() {
// Check whether the agent is configured to stop once a winner's been found.
if (!isset($this->data['auto_stop']) || !$this->data['auto_stop']) {
return parent::stopNow();
}
// If neither minimum runtime nor minimum decisions has been configured,
// then return FALSE.
if (!$this->globalConfig['minimum_runtime'] && !$this->globalConfig['minimum_decisions']) {
return FALSE;
}
// If there a minimum runtime, check that it has been reached.
$time_now = time();
if ($this->globalConfig['minimum_runtime']) {
$runtime = $time_now - $this->startTime;
if ($runtime < $this->globalConfig['minimum_runtime']) {
// Campaign has not been running long enough.
return FALSE;
}
}
// Need to go through each option set for this agent and establish whether each
// of them satisfies the minimum number of decisions made, if this has been set.
$option_sets = $this->data['decisions'];
if (empty($option_sets)) {
return FALSE;
}
$decision_points = self::convertOptionSetsToDecisions($option_sets);
$points = array_keys($decision_points);
$winners = array();
foreach ($points as $point) {
$num_decisions = 0;
$vMeans = array();
// Get the confidence report directly from Lift.
$report_options = array(
'confidence-measure' => $this->globalConfig['confidence_measure'],
'aggregated-over-dates' => TRUE,
'features' => "(none)",
);
$confidence_report = $this->liftAPI
->getConfidenceReport($this->machineName, $this->startTime, $time_now, $point, $report_options);
$items = $confidence_report['data']['items'];
if (empty($items)) {
return FALSE;
}
foreach ($items as $item) {
$choice = $item['choice'];
$vMeans[$choice] = $item['vMean'];
$num_decisions += $item['count'];
}
if ($this->globalConfig['minimum_decisions']) {
// If any of the decision points has fewer than the required minimum number
// of decisions, don't stop the campaign.
if ($num_decisions < $this->globalConfig['minimum_decisions']) {
return FALSE;
}
}
// Find the variation with the highest estimated value.
arsort($vMeans);
$winners[$point] = key($vMeans);
}
// If we reach here, we have a winner at each decision point and have satisfied all
// criteria for choosing a winner and pausing the campaign. Get all option sets for the
// agent and set the winner for each one.
$option_sets = personalize_option_set_load_by_agent($this->machineName);
foreach ($winners as $point => $choice) {
// The "winner" at any decision point might be a single decision or a combination
// of decisions, in which case they'll be separated by a comma.
$decisions = explode(',', $choice);
foreach ($decisions as $decision) {
list($decision_name, $option_id) = explode(':', $decision);
// Get all option sets for this decision name.
foreach ($option_sets as $osid => $option_set) {
if ($option_set->decision_point == $point && $option_set->decision_name == $decision_name) {
$option_set->winner = $option_id;
personalize_option_set_save($option_set);
}
}
}
}
return TRUE;
}
/**
* Converts an exception thrown by the API class into errors added to the passed in array.
*
* @param AcquiaLiftException $e
* The excetion that was thrown.
* @param $errors
* An array of errors to add to.
* @return array
* The new errors array.
*/
protected function convertAgentExceptionToErrors(AcquiaLiftException $e, &$errors) {
if ($e instanceof AcquiaLiftNotFoundException) {
$errors[] = t('This agent has not yet been pushed to Acquia Lift');
}
else {
$errors[] = t('There was a problem communicating with the Acquia Lift server.');
}
return $errors;
}
/**
* Returns a queue to use.
*
* @return DrupalQueueInterface
*/
protected function getQueue() {
if ($this->queue !== NULL) {
return $this->queue;
}
return DrupalQueue::get('acquia_lift_sync');
}
/**
* Sets the queue to use.
*
* @param DrupalQueueInterface $queue
*/
public function setQueue(DrupalQueueInterface $queue) {
$this->queue = $queue;
}
/**
* Implements AcquiaLiftAgentInterface::syncAgentStatus().
*/
public function syncAgentStatus() {
$items = array();
$items[] = array(
'method' => 'updateAgentStatus',
'args' => array(
$this->machineName,
$this->status,
),
);
$this
->queueItems($items);
}
/**
* Implements AcquiaLiftAgentInterface::syncDecisions().
*/
public function syncDecisions($old_decisions, $new_decisions) {
$items = $this
->getDecisionSyncOperations($old_decisions, $new_decisions);
$this
->queueItems($items);
}
/**
* Returns the operations needed to sync decisions to Acquia Lift.
*
* @param $old_decisions
* An array representing the old decisions Acquia Lift knows about.
* @param $new_decisions
* An array representing the new decisions.
* @return array
* An array of items representing API calls to be made to Acquia Lift.
*/
public function getDecisionSyncOperations($old_decisions, $new_decisions) {
$items = array();
// Save everything in $new_decisions to Acquia Lift.
foreach ($new_decisions as $point => $decisions) {
$items[] = array(
'method' => 'savePoint',
'args' => array(
$this->machineName,
$point,
),
);
foreach ($decisions as $decision_name => $choices) {
$items[] = array(
'method' => 'saveDecision',
'args' => array(
$this->machineName,
$point,
$decision_name,
),
);
foreach ($choices as $choice) {
$items[] = array(
'method' => 'saveChoice',
'args' => array(
$this->machineName,
$point,
$decision_name,
$choice,
),
);
}
}
}
// Now remove anything that was in $old_decisions but not in
// $new_decisions.
foreach ($old_decisions as $point => $decisions) {
if (!isset($new_decisions[$point])) {
$items[] = array(
'method' => 'deletePoint',
'args' => array(
$this->machineName,
$point,
),
);
}
else {
foreach ($decisions as $decision_name => $choices) {
if (!isset($new_decisions[$point][$decision_name])) {
// Delete this decision from the decision point.
$items[] = array(
'method' => 'deleteDecision',
'args' => array(
$this->machineName,
$point,
$decision_name,
),
);
}
else {
foreach ($choices as $choice) {
if (!in_array($choice, $new_decisions[$point][$decision_name])) {
// Delete this choice from the decision.
$items[] = array(
'method' => 'deleteChoice',
'args' => array(
$this->machineName,
$point,
$decision_name,
$choice,
),
);
}
}
}
}
}
}
return $items;
}
/**
* Implements AcquiaLiftAgentInterface::syncGoals().
*/
public function syncGoals($old_goals, $new_goals) {
$items = $this
->getGoalSyncOperations($old_goals, $new_goals);
$this
->queueItems($items);
}
/**
* Returns the operations needed to sync goals to Acquia Lift.
*
* @param $old_goals
* An array representing the old goals Acquia Lift knows about.
* @param $new_goals
* An array representing the new goals.
* @return array
* An array of items representing API calls to be made to Acquia Lift.
*/
public function getGoalSyncOperations($old_goals, $new_goals) {
$items = array();
// Save the new goals to Acquia Lift
foreach ($new_goals as $goal_name => $goal_value) {
$items[] = array(
'method' => 'saveGoal',
'args' => array(
$this->machineName,
$goal_name,
),
);
}
// Now delete any old goals that are not in the new goals array.
foreach ($old_goals as $goal_name => $goal_value) {
if (!isset($new_goals[$goal_name])) {
$items[] = array(
'method' => 'deleteGoal',
'args' => array(
$this->machineName,
$goal_name,
),
);
}
}
return $items;
}
/**
* Implements AcquiaLiftAgentInterface::syncFixedTargeting().
*/
public function syncFixedTargeting($option_sets) {
$items = $this
->getFixedTargetingSyncOperations($option_sets);
$this
->queueItems($items);
}
/**
* Returns the operations needed to sync goals to Acquia Lift.
*
* @param $option_sets
* An array representing the option sets whose targeting rules need to be sync'd.
* @return array
* An array of items representing API calls to be made to Acquia Lift.
*/
public function getFixedTargetingSyncOperations($option_sets) {
$items = array();
// If any of this agent's option sets has explicit targeting mappings configured,
// we need to send these mappings to Acquia Lift.
$mappings = array();
foreach ($option_sets as $option_set) {
if (empty($option_set->targeting)) {
continue;
}
$point_name = $option_set->decision_point;
$decision_name = $option_set->decision_name;
$mappings[$point_name] = isset($mappings[$point_name]) ? $mappings[$point_name] : array();
foreach ($option_set->targeting as $targ) {
if (!isset($targ['option_id'])) {
continue;
}
if (isset($targ['targeting_features'])) {
// Check if we're supposed to AND or OR mulitple features together.
if (isset($targ['targeting_strategy']) && $targ['targeting_strategy'] == 'AND') {
// Create a single mapping, with a comma-separated list of features.
$mappings[$point_name][] = array(
'feature' => implode(',', $targ['targeting_features']),
'decision' => $decision_name . ':' . $targ['option_id'],
);
}
else {
// Create a mapping for each feature and they will be OR'd together.
foreach ($targ['targeting_features'] as $feature) {
$mappings[$point_name][] = array(
'feature' => $feature,
'decision' => $decision_name . ':' . $targ['option_id'],
);
}
}
}
}
}
// Send mappings per decision point.
foreach ($mappings as $point_name => $map) {
$items[] = array(
'method' => 'saveFixedTargetingMapping',
'args' => array(
$this->machineName,
$point_name,
$map,
),
);
}
return $items;
}
/**
* Adds items to the queue and sets a message to inform the user.
*
* @param $items
* An array of items to add to the queue.
*/
protected function queueItems($items) {
if (!empty($items)) {
foreach ($items as $item) {
// Create a hash of the item. This will prevent duplicate items from
// being added to the queue.
$hash = md5(serialize($item));
$item['hash'] = $hash;
// We add the agent machine name to the queue item so that if it fails
// the agent in question can be paused.
$item['agent'] = $this->machineName;
$this
->getQueue()
->createItem($item);
}
// Make sure the queue gets triggered on the next request.
$_SESSION['acquia_lift_queue_trigger'] = 1;
}
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
AcquiaLiftAgent:: |
protected | property | An object containing the agent data. | |
AcquiaLiftAgent:: |
protected | property | ||
AcquiaLiftAgent:: |
protected | property | An instance of AcquiaLiftAPI. | |
AcquiaLiftAgent:: |
protected | property | An instance of DrupalQueueInterface | |
AcquiaLiftAgent:: |
protected | property | An instance of PersonalizeAgentReportInterface. | |
AcquiaLiftAgent:: |
public | function | Implements PersonalizeAgentReportInterface::buildCampaignReports(). | |
AcquiaLiftAgent:: |
public | function |
Implements AcquiaLiftReportInterface()::buildConversionReport(). Overrides AcquiaLiftAgentInterface:: |
|
AcquiaLiftAgent:: |
protected static | function | Builds the options to display for a campaign form. | |
AcquiaLiftAgent:: |
public static | function | Implements PersonalizeAutoTargetingInterface::constrainExplicitTargetingContexts(). | |
AcquiaLiftAgent:: |
protected | function | Converts an exception thrown by the API class into errors added to the passed in array. | |
AcquiaLiftAgent:: |
public static | function | Implements PersonalizeAgentInterface::convertContextToFeatureString(). | |
AcquiaLiftAgent:: |
public static | function |
Implements AcquiaLiftAgent::convertOptionSetsToDecisions(). Overrides AcquiaLiftAgentInterface:: |
|
AcquiaLiftAgent:: |
public static | function | Implements PersonalizeAgentInterface::create(). | |
AcquiaLiftAgent:: |
public | function | Implements PersonalizeAgentInterface::errors(). | |
AcquiaLiftAgent:: |
public static | function | Implements PersonalizeExplicitTargetingInterface::explicitTargetingSupportMultiple(). | |
AcquiaLiftAgent:: |
public | function | Returns the operations needed to sync an agent to Acquia Lift. | |
AcquiaLiftAgent:: |
public | function | Implements PersonalizeAgentInterface::getAssets(). | |
AcquiaLiftAgent:: |
public | function | Returns the operations needed to sync decisions to Acquia Lift. | |
AcquiaLiftAgent:: |
public | function | Returns the operations needed to sync goals to Acquia Lift. | |
AcquiaLiftAgent:: |
public | function | Returns the operations needed to sync goals to Acquia Lift. | |
AcquiaLiftAgent:: |
protected | function | Returns a queue to use. | |
AcquiaLiftAgent:: |
protected | function | Get a reference to this agent's reporting class. | |
AcquiaLiftAgent:: |
public | function | Implements PersonalizeAgentInterface::getType(); | |
AcquiaLiftAgent:: |
public static | function | Implements PersonalizeAgentInterface::optionsForm(). | |
AcquiaLiftAgent:: |
public static | function | Implements PersonalizeAgentInterface::optionsFormValidate(). | |
AcquiaLiftAgent:: |
public | function | Implements PersonalizeAgentInterface::postSave(). | |
AcquiaLiftAgent:: |
protected | function | Adds items to the queue and sets a message to inform the user. | |
AcquiaLiftAgent:: |
public | function | Implements PersonalizeAgentReportInterface::renderStatsForOptionSet(). | |
AcquiaLiftAgent:: |
public | function | Implements PersonalizeAgentGoalInterface::sendGoal(). | |
AcquiaLiftAgent:: |
public | function | Sets the queue to use. | |
AcquiaLiftAgent:: |
public | function | Implements PersonalizeAgentInterface::stopNow(). | |
AcquiaLiftAgent:: |
public | function |
Implements AcquiaLiftAgentInterface::syncAgentStatus(). Overrides AcquiaLiftAgentInterface:: |
|
AcquiaLiftAgent:: |
public | function |
Implements AcquiaLiftAgentInterface::syncDecisions(). Overrides AcquiaLiftAgentInterface:: |
|
AcquiaLiftAgent:: |
public | function |
Implements AcquiaLiftAgentInterface::syncFixedTargeting(). Overrides AcquiaLiftAgentInterface:: |
|
AcquiaLiftAgent:: |
public | function |
Implements AcquiaLiftAgentInterface::syncGoals(). Overrides AcquiaLiftAgentInterface:: |
|
AcquiaLiftAgent:: |
public | function | Implements PersonalizeAgentGoalInterface::useClientSideGoalDelivery(). | |
AcquiaLiftAgent:: |
public | function | Constructs an Acquia Lift agent. |