View source
<?php
class AcquiaLiftAPI implements PersonalizeLoggerAwareInterface, AcquiaLiftReportDataSourceInterface {
const API_URL = 'api.lift.acquia.com';
const EXPLORATION_RATE_RANDOM = 1;
const FEATURE_STRING_REPLACE_PATTERN = '[^A-Za-z0-9_-]';
const FEATURE_STRING_MAX_LENGTH = 50;
const FEATURE_STRING_SEPARATOR_MUTEX = ':';
const FEATURE_STRING_SEPARATOR_NONMUTEX = '::';
const VALID_NAME_MATCH_PATTERN = '[A-Za-z0-9_-]';
const ENABLED_STATUS = 'enabled';
const PAUSED_STATUS = 'paused';
const STOPPED_STATUS = 'stopped';
const PROVISIONAL_STATUS = 'provisional';
protected $api_key;
protected $admin_key;
protected $owner_code;
protected $api_url;
protected $httpClient;
private static $instance;
protected $logger = NULL;
public static function getInstance($account_info) {
if (empty(self::$instance)) {
if (drupal_valid_test_ua()) {
self::setTestInstance();
return self::$instance;
}
foreach (array(
'api_key',
'admin_key',
'owner_code',
) as $key) {
if (!isset($account_info[$key])) {
throw new AcquiaLiftCredsException('Acquia Lift account info is not complete.');
}
}
if (!self::codeIsValid($account_info['owner_code'])) {
throw new AcquiaLiftCredsException('Acquia Lift owner code is invalid.');
}
$api_url = self::API_URL;
$needs_scheme = TRUE;
if (!empty($account_info['api_url'])) {
if (!valid_url($account_info['api_url'])) {
throw new AcquiaLiftCredsException('Acquia Lift API URL is not a valid URL.');
}
$api_url = $account_info['api_url'];
$needs_scheme = strpos($api_url, '://') === FALSE;
}
if ($needs_scheme) {
global $is_https;
$url_scheme = $is_https ? 'https://' : 'http://';
$api_url = $url_scheme . $api_url;
}
if (substr($api_url, -1) === '/') {
$api_url = substr($api_url, 0, -1);
}
self::$instance = new self($account_info['api_key'], $account_info['admin_key'], $account_info['owner_code'], $api_url);
}
return self::$instance;
}
public static function reset() {
self::$instance = NULL;
}
public static function setTestInstance($broken_http_client = FALSE, $simulate_client_side_breakage = FALSE) {
module_load_include('inc', 'acquia_lift', 'tests/acquia_lift.test_classes');
self::$instance = new self('test-api-key', 'test-admin-key', 'test-owner-code', 'http://api.example.com');
$test_data = variable_get('acquia_lift_web_test_data', array());
self::$instance
->setHttpClient(new DummyAcquiaLiftHttpClient($broken_http_client, $test_data, $simulate_client_side_breakage));
self::$instance
->setLogger(new AcquiaLiftTestLogger(FALSE));
}
public static function mapStatus($status) {
$status_map = array(
PERSONALIZE_STATUS_NOT_STARTED => self::PAUSED_STATUS,
PERSONALIZE_STATUS_PAUSED => self::PAUSED_STATUS,
PERSONALIZE_STATUS_RUNNING => self::ENABLED_STATUS,
PERSONALIZE_STATUS_COMPLETED => self::STOPPED_STATUS,
);
if (in_array($status, $status_map)) {
return $status;
}
if (!isset($status_map[$status])) {
throw new AcquiaLiftException('Unknown agent status: ' . $status);
}
return $status_map[$status];
}
public static function isSuccessful($code) {
return $code >= 200 && $code < 300;
}
public static function isClientError($code) {
return $code >= 400 && $code < 500;
}
public static function isServerError($code) {
return $code >= 500 && $code < 600;
}
protected static function mapBadResponseToExceptionClass($code) {
if (self::isSuccessful($code)) {
return NULL;
}
if (self::isClientError($code)) {
switch ($code) {
case 404:
return 'AcquiaLiftNotFoundException';
case 403:
return 'AcquiaLiftForbiddenException';
default:
return 'AcquiaLiftClientErrorException';
}
}
elseif (self::isServerError($code)) {
return 'AcquiaLiftServerErrorException';
}
return NULL;
}
public static function codeIsValid($str) {
return (bool) preg_match('/^' . self::VALID_NAME_MATCH_PATTERN . '+$/', $str);
}
public static function cleanFeatureString($str) {
$string = preg_replace('/' . self::FEATURE_STRING_REPLACE_PATTERN . '/', '-', $str);
$string = preg_replace('/\\-{2,}/', '-', $string);
return $string;
}
private function __construct($api_key, $admin_key, $owner_code, $api_url) {
$this->api_key = $api_key;
$this->admin_key = $admin_key;
$this->owner_code = $owner_code;
$this->api_url = $api_url;
}
public function getApiKey() {
return $this->api_key;
}
public function getAdminKey() {
return $this->admin_key;
}
public function getOwnerCode() {
return $this->owner_code;
}
public function getApiUrl() {
return $this->api_url;
}
protected function httpClient() {
if (!isset($this->httpClient)) {
$this->httpClient = new AcquiaLiftDrupalHttpClient();
}
return $this->httpClient;
}
public function setHttpClient(AcquiaLiftDrupalHttpClientInterface $client) {
$this->httpClient = $client;
}
protected function generateEndpoint($path, $admin = TRUE) {
$endpoint = $this->api_url . '/';
$endpoint .= $this->owner_code;
if (substr($path, 0, 1) !== '/') {
$endpoint .= '/';
}
$endpoint .= $path;
if (strpos($endpoint, '?')) {
$endpoint .= '&';
}
else {
$endpoint .= '?';
}
$key = $admin ? $this
->getAdminKey() : $this
->getApiKey();
$endpoint .= "apikey={$key}";
return $endpoint;
}
protected function logger() {
if ($this->logger !== NULL) {
return $this->logger;
}
return new PersonalizeLogger();
}
public function setLogger(PersonalizeLoggerInterface $logger) {
$this->logger = $logger;
}
public function pingTest() {
$url = $this
->generateEndpoint("/list-agents");
$admin_response = $this
->httpClient()
->get($url, array(
'Accept' => 'application/json',
));
$url = $this
->generateEndpoint('/ping-test-agent/expire', FALSE);
$runtime_response = $this
->httpClient()
->post($url, array(
'Accept' => 'application/json',
));
return $admin_response->code == 200 && $runtime_response->code != 403;
}
public function saveAgent($machine_name, $label, $decision_style, $status = 'enabled', $control_rate = 0.1, $explore_rate = 0.2, $sticky = TRUE) {
if ($decision_style !== "random") {
$decision_style = "adaptive";
}
$status = self::mapStatus($status);
$url = $this
->generateEndpoint("/agent-api/{$machine_name}");
$agent = array(
'name' => $label,
'selection-mode' => $decision_style,
'status' => $status,
'control-rate' => $control_rate,
'explore-rate' => $explore_rate,
'decision-stickiness' => $sticky ? 'session' : 'none',
);
$response = $this
->httpClient()
->put($url, array(
'Content-Type' => 'application/json; charset=utf-8',
'Accept' => 'application/json',
), $agent);
$data = json_decode($response->data, TRUE);
$vars = array(
'agent' => $machine_name,
);
$success_msg = 'The campaign {agent} was pushed to Acquia Lift';
$fail_msg = 'The campaign {agent} could not be pushed to Acquia Lift';
if ($response->code == 200 && $data['status'] == 'ok') {
$this
->logger()
->log(PersonalizeLogLevel::INFO, $success_msg, $vars);
}
else {
$this
->handleBadResponse($response->code, $fail_msg, $vars);
}
}
public function updateAgentStatus($machine_name, $status) {
$lift_status_code = self::mapStatus($status);
$url = $this
->generateEndpoint("/agent-api/{$machine_name}");
$agent = array(
'status' => $lift_status_code,
);
$response = $this
->httpClient()
->put($url, array(
'Content-Type' => 'application/json; charset=utf-8',
'Accept' => 'application/json',
), $agent);
$data = json_decode($response->data, TRUE);
$vars = array(
'agent' => $machine_name,
);
$success_msg = 'The new status of campaign {agent} was pushed to Acquia Lift';
$fail_msg = 'The new status of campaign {agent} could not be pushed to Acquia Lift';
if ($response->code == 200 && $data['status'] == 'ok') {
$this
->logger()
->log(PersonalizeLogLevel::INFO, $success_msg, $vars);
}
else {
$this
->handleBadResponse($response->code, $fail_msg, $vars);
}
}
public function handleBadResponse($response_code, $fail_msg, $vars = array(), $log_failure = TRUE) {
if ($exception_class = self::mapBadResponseToExceptionClass($response_code)) {
if ($log_failure) {
$this
->logger()
->log(PersonalizeLogLevel::ERROR, $fail_msg, $vars);
}
throw new $exception_class(t($fail_msg, $vars));
}
if (self::isSuccessful($response_code)) {
if ($log_failure) {
$this
->logger()
->log(PersonalizeLogLevel::WARNING, $fail_msg, $vars);
}
return;
}
throw new AcquiaLiftException($fail_msg);
}
public function savePoint($agent_name, $point_name) {
$url = $this
->generateEndpoint("/agent-api/{$agent_name}/points/{$point_name}");
$response = $this
->httpClient()
->put($url, array(
'Content-Type' => 'application/json; charset=utf-8',
'Accept' => 'application/json',
));
$data = json_decode($response->data, TRUE);
$vars = array(
'agent' => $agent_name,
'decpoint' => $point_name,
);
$success_msg = 'The point {decpoint} was pushed to the Acquia Lift campaign {agent}';
$fail_msg = 'Could not save the point {decpoint} to the Acquia Lift campaign {agent}';
if ($response->code == 200 && $data['status'] == 'ok') {
$this
->logger()
->log(PersonalizeLogLevel::INFO, $success_msg, $vars);
}
else {
$this
->handleBadResponse($response->code, $fail_msg, $vars);
}
}
public function saveDecision($agent_name, $point_name, $decision_name, $data = array()) {
$url = $this
->generateEndpoint("/agent-api/{$agent_name}/points/{$point_name}/decisions/{$decision_name}");
$response = $this
->httpClient()
->put($url, array(
'Content-Type' => 'application/json; charset=utf-8',
'Accept' => 'application/json',
), $data);
$data = json_decode($response->data, TRUE);
$vars = array(
'agent' => $agent_name,
'decpoint' => $point_name,
'decname' => $decision_name,
);
$success_msg = 'The decision {decname} for point {decpoint} was pushed to the Acquia Lift campaign {agent}';
$fail_msg = 'Could not save decision {decname} for point {decpoint} to the Acquia Lift campaign {agent}';
if ($response->code == 200 && $data['status'] == 'ok') {
$this
->logger()
->log(PersonalizeLogLevel::INFO, $success_msg, $vars);
}
else {
$this
->handleBadResponse($response->code, $fail_msg, $vars);
}
}
public function saveChoice($agent_name, $point_name, $decision_name, $choice, $data = array()) {
$url = $this
->generateEndpoint("/agent-api/{$agent_name}/points/{$point_name}/decisions/{$decision_name}/choices/{$choice}");
$response = $this
->httpClient()
->put($url, array(
'Content-Type' => 'application/json; charset=utf-8',
'Accept' => 'application/json',
), $data);
$data = json_decode($response->data, TRUE);
$vars = array(
'agent' => $agent_name,
'decpoint' => $point_name,
'choicename' => $decision_name . ': ' . $choice,
);
$success_msg = 'The decision choice {choicename} for point {decpoint} was pushed to the Acquia Lift campaign {agent}';
$fail_msg = 'Could not save decision choice {choicename} for point {decpoint} to the Acquia Lift campaign {agent}';
if ($response->code == 200 && $data['status'] == 'ok') {
$this
->logger()
->log(PersonalizeLogLevel::INFO, $success_msg, $vars);
}
else {
$this
->handleBadResponse($response->code, $fail_msg, $vars);
}
}
public function resetAgentData($agent_name) {
$url = $this
->generateEndpoint("/{$agent_name}/data");
$response = $this
->httpClient()
->delete($url);
$vars = array(
'agent' => $agent_name,
);
$success_msg = 'The data for Acquia Lift campaign {agent} was reset';
$fail_msg = 'Could not reset data for Acquia Lift campaign {agent}';
if ($response->code == 200) {
$this
->logger()
->log(PersonalizeLogLevel::INFO, $success_msg, $vars);
}
else {
$this
->handleBadResponse($response->code, $fail_msg, $vars);
}
}
public function deleteAgent($agent_name) {
$url = $this
->generateEndpoint("/agent-api/{$agent_name}");
$response = $this
->httpClient()
->delete($url);
$vars = array(
'agent' => $agent_name,
);
$success_msg = 'The Acquia Lift campaign {agent} was deleted';
$fail_msg = 'Could not delete Acquia Lift campaign {agent}';
if ($response->code == 200) {
$this
->logger()
->log(PersonalizeLogLevel::INFO, $success_msg, $vars);
}
else {
$this
->handleBadResponse($response->code, $fail_msg, $vars);
}
}
public function deletePoint($agent_name, $point_name) {
$url = $this
->generateEndpoint("/agent-api/{$agent_name}/points/{$point_name}");
$response = $this
->httpClient()
->delete($url);
$data = json_decode($response->data, TRUE);
$vars = array(
'agent' => $agent_name,
'decpoint' => $point_name,
);
$success_msg = 'The decision point {decpoint} was deleted from the Acquia Lift campaign {agent}';
$fail_msg = 'Could not delete decision point {decpoint} from the Acquia Lift campaign {agent}';
if ($response->code == 200 && $data['status'] == 'ok') {
$this
->logger()
->log(PersonalizeLogLevel::INFO, $success_msg, $vars);
}
else {
$this
->handleBadResponse($response->code, $fail_msg, $vars);
}
}
public function deleteDecision($agent_name, $point_name, $decision_name) {
$url = $this
->generateEndpoint("/agent-api/{$agent_name}/points/{$point_name}/decisions/{$decision_name}");
$response = $this
->httpClient()
->delete($url);
$data = json_decode($response->data, TRUE);
$vars = array(
'agent' => $agent_name,
'decpoint' => $point_name,
'decname' => $decision_name,
);
$success_msg = 'The decision {decname} for point {decpoint} was deleted from the Acquia Lift campaign {agent}';
$fail_msg = 'Could not delete decision {decname} for point {decpoint} from the Acquia Lift campaign {agent}';
if ($response->code == 200 && $data['status'] == 'ok') {
$this
->logger()
->log(PersonalizeLogLevel::INFO, $success_msg, $vars);
}
else {
$this
->handleBadResponse($response->code, $fail_msg, $vars);
}
}
public function deleteChoice($agent_name, $point_name, $decision_name, $choice) {
$url = $this
->generateEndpoint("/agent-api/{$agent_name}/points/{$point_name}/decisions/{$decision_name}/choices/{$choice}");
$response = $this
->httpClient()
->delete($url);
$data = json_decode($response->data, TRUE);
$vars = array(
'agent' => $agent_name,
'decpoint' => $point_name,
'choicename' => $decision_name . ': ' . $choice,
);
$success_msg = 'The decision choice {choicename} for point {decpoint} was deleted from the Acquia Lift campaign {agent}';
$fail_msg = 'Could not delete decision choice {choicename} for point {decpoint} from the Acquia Lift campaign {agent}';
if ($response->code == 200 && $data['status'] == 'ok') {
$this
->logger()
->log(PersonalizeLogLevel::INFO, $success_msg, $vars);
}
else {
$this
->logger()
->log(PersonalizeLogLevel::ERROR, $fail_msg, $vars);
throw new AcquiaLiftException($fail_msg);
}
}
public function saveGoal($agent_name, $goal_name, $data = array()) {
$url = $this
->generateEndpoint("/agent-api/{$agent_name}/goals/{$goal_name}");
$response = $this
->httpClient()
->put($url, array(
'Content-Type' => 'application/json; charset=utf-8',
'Accept' => 'application/json',
), $data);
$data = json_decode($response->data, TRUE);
$vars = array(
'agent' => $agent_name,
'goalname' => $goal_name,
);
$success_msg = 'The goal {goalname} was pushed to the Acquia Lift campaign {agent}';
$fail_msg = 'Could not save the goal {goalname} to the Acquia Lift campaign {agent}';
if ($response->code == 200 && $data['status'] == 'ok') {
$this
->logger()
->log(PersonalizeLogLevel::INFO, $success_msg, $vars);
}
else {
$this
->handleBadResponse($response->code, $fail_msg, $vars);
}
}
public function deleteGoal($agent_name, $goal_name) {
$url = $this
->generateEndpoint("/agent-api/{$agent_name}/goals/{$goal_name}");
$response = $this
->httpClient()
->delete($url);
$vars = array(
'agent' => $agent_name,
'goalname' => $goal_name,
);
$success_msg = 'The goal {goalname} was deleted from the Acquia Lift campaign {agent}';
$fail_msg = 'Could not delete the goal {goalname} from the Acquia Lift campaign {agent}';
if ($response->code == 200) {
$this
->logger()
->log(PersonalizeLogLevel::INFO, $success_msg, $vars);
}
else {
$this
->handleBadResponse($response->code, $fail_msg, $vars);
}
}
public function getAgent($machine_name) {
$url = $this
->generateEndpoint("/agent-api/{$machine_name}");
$response = $this
->httpClient()
->get($url, array(
'Accept' => 'application/json',
));
if ($response->code == 200) {
return json_decode($response->data, TRUE);
}
else {
$this
->handleBadResponse($response->code, 'Could not retrieve agent from Acquia Lift', array(), FALSE);
}
return FALSE;
}
public function getGoalsForAgent($agent_name) {
$url = $this
->generateEndpoint("/agent-api/{$agent_name}/goals");
$response = $this
->httpClient()
->get($url, array(
'Accept' => 'application/json',
));
if ($response->code == 200) {
return json_decode($response->data, TRUE);
}
else {
$this
->handleBadResponse($response->code, 'Could not retrieve goals from Acquia Lift', array(), FALSE);
}
return FALSE;
}
public function getPointsForAgent($agent_name) {
$url = $this
->generateEndpoint("/agent-api/{$agent_name}/points");
$response = $this
->httpClient()
->get($url, array(
'Accept' => 'application/json',
));
if ($response->code == 200) {
return json_decode($response->data, TRUE);
}
else {
$this
->handleBadResponse($response->code, 'Could not retrieve decision points from Acquia Lift', array(), FALSE);
}
return FALSE;
}
public function getDecisionsForPoint($agent_name, $point_name) {
$url = $this
->generateEndpoint("/agent-api/{$agent_name}/points/{$point_name}/decisions");
$response = $this
->httpClient()
->get($url, array(
'Accept' => 'application/json',
));
if ($response->code == 200) {
return json_decode($response->data, TRUE);
}
else {
$this
->handleBadResponse($response->code, 'Could not retrieve decisions from Acquia Lift', array(), FALSE);
}
return FALSE;
}
public function getChoicesForDecision($agent_name, $point_name, $decision_name) {
$url = $this
->generateEndpoint("/agent-api/{$agent_name}/points/{$point_name}/decisions/{$decision_name}/choices");
$response = $this
->httpClient()
->get($url, array(
'Accept' => 'application/json',
));
if ($response->code == 200) {
return json_decode($response->data, TRUE);
}
else {
$this
->handleBadResponse($response->code, 'Could not retrieve choices from Acquia Lift', array(), FALSE);
}
return FALSE;
}
public function getExistingAgents() {
$url = $this
->generateEndpoint("/list-agents");
$response = $this
->httpClient()
->get($url, array(
'Accept' => 'application/json',
));
if ($response->code == 200) {
$response = json_decode($response->data, TRUE);
if (!isset($response['data']['agents'])) {
return array();
}
$existing_agents = array();
foreach ($response['data']['agents'] as $agent) {
$existing_agents[$agent['code']] = $agent;
}
return $existing_agents;
}
else {
$this
->handleBadResponse($response->code, 'Error retrieving agent list from Acquia Lift');
}
return array();
}
public function getTransformOptions() {
$url = $this
->generateEndpoint("/transforms-options");
$response = $this
->httpClient()
->get($url, array(
'Accept' => 'application/json',
));
if ($response->code == 200) {
$response = json_decode($response->data, TRUE);
return $response['data']['options'];
}
else {
$this
->handleBadResponse($response->code, 'Error retrieving list of transforms options');
}
return FALSE;
}
public function saveAutoTargetingRule($agent_name, $auto_features) {
$rule_name = $agent_name . '-auto-targeting';
$url = $this
->generateEndpoint("/transform-rule");
foreach ($auto_features as &$feature) {
$feature = '#' . $feature;
}
$body = array(
'code' => $rule_name,
'status' => 1,
'agents' => array(
$agent_name,
),
'when' => array(),
'apply' => array(
'feature' => implode(',', $auto_features),
),
);
$response = $this
->httpClient()
->post($url, array(
'Content-Type' => 'application/json; charset=utf-8',
'Accept' => 'application/json',
), $body);
$vars = array(
'agent' => $agent_name,
);
$success_msg = 'The targeting rule for campaign {agent} was saved successfully';
$fail_msg = 'The targeting rule could not be saved for campaign {agent}';
if ($response->code == 200) {
$this
->logger()
->log(PersonalizeLogLevel::INFO, $success_msg, $vars);
}
else {
$this
->handleBadResponse($response->code, $fail_msg, $vars);
}
}
public function deleteAutoTargetingRule($agent_name) {
$rule_name = $agent_name . '-auto-targeting';
$url = $this
->generateEndpoint("/transform-rule/{$rule_name}");
$response = $this
->httpClient()
->delete($url, array(
'Content-Type' => 'application/json; charset=utf-8',
'Accept' => 'application/json',
));
$vars = array(
'agent' => $agent_name,
);
$success_msg = 'The targeting rule for campaign {agent} was deleted successfully';
$fail_msg = 'The targeting rule could not be deleted for campaign {agent}';
if ($response->code == 200) {
$this
->logger()
->log(PersonalizeLogLevel::INFO, $success_msg, $vars);
}
else {
$this
->handleBadResponse($response->code, $fail_msg, $vars);
}
}
public function hasAutoTargetingRule($agent_name) {
$rule_name = $agent_name . '-auto-targeting';
$url = $this
->generateEndpoint("/transforms-list");
$response = $this
->httpClient()
->get($url, array(
'Accept' => 'application/json',
));
if ($response->code != 200) {
$this
->handleBadResponse($response->code, 'Problem retrieving auto-targeting rules');
return FALSE;
}
$response = json_decode($response->data, TRUE);
if (!isset($response['data']) || !isset($response['data']['transforms'])) {
return FALSE;
}
foreach ($response['data']['transforms'] as $rule) {
if ($rule['code'] == $rule_name) {
return TRUE;
}
}
return FALSE;
}
public function getPotentialTargetingValues($agent_name) {
$url = $this
->generateEndpoint("/-/potential-targeting?agent={$agent_name}&include-current=true");
$headers = array(
'Accept' => 'application/json',
);
$response = $this
->httpClient()
->get($url, $headers);
if ($response->code != 200) {
$this
->handleBadResponse($response->code, 'Problem retrieving potential targeting values');
return array();
}
return json_decode($response->data, TRUE);
}
public function getPossibleValues($agent_name, $mutex_separator = NULL, $non_mutex_separator = NULL) {
$possible_values = array();
if (empty($mutex_separator)) {
$mutex_separator = self::FEATURE_STRING_SEPARATOR_MUTEX;
}
if (empty($non_mutex_separator)) {
$non_mutex_separator = self::FEATURE_STRING_SEPARATOR_NONMUTEX;
}
$result = $this
->getPotentialTargetingValues($agent_name);
if (isset($result['data']['potential']['features']) && !empty($result['data']['potential']['features'])) {
$check = $non_mutex_separator;
if (strpos($mutex_separator, $non_mutex_separator) !== FALSE) {
$check = $mutex_separator;
}
foreach ($result['data']['potential']['features'] as $feature) {
$code = $feature['code'];
$mutex = strpos($code, $check) === FALSE ? $check === $non_mutex_separator : $check === $mutex_separator;
$separator = $mutex ? $mutex_separator : $non_mutex_separator;
$separated = explode($separator, $code);
if (count($separated) !== 2) {
continue;
}
list($name, $value) = $separated;
$name = trim($name);
$value = trim($value);
$friendly_name = isset($feature['typeName']) ? $feature['typeName'] : $name;
if (!isset($possible_values[$name])) {
$possible_values[$name] = array(
'value type' => 'predefined',
'mutex' => $mutex,
'friendly name' => $friendly_name,
'values' => array(),
);
}
$possible_values[$name]['values'][$value] = $feature['name'];
}
}
return $possible_values;
}
public function saveFixedTargetingMapping($agent_name, $point_name, $map) {
$url = $this
->generateEndpoint("/agent-api/{$agent_name}/points/{$point_name}/fixed-targeting");
$response = $this
->httpClient()
->put($url, array(
'Content-Type' => 'application/json; charset=utf-8',
'Accept' => 'application/json',
), $map);
$data = json_decode($response->data, TRUE);
$vars = array(
'agent' => $agent_name,
'decpoint' => $point_name,
);
$success_msg = 'The fixed targeting mapping for point {decpoint} was successfully saved for campaign {agent}';
$fail_msg = 'The fixed targeting mapping for point {decpoint} could not be saved for campaign {agent}';
if ($response->code == 200 && $data['status'] == 'ok') {
$this
->logger()
->log(PersonalizeLogLevel::INFO, $success_msg, $vars);
}
else {
$this
->handleBadResponse($response->code, $fail_msg, $vars);
}
}
public function getTargetingImpactReport($agent_name, $date_start = NULL, $date_end = NULL, $point = NULL) {
$date_str = $this
->getDateString($date_start, $date_end);
$url = $this
->generateEndpoint("/{$agent_name}/report/targeting-features{$date_str}");
$headers = array(
'Accept' => 'application/json',
);
if ($point !== NULL) {
$headers['x-mpath-point'] = $point;
}
$response = $this
->httpClient()
->get($url, $headers);
if ($response->code != 200) {
$this
->handleBadResponse($response->code, 'Problem retrieving targeting impact report.');
return array();
}
return json_decode($response->data, TRUE);
}
public function getAgentStatusReport($agent_names, $num_days = NULL) {
$codes = implode(',', $agent_names);
$days = is_null($num_days) || !is_numeric($num_days) ? '' : '&days=' . $num_days;
$url = $this
->generateEndpoint("/report/status?codes={$codes}{$days}");
$response = $this
->httpClient()
->get($url, array(
'Accept' => 'application/json',
));
if ($response->code != 200) {
$this
->handleBadResponse($response->code, 'Problem retrieving status report.');
return array();
}
return json_decode($response->data, TRUE);
}
public function getConfidenceReport($agent_name, $date_start = NULL, $date_end = NULL, $point = NULL, $options = array()) {
$default_options = array(
'confidence-measure' => 0.95,
'aggregated-over-dates' => true,
'features' => NULL,
);
$options = array_merge($default_options, $options);
$date_str = $this
->getDateString($date_start, $date_end);
if ($options['features'] === 'all') {
$features = '';
}
else {
$features = is_array($options['features']) ? implode(',', $options['features']) : "(none)";
}
unset($options['features']);
$url = $this
->generateEndpoint("/{$agent_name}/report/confidence{$date_str}?features={$features}");
foreach ($options as $param => $value) {
$param_value = is_bool($value) ? var_export($value, TRUE) : (string) $value;
$url .= "&{$param}=" . $param_value;
}
$headers = array(
'Accept' => 'application/json',
);
if ($point !== NULL) {
$headers['x-mpath-point'] = $point;
}
$response = $this
->httpClient()
->get($url, $headers);
if ($response->code != 200) {
$this
->handleBadResponse($response->code, 'Problem retrieving confidence report.');
return array();
}
return json_decode($response->data, TRUE);
}
public function getContextFilters($agent_name) {
return $this
->getPotentialTargetingValues($agent_name);
}
public function getRawLearningReport($agent_name, $date_start = NULL, $date_end = NULL, $point = NULL) {
$date_str = $this
->getDateString($date_start, $date_end);
$url = $this
->generateEndpoint("/{$agent_name}/report/learning{$date_str}");
$headers = array(
'Accept' => 'application/json',
);
if ($point !== NULL) {
$headers['x-mpath-point'] = $point;
}
$response = $this
->httpClient()
->get($url, $headers);
if ($response->code != 200) {
$this
->handleBadResponse($response->code, 'Problem retrieving learning report.');
return array();
}
return json_decode($response->data, TRUE);
}
public function getAPICallsForPeriod($date_start, $date_end) {
$date_str = $this
->getDateString($date_start, $date_end);
$url = $this
->generateEndpoint("/-/report/system-usage{$date_str}");
$headers = array(
'Accept' => 'application/json',
);
$response = $this
->httpClient()
->get($url, $headers);
if ($response->code != 200) {
$this
->handleBadResponse($response->code, 'Problem retrieving API call counts.');
return array();
}
$result = json_decode($response->data, TRUE);
if (!isset($result['data']) || !isset($result['data'][0]) || !isset($result['data'][0]['calls'])) {
return array();
}
return $result['data'][0]['calls'];
}
protected function convertCallCountsToTotalCount($counts, $exclude = array(
'other',
)) {
$total_count = 0;
foreach ($counts as $type => $count) {
if (in_array($type, $exclude)) {
continue;
}
$total_count += $count;
}
return $total_count;
}
public function getCallsForPreviousMonth($timestamp) {
$date = getdate($timestamp);
$current_month = $date['mon'];
$current_month_year = $last_month_year = $date['year'];
if ($current_month == 1) {
$last_month = 12;
$last_month_year = $current_month_year - 1;
}
else {
$last_month = $current_month - 1;
if ($last_month < 10) {
$last_month = '0' . $last_month;
}
}
$ts_last_month = strtotime("{$last_month}/01/{$last_month_year}");
$num_days_last_month = date('t', $ts_last_month);
$date_start = $last_month_year . '-' . $last_month . '-01';
$date_end = $last_month_year . '-' . $last_month . '-' . $num_days_last_month;
$calls_last_month = $this
->getAPICallsForPeriod($date_start, $date_end);
return $calls_last_month;
}
public function getTotalRuntimeCallsForPreviousMonth($timestamp) {
$calls_last_month = $this
->getCallsForPreviousMonth($timestamp);
return $this
->convertCallCountsToTotalCount($calls_last_month);
}
public function getCallsForMonthToDate($timestamp) {
$date_start = date('Y', $timestamp) . '-' . date('m', $timestamp) . '-01';
$date_end = date('Y-m-d', $timestamp);
$calls_this_month = $this
->getAPICallsForPeriod($date_start, $date_end);
return $calls_this_month;
}
public function getTotalRuntimeCallsForMonthToDate($timestamp) {
$calls_last_month = $this
->getCallsForMonthToDate($timestamp);
return $this
->convertCallCountsToTotalCount($calls_last_month);
}
public function ensureUniqueAgentName($agent_name, $max_length) {
if ($max_length > self::FEATURE_STRING_MAX_LENGTH) {
$max_length = self::FEATURE_STRING_MAX_LENGTH;
}
$agent_name = substr($agent_name, 0, $max_length);
$existing = $this
->getExistingAgents();
$index = 0;
$suffix = '';
while (in_array($agent_name . $suffix, array_keys($existing))) {
$suffix = '-' . $index;
while (strlen($agent_name . $suffix) > $max_length) {
$agent_name = substr($agent_name, 0, -1);
}
$index++;
}
return $agent_name . $suffix;
}
protected function getDateString($date_start, $date_end) {
if ($date_start === NULL || !preg_match('/\\d{4}\\-\\d{2}\\-\\d{2}/', $date_start)) {
$date_start = date('Y-m-d');
}
$date_str = '/' . $date_start;
if ($date_end !== NULL && preg_match('/\\d{4}\\-\\d{2}\\-\\d{2}/', $date_end)) {
$date_str .= '/' . $date_end;
}
return $date_str;
}
}
interface AcquiaLiftReportDataSourceInterface {
public function getConfidenceReport($agent_name, $date_start = NULL, $date_end = NULL, $point = NULL, $options = array());
public function getTargetingImpactReport($agent_name, $date_start = NULL, $date_end = NULL, $point = NULL);
public function getAgentStatusReport($agent_names, $num_days = NULL);
public function getRawLearningReport($agent_name, $date_start = NULL, $date_end = NULL, $point = NULL);
public function getContextFilters($agent_name);
}
class AcquiaLiftReportDataFromFile implements AcquiaLiftReportDataSourceInterface {
protected $fileLocation;
protected $reports;
public function __construct($file_location, AcquiaLiftReportCacheInterface $cache = NULL) {
$this->fileLocation = $file_location;
$this->cache = $cache;
}
protected function getReport($agent_name, $report_name) {
if ($this->reports) {
return isset($this->reports[$report_name]) ? $this->reports[$report_name] : array();
}
if ($this->cache && ($reports = $this->cache
->getCachedReports($agent_name))) {
$this->reports = $reports;
return isset($reports[$report_name]) ? $reports[$report_name] : array();
}
else {
$file_name = $this->fileLocation;
if (strpos($file_name, '://') === FALSE) {
global $base_url;
$file_name = $base_url . $file_name;
}
if ($str = file_get_contents($file_name)) {
$parsed = json_decode($str, TRUE);
$this->reports = $parsed['reports'];
if ($this->cache) {
$this->cache
->cacheReports($agent_name, $this->reports);
}
return isset($this->reports[$report_name]) ? $this->reports[$report_name] : array();
}
}
return array();
}
public function getTargetingImpactReport($agent_name, $date_start = NULL, $date_end = NULL, $point = NULL) {
return $this
->getReport($agent_name, 'targeting-impact');
}
public function getAgentStatusReport($agent_names, $num_days = NULL) {
$agent_name = reset($agent_names);
return $this
->getReport($agent_name, 'agent-status');
}
public function getConfidenceReport($agent_name, $date_start = NULL, $date_end = NULL, $point = NULL, $options = array()) {
$report_name = 'confidence';
if (!empty($options['goal'])) {
$report_name .= '_goal' . rand(1, 2);
}
if (isset($options['aggregated-over-dates']) && $options['aggregated-over-dates'] === FALSE) {
$report_name .= '_detail';
}
return $this
->getReport($agent_name, $report_name);
}
public function getRawLearningReport($agent_name, $date_start = NULL, $date_end = NULL, $point = NULL) {
return $this
->getReport($agent_name, 'raw-learning');
}
public function getContextFilters($agent_name) {
$context_filters = $this
->getReport($agent_name, 'context-filters');
return !empty($context_filters) ? $context_filters : array(
'data' => array(
'potential' => array(
'features' => array(),
),
),
);
}
}
interface AcquiaLiftReportCacheInterface {
public function getCachedReports($agent_name);
public function cacheReports($agent_name, $data);
}
class AcquiaLiftReportCache implements AcquiaLiftReportCacheInterface {
protected $cache = array();
protected $bin = 'cache_acquia_lift_reports';
public function getCachedReports($agent_name) {
if (!isset($this->cache[$agent_name])) {
if ($get = cache_get($agent_name, $this->bin)) {
$this->cache[$agent_name] = $get->data;
}
else {
$this->cache[$agent_name] = FALSE;
}
}
return $this->cache[$agent_name];
}
public function cacheReports($agent_name, $data) {
cache_set($agent_name, $data, $this->bin);
}
}
interface AcquiaLiftDrupalHttpClientInterface {
public function get($uri = null, $headers = null, array $options = array());
public function put($uri = null, $headers = null, $body = null, array $options = array());
public function post($uri = null, $headers = null, $body = null, array $options = array());
public function delete($uri = null, $headers = null, $body = null, array $options = array());
}
class AcquiaLiftDrupalHttpClient implements AcquiaLiftDrupalHttpClientInterface {
const REQUEST_TIMEOUT_VALUE_GET = 8.0;
const REQUEST_TIMEOUT_VALUE_DEFAULT = 15.0;
protected function encodeBody($body) {
if (is_string($body)) {
$data = $body;
}
else {
$data = drupal_json_encode($body);
}
return $data;
}
public function get($uri = NULL, $headers = NULL, array $options = array()) {
$headers = $headers ? $headers : array();
$options += array(
'timeout' => self::REQUEST_TIMEOUT_VALUE_GET,
);
$options = array(
'method' => 'GET',
'headers' => $headers,
) + $options;
return drupal_http_request($uri, $options);
}
public function put($uri = NULL, $headers = NULL, $body = NULL, array $options = array()) {
$data = $body === NULL ? NULL : $this
->encodeBody($body);
$headers = $headers ? $headers : array();
$options += array(
'timeout' => self::REQUEST_TIMEOUT_VALUE_DEFAULT,
);
$options = array(
'method' => 'PUT',
'data' => $data,
'headers' => $headers,
) + $options;
return drupal_http_request($uri, $options);
}
public function post($uri = NULL, $headers = NULL, $body = NULL, array $options = array()) {
$data = $body ? $this
->encodeBody($body) : NULL;
$headers = $headers ? $headers : array();
$options += array(
'timeout' => self::REQUEST_TIMEOUT_VALUE_DEFAULT,
);
$options = array(
'method' => 'POST',
'data' => $data,
'headers' => $headers,
) + $options;
return drupal_http_request($uri, $options);
}
public function delete($uri = null, $headers = null, $body = null, array $options = array()) {
$data = $body ? $this
->encodeBody($body) : NULL;
$headers = $headers ? $headers : array();
$options += array(
'timeout' => self::REQUEST_TIMEOUT_VALUE_DEFAULT,
);
$options = array(
'method' => 'DELETE',
'data' => $data,
'headers' => $headers,
) + $options;
return drupal_http_request($uri, $options);
}
}
class AcquiaLiftQueue extends SystemQueue {
public static $items = array();
public static $retries = array();
const MAX_RETRIES = 15;
public static function handleFailedItem($item) {
if (!isset($item->data['agent'])) {
return;
}
personalize_pause_if_running($item->data['agent']);
}
public function createItem($data) {
if (isset($data['hash'])) {
if (in_array($data['hash'], self::$items)) {
return;
}
else {
self::$items[] = $data['hash'];
unset($data['hash']);
}
}
parent::createItem($data);
}
public function releaseItem($item) {
if (!isset(self::$retries[$item->item_id])) {
self::$retries[$item->item_id] = 0;
}
elseif (self::$retries[$item->item_id] >= self::MAX_RETRIES) {
self::handleFailedItem($item);
$this
->deleteItem($item);
return;
}
parent::releaseItem($item);
self::$retries[$item->item_id]++;
}
public function deleteQueue() {
parent::deleteQueue();
self::$items = array();
}
}
class AcquiaLiftException extends Exception {
}
class AcquiaLiftServerErrorException extends AcquiaLiftException {
}
class AcquiaLiftClientErrorException extends AcquiaLiftException {
}
class AcquiaLiftNotFoundException extends AcquiaLiftClientErrorException {
}
class AcquiaLiftForbiddenException extends AcquiaLiftClientErrorException {
}
class AcquiaLiftCredsException extends AcquiaLiftException {
}