622 lines
23 KiB
PHP
622 lines
23 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Common class for Timely API communication.
|
|
*
|
|
* @author Time.ly Network, Inc.
|
|
* @since 2.4
|
|
* @package Ai1EC
|
|
* @subpackage Ai1EC.Model
|
|
*/
|
|
abstract class Ai1ec_Api_Abstract extends Ai1ec_App {
|
|
|
|
const WP_OPTION_KEY = 'ai1ec_api_settings';
|
|
const DEFAULT_TIMEOUT = 30;
|
|
|
|
protected $_settings;
|
|
|
|
/**
|
|
* Post construction routine.
|
|
*
|
|
* Override this method to perform post-construction tasks.
|
|
*
|
|
* @return void Return from this method is ignored.
|
|
*/
|
|
protected function _initialize() {
|
|
$this->_settings = $this->_registry->get( 'model.settings' );
|
|
}
|
|
|
|
protected function get_ticketing_settings( $find_attribute = null, $default_value_attribute = null ) {
|
|
$api_settings = get_option( self::WP_OPTION_KEY, null );
|
|
if ( ! is_array( $api_settings ) ) {
|
|
$api_settings = array(
|
|
'enabled' => $this->_settings->get( 'ticketing_enabled' ),
|
|
'message' => $this->_settings->get( 'ticketing_message' ),
|
|
'token' => $this->_settings->get( 'ticketing_token' ),
|
|
'calendar_id' => $this->_settings->get( 'ticketing_calendar_id' )
|
|
);
|
|
update_option( self::WP_OPTION_KEY, $api_settings );
|
|
$this->_settings->set( 'ticketing_message' , '' );
|
|
$this->_settings->set( 'ticketing_enabled' , false );
|
|
$this->_settings->set( 'ticketing_token' , '' );
|
|
$this->_settings->set( 'ticketing_calendar_id', null );
|
|
}
|
|
if ( is_null( $find_attribute ) ) {
|
|
return $api_settings;
|
|
} else {
|
|
if ( isset( $api_settings[$find_attribute] ) ) {
|
|
return $api_settings[$find_attribute];
|
|
} else {
|
|
return $default_value_attribute;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param String $message last message received from the Sign up or Sign in process
|
|
* @param bool $enabled true or false is ticket is enabled
|
|
* @param string $token autenthication token
|
|
* @param int @calendar_id remote id of the calendar
|
|
* @param string $account Email used to create the account
|
|
*/
|
|
protected function save_ticketing_settings( $message, $enabled, $token, $calendar_id, $account ) {
|
|
$api_settings = $this->get_ticketing_settings();
|
|
$api_settings['message'] = $message;
|
|
$api_settings['enabled'] = $enabled;
|
|
$api_settings['token'] = $token;
|
|
$api_settings['calendar_id'] = $calendar_id;
|
|
$api_settings['account'] = $account;
|
|
return update_option( self::WP_OPTION_KEY, $api_settings );
|
|
}
|
|
|
|
protected function clear_ticketing_settings() {
|
|
delete_option( self::WP_OPTION_KEY );
|
|
|
|
// Clear transient API data
|
|
delete_transient( 'ai1ec_api_feeds_subscriptions' );
|
|
delete_transient( 'ai1ec_api_subscriptions' );
|
|
delete_transient( 'ai1ec_api_features' );
|
|
delete_transient( 'ai1ec_api_checked' );
|
|
|
|
$this->check_settings();
|
|
}
|
|
|
|
/**
|
|
* Save the Payment settings localy (same saved on the API)
|
|
* @param array Preferences to save
|
|
*/
|
|
public function save_payment_settings( array $values ) {
|
|
$api_settings = $this->get_ticketing_settings();
|
|
if ( null !== $values ) {
|
|
$api_settings['payment_settings'] = $values;
|
|
} else {
|
|
unset( $api_settings['payment_settings'] );
|
|
}
|
|
return update_option( self::WP_OPTION_KEY, $api_settings );
|
|
}
|
|
|
|
/**
|
|
* Get the saved payments settings (the same saved on the API)
|
|
*/
|
|
public function get_payment_settings() {
|
|
return $this->get_ticketing_settings( 'payment_settings' );
|
|
}
|
|
|
|
/**
|
|
* Check if the current WP instance has payments settings configured
|
|
*/
|
|
public function has_payment_settings() {
|
|
$payment_settings = $this->get_payment_settings();
|
|
if ( null === $payment_settings ) {
|
|
//code to migrate the settings save on ticketing api and
|
|
//bring them to the core side
|
|
$payment_settings = $this->get_payment_preferences();
|
|
if ( is_object( $payment_settings ) ) {
|
|
$payment_settings = (array) $payment_settings;
|
|
}
|
|
$this->save_payment_settings( (array) $payment_settings );
|
|
}
|
|
return ( null !== $payment_settings &&
|
|
'paypal' === $payment_settings['payment_method'] &&
|
|
false === ai1ec_is_blank( $payment_settings['paypal_email'] ) ) ;
|
|
}
|
|
|
|
|
|
/**
|
|
* @return object Response from API, or empty defaults
|
|
*/
|
|
public function get_payment_preferences() {
|
|
$calendar_id = $this->_get_ticket_calendar();
|
|
$settings = null;
|
|
if ( 0 < $calendar_id ) {
|
|
$response = $this->request_api( 'GET', AI1EC_API_URL . "calendars/$calendar_id/payment",
|
|
null, //no body
|
|
true //decode response body
|
|
);
|
|
if ( $this->is_response_success( $response ) ) {
|
|
$settings = $response->body;
|
|
}
|
|
}
|
|
if ( is_null( $settings ) ) {
|
|
return (object) array( 'payment_method'=>'paypal', 'paypal_email'=> '', 'first_name'=>'', 'last_name'=>'', 'currency'=> 'USD' );
|
|
} else {
|
|
if ( ! isset( $settings->currency ) ) {
|
|
$settings->currency = 'USD';
|
|
}
|
|
return $settings;
|
|
}
|
|
}
|
|
|
|
|
|
public function get_timely_token() {
|
|
return $this->get_ticketing_settings( 'token' );
|
|
}
|
|
|
|
protected function save_calendar_id ( $calendar_id ) {
|
|
$api_settings = $this->get_ticketing_settings();
|
|
$api_settings['calendar_id'] = $calendar_id;
|
|
return update_option( self::WP_OPTION_KEY, $api_settings );
|
|
}
|
|
|
|
/**
|
|
* Get the header array with authorization token
|
|
*/
|
|
protected function _get_headers( $custom_headers = null ) {
|
|
$headers = array(
|
|
'Content-Type' => 'application/x-www-form-urlencoded'
|
|
);
|
|
$headers['Authorization'] = 'Basic ' . $this->get_ticketing_settings( 'token', '' );
|
|
if ( null !== $custom_headers ) {
|
|
foreach ( $custom_headers as $key => $value ) {
|
|
if ( null === $value ) {
|
|
unset( $headers[$key] );
|
|
} else {
|
|
$headers[$key] = $value;
|
|
}
|
|
}
|
|
}
|
|
return $headers;
|
|
}
|
|
|
|
/**
|
|
* Create a standarized message to return
|
|
* 1) If the API respond with http code 400 and with a JSON body, so, we will consider the API message to append in the base message.
|
|
* 2) If the API does not responde with http code 400 or does not have a valid a JSON body, we will show the API URL and the http message error.
|
|
*/
|
|
protected function _transform_error_message( $base_message, $response, $url, $ask_for_reload = false ) {
|
|
$api_error = $this->get_api_error_msg( $response );
|
|
$result = null;
|
|
if ( false === ai1ec_is_blank( $api_error ) ) {
|
|
$result = sprintf(
|
|
__( '%s.<br/>Detail: %s.', AI1EC_PLUGIN_NAME ),
|
|
$base_message, $api_error
|
|
);
|
|
} else {
|
|
if ( is_wp_error( $response ) ) {
|
|
$error_message = sprintf(
|
|
__( 'API URL: %s.<br/>Detail: %s', AI1EC_PLUGIN_NAME ),
|
|
$url,
|
|
$response->get_error_message()
|
|
);
|
|
} else {
|
|
$error_message = sprintf(
|
|
__( 'API URL: %s.<br/>Detail: %s - %s', AI1EC_PLUGIN_NAME ),
|
|
$url,
|
|
wp_remote_retrieve_response_code( $response ),
|
|
wp_remote_retrieve_response_message( $response )
|
|
);
|
|
$mailto = '<a href="mailto:labs@time.ly" target="_top">labs@time.ly</a>';
|
|
if ( true === $ask_for_reload ) {
|
|
$result = sprintf(
|
|
__( '%s. Please reload this page to try again. If this error persists, please contact us at %s. In your report please include the information below.<br/>%s.', AI1EC_PLUGIN_NAME ),
|
|
$base_message,
|
|
$mailto,
|
|
$error_message
|
|
);
|
|
} else {
|
|
$result = sprintf(
|
|
__( '%s. Please try again. If this error persists, please contact us at %s. In your report please include the information below.<br/>%s.', AI1EC_PLUGIN_NAME ),
|
|
$base_message,
|
|
$mailto,
|
|
$error_message
|
|
);
|
|
}
|
|
}
|
|
}
|
|
$result = trim( $result );
|
|
$result = str_replace( '..', '.', $result );
|
|
$result = str_replace( '.,', '.', $result );
|
|
return $result;
|
|
}
|
|
|
|
|
|
/**
|
|
* Search for the API message error
|
|
*/
|
|
public function get_api_error_msg( $response ) {
|
|
if ( isset( $response ) && false === is_wp_error( $response ) ) {
|
|
$response_body = json_decode( $response['body'], true );
|
|
if ( is_array( $response_body ) &&
|
|
isset( $response_body['errors'] ) ) {
|
|
$errors = $response_body['errors'];
|
|
if ( false === is_array( $errors )) {
|
|
$errors = array( $errors );
|
|
}
|
|
$messages = null;
|
|
foreach ($errors as $key => $value) {
|
|
if ( false === ai1ec_is_blank( $value ) ) {
|
|
if ( is_array( $value ) ) {
|
|
$value = implode ( ', ', $value );
|
|
}
|
|
$messages[] = $value;
|
|
}
|
|
}
|
|
if ( null !== $messages && false === empty( $messages ) ) {
|
|
return implode ( ', ', $messages);
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get the ticket calendar from settings, if the calendar does not exists in
|
|
* settings, then we will try to find on the API
|
|
* @return string JSON.
|
|
*/
|
|
protected function _get_ticket_calendar( $createIfNotExists = true ) {
|
|
$ticketing_calendar_id = $this->get_ticketing_settings( 'calendar_id', 0 );
|
|
if ( 0 < $ticketing_calendar_id ) {
|
|
return $ticketing_calendar_id;
|
|
} else {
|
|
if ( ! $createIfNotExists ) {
|
|
return 0;
|
|
}
|
|
// Try to find the calendar in the API
|
|
$ticketing_calendar_id = $this->_find_user_calendar();
|
|
if ( 0 < $ticketing_calendar_id ) {
|
|
$this->save_calendar_id( $ticketing_calendar_id );
|
|
|
|
return $ticketing_calendar_id;
|
|
} else {
|
|
// If the calendar doesn't exist in the API, create a new one
|
|
$ticketing_calendar_id = $this->_create_calendar();
|
|
if ( 0 < $ticketing_calendar_id ) {
|
|
$this->save_calendar_id( $ticketing_calendar_id );
|
|
|
|
return $ticketing_calendar_id;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find the existent calendar when the user is signing in
|
|
*/
|
|
protected function _find_user_calendar() {
|
|
$body = array(
|
|
'title' => get_bloginfo( 'name' ),
|
|
'url' => ai1ec_site_url()
|
|
);
|
|
$response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars', $body );
|
|
if ( $this->is_response_success( $response ) ) {
|
|
if ( is_array( $response->body ) ) {
|
|
return $response->body[0]->id;
|
|
} else {
|
|
return $response->body->id;
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a calendar when the user is signup
|
|
*/
|
|
protected function _create_calendar() {
|
|
$body = array(
|
|
'title' => get_bloginfo( 'name' ),
|
|
'url' => ai1ec_site_url(),
|
|
'timezone' => $this->_settings->get( 'timezone_string' )
|
|
);
|
|
$response = $this->request_api( 'POST', AI1EC_API_URL . 'calendars', $body );
|
|
if ( $this->is_response_success( $response ) ) {
|
|
return $response->body->id;
|
|
} else {
|
|
$this->log_error( $response, 'Created calendar' );
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the current WP instance is signed into the API
|
|
*/
|
|
public function is_signed() {
|
|
return ( true === $this->get_ticketing_settings( 'enabled', false ) );
|
|
}
|
|
|
|
public function check_settings( $force = false ) {
|
|
$checked = get_transient( 'ai1ec_api_checked' );
|
|
|
|
if ( false === $checked || $force ) {
|
|
require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
|
|
|
|
$failList = array();
|
|
foreach ( Ai1ec_Api_Features::$features as $key => $value ) {
|
|
if ( empty( $value ) ) {
|
|
continue;
|
|
}
|
|
if ( ( ! $this->is_signed() || ! $this->has_subscription_active( $key ) ) && call_user_func( 'is'.'_'.'pl'.'ug'.'in'.'_'.'ac'.'ti'.'ve', $value ) ) {
|
|
$failList[] = $value;
|
|
}
|
|
}
|
|
|
|
if ( count( $failList ) > 0 ) {
|
|
call_user_func( 'de'.'act'.'iv'.'ate'.'_'.'pl'.'ug'.'ins', $failList );
|
|
|
|
$message = 'Your ' .
|
|
'All-in-One Event Calendar ' .
|
|
'has the ' .
|
|
'following ' .
|
|
'plugins ' .
|
|
'installed ' .
|
|
'but they are ' .
|
|
'disabled. '.
|
|
'To keep ' .
|
|
'them ' .
|
|
'enabled'.
|
|
', simply '.
|
|
'keep ' .
|
|
'your calendar ' .
|
|
'logged in ' .
|
|
'to your '.
|
|
'Timely account.' .
|
|
'<br /><br />';
|
|
|
|
foreach ( $failList as $failed ) {
|
|
$pieces = explode( '/', $failed );
|
|
$message .= '- ' . $pieces[0] . '<br />';
|
|
}
|
|
|
|
$this->show_error( $message );
|
|
}
|
|
|
|
set_transient( 'ai1ec_api_checked', true, 5 * 60 );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the current email account
|
|
*/
|
|
public function get_current_account() {
|
|
return $this->get_ticketing_settings( 'account', '' );
|
|
}
|
|
|
|
/**
|
|
* Get the current calendar id
|
|
*/
|
|
public function get_current_calendar() {
|
|
return $this->get_ticketing_settings( 'calendar_id', 0 );
|
|
}
|
|
|
|
/**
|
|
* Get the last message return by Signup or Signup process
|
|
*/
|
|
public function get_sign_message() {
|
|
return $this->get_ticketing_settings( 'message', '' );
|
|
}
|
|
|
|
/**
|
|
* Clear the last message return by Signup or Signup process
|
|
*/
|
|
public function clear_sign_message() {
|
|
$api_settings = $this->get_ticketing_settings();
|
|
$api_settings['message'] = '';
|
|
return update_option( self::WP_OPTION_KEY, $api_settings );
|
|
}
|
|
|
|
/**
|
|
* @return array List of subscriptions and limits
|
|
*/
|
|
protected function get_subscriptions( $force_refresh = false ) {
|
|
$subscriptions = get_transient( 'ai1ec_api_subscriptions' );
|
|
|
|
if ( false === $subscriptions || $force_refresh || ( defined( 'AI1EC_DEBUG' ) && AI1EC_DEBUG ) ) {
|
|
$response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $this->_get_ticket_calendar() . '/subscriptions',
|
|
null,
|
|
true
|
|
);
|
|
if ( $this->is_response_success( $response ) ) {
|
|
$subscriptions = (array) $response->body;
|
|
} else {
|
|
$subscriptions = array();
|
|
}
|
|
|
|
// Save for 5 minutes
|
|
$minutes = 5;
|
|
set_transient( 'ai1ec_api_subscriptions', $subscriptions, $minutes * 60 );
|
|
}
|
|
|
|
return $subscriptions;
|
|
}
|
|
|
|
/**
|
|
* Check if calendar should have a specific feature enabled
|
|
*/
|
|
public function has_subscription_active( $feature ) {
|
|
$subscriptions = $this->get_subscriptions();
|
|
|
|
return array_key_exists( $feature, $subscriptions );
|
|
}
|
|
|
|
/**
|
|
* Check if feature has reached its limit
|
|
*/
|
|
public function subscription_has_reached_limit( $feature ) {
|
|
$has_reached_limit = true;
|
|
|
|
$provided = $this->subscription_get_quantity_limit( $feature );
|
|
$used = $this->subscription_get_used_quantity( $feature );
|
|
|
|
if ( $provided - $used > 0 ) {
|
|
$has_reached_limit = false;
|
|
}
|
|
|
|
return $has_reached_limit;
|
|
}
|
|
|
|
/**
|
|
* Get feature quantity limit
|
|
*/
|
|
public function subscription_get_quantity_limit( $feature ) {
|
|
$provided = 0;
|
|
|
|
$subscriptions = $this->get_subscriptions();
|
|
|
|
if ( array_key_exists( $feature, $subscriptions ) ) {
|
|
$quantity = (array) $subscriptions[$feature];
|
|
|
|
$provided = $quantity['provided'];
|
|
}
|
|
|
|
return $provided;
|
|
}
|
|
|
|
/**
|
|
* Get feature used quantity
|
|
*/
|
|
public function subscription_get_used_quantity( $feature ) {
|
|
$used = 0;
|
|
|
|
$subscriptions = $this->get_subscriptions();
|
|
|
|
if ( array_key_exists( $feature, $subscriptions ) ) {
|
|
$quantity = (array) $subscriptions[$feature];
|
|
|
|
$used = $quantity['used'];
|
|
}
|
|
|
|
return $used;
|
|
}
|
|
|
|
/**
|
|
* Make the request to the API endpons
|
|
* @param $url The end part of the url to make the request.
|
|
* $body The body to send the message
|
|
* $method POST | GET | PUT, etc
|
|
* or send a customized message to be showed in case of error
|
|
* $decode_response_body TRUE (default) to decode the body response
|
|
* @return stdClass with the the fields:
|
|
* is_error TRUE or FALSE
|
|
* error in case of is_error be true
|
|
* body in case of is_error be false
|
|
*/
|
|
protected function request_api( $method, $url, $body = null, $decode_response_body = true, $custom_headers = null ) {
|
|
$request = array(
|
|
'method' => $method,
|
|
'accept' => 'application/json',
|
|
'headers' => $this->_get_headers( $custom_headers ),
|
|
'timeout' => self::DEFAULT_TIMEOUT
|
|
);
|
|
if ( ! is_null( $body ) ) {
|
|
$request['body'] = $body;
|
|
}
|
|
$response = wp_remote_request( $url, $request );
|
|
$result = new stdClass();
|
|
$result->url = $url;
|
|
$result->raw = $response;
|
|
if ( is_wp_error( $response ) ) {
|
|
$result->is_error = true;
|
|
$result->error = $response->get_error_message();
|
|
} else {
|
|
$result->response_code = wp_remote_retrieve_response_code( $response );
|
|
if ( 200 === $result->response_code ) {
|
|
if ( true === $decode_response_body ) {
|
|
$result->body = json_decode( $response['body'] );
|
|
if ( false === is_null( $result->body ) ) {
|
|
$result->is_error = false;
|
|
} else {
|
|
$result->is_error = true;
|
|
$result->error = __( 'Error decoding the response', AI1EC_PLUGIN_NAME );
|
|
unset( $result->body );
|
|
}
|
|
} else {
|
|
$result->is_error = false;
|
|
$result->body = $response['body'];
|
|
}
|
|
} else {
|
|
$result->is_error = true;
|
|
$result->error = wp_remote_retrieve_response_message( $response );
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Make a post request to the api
|
|
* @param rest_endpoint Partial URL that can include {calendar_id} that will be replaced by the current calendar signed
|
|
*/
|
|
public function call_api( $method, $endpoint, $body = null, $decode_response_body = true, $custom_headers = null ) {
|
|
$calendar_id = $this->_get_ticket_calendar();
|
|
if ( 0 >= $calendar_id ) {
|
|
return false;
|
|
}
|
|
$url = AI1EC_API_URL . str_replace( '{calendar_id}', $calendar_id, $endpoint );
|
|
return $this->request_api( $method, $url, $body, $decode_response_body, $custom_headers );
|
|
}
|
|
|
|
/**
|
|
* Save an error notification to be showed to the user on WP header of the page
|
|
* @param $response The response got from request_api method.
|
|
* $custom_error_message The custom message to show before the detailed message
|
|
* @return full error message
|
|
*/
|
|
protected function save_error_notification( $response, $custom_error_response ) {
|
|
$error_message = $this->_transform_error_message(
|
|
$custom_error_response,
|
|
$response->raw,
|
|
$response->url,
|
|
true
|
|
);
|
|
$response->error_message = $error_message;
|
|
$notification = $this->_registry->get( 'notification.admin' );
|
|
$notification->store( $error_message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
|
|
error_log( $custom_error_response . ': ' . $error_message . ' - raw error: ' . print_r( $response->raw, true ) );
|
|
return $error_message;
|
|
}
|
|
|
|
/**
|
|
* Save an error notification to be showed to the user on WP header of the page
|
|
* @param $response The response got from request_api method.
|
|
* $custom_error_message The custom message to show before the detailed message
|
|
* @return full error message
|
|
*/
|
|
protected function log_error( $response, $custom_error_response ) {
|
|
$error_message = $this->_transform_error_message(
|
|
$custom_error_response,
|
|
$response->raw,
|
|
$response->url,
|
|
true
|
|
);
|
|
error_log( $custom_error_response . ': ' . $error_message . ' - raw error: ' . print_r( $response->raw, true ) );
|
|
return $error_message;
|
|
}
|
|
|
|
protected function show_error( $error_message ) {
|
|
$notification = $this->_registry->get( 'notification.admin' );
|
|
$notification->store( $error_message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
|
|
error_log( $error_message);
|
|
return $error_message;
|
|
}
|
|
|
|
/**
|
|
* Useful method to check if the response of request_api is a successful message
|
|
*/
|
|
public function is_response_success( $response ) {
|
|
return $response != null &&
|
|
( !isset( $response->is_error ) || ( isset( $response->is_error ) && false === $response->is_error ) );
|
|
}
|
|
|
|
}
|