@@ -0,0 +1,622 @@
|
||||
<?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 ) );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
class Ai1ec_Api_Features {
|
||||
|
||||
const CODE_API_ACCESS = 'api-access';
|
||||
const CODE_TICKETING = 'ticketing';
|
||||
const CODE_TWITTER = 'twitter';
|
||||
const CODE_FRONTEND_SUBMISSIONS = 'frontend-submissions';
|
||||
const CODE_CSV_IMPORT = 'csv-import';
|
||||
const CODE_SUPER_WIDGET = 'super-widget';
|
||||
const CODE_EXTENDED_VIEWS = 'extended-views';
|
||||
const CODE_BIG_FILTERING = 'big-filtering';
|
||||
const CODE_CUSTOM_FILTERS = 'custom-filter-groups';
|
||||
const CODE_DISCOVER_EVENTS = 'discover-events';
|
||||
const CODE_EVENT_PROMOTE = 'event-promote';
|
||||
const CODE_FACEBOOK_INTEGRATION = 'facebook-integration';
|
||||
const CODE_FEATURED_EVENTS = 'featured-events';
|
||||
const CODE_MAILCHIMP = 'mailchimp';
|
||||
const CODE_PHRASE_OVERRIDE = 'phrase-override';
|
||||
const CODE_POPOVERS = 'popovers';
|
||||
const CODE_SAVE_AND_SHARE = 'save-and-share';
|
||||
const CODE_VENUES = 'venues';
|
||||
const CODE_IMPORT_FEEDS = 'import-feeds';
|
||||
|
||||
public static $features = array(
|
||||
self::CODE_API_ACCESS => '',
|
||||
self::CODE_TICKETING => '',
|
||||
self::CODE_TWITTER => 'all-in-one-event-calendar-twitter-integration/all-in-one-event-calendar-twitter-integration.php',
|
||||
self::CODE_FRONTEND_SUBMISSIONS => 'all-in-one-event-calendar-frontend-submissions/all-in-one-event-calendar-frontend-submissions.php',
|
||||
self::CODE_CSV_IMPORT => 'all-in-one-event-calendar-csv-feed/all-in-one-event-calendar-csv-feed.php',
|
||||
self::CODE_SUPER_WIDGET => 'all-in-one-event-calendar-super-widget/all-in-one-event-calendar-super-widget.php',
|
||||
self::CODE_EXTENDED_VIEWS => 'all-in-one-event-calendar-extended-views/all-in-one-event-calendar-extended-views.php',
|
||||
self::CODE_BIG_FILTERING => 'all-in-one-event-calendar-big-filtering/all-in-one-event-calendar-big-filtering.php',
|
||||
self::CODE_CUSTOM_FILTERS => 'all-in-one-event-calendar-custom-filter-groups/all-in-one-event-calendar-custom-filter-groups.php',
|
||||
self::CODE_DISCOVER_EVENTS => '',
|
||||
self::CODE_EVENT_PROMOTE => 'all-in-one-event-calendar-event-promote/all-in-one-event-calendar-event-promote.php',
|
||||
self::CODE_FACEBOOK_INTEGRATION => 'all-in-one-event-calendar-facebook-integration/all-in-one-event-calendar-facebook-integration.php',
|
||||
self::CODE_FEATURED_EVENTS => 'all-in-one-event-calendar-featured-events/all-in-one-event-calendar-featured-events.php',
|
||||
self::CODE_MAILCHIMP => 'all-in-one-event-calendar-mailchimp/all-in-one-event-calendar-mailchimp.php',
|
||||
self::CODE_PHRASE_OVERRIDE => 'all-in-one-event-calendar-phrase-override/all-in-one-event-calendar-phrase-override.php',
|
||||
self::CODE_POPOVERS => 'all-in-one-event-calendar-popovers/all-in-one-event-calendar-popovers.php',
|
||||
self::CODE_SAVE_AND_SHARE => 'all-in-one-event-calendar-save-and-share/all-in-one-event-calendar-save-and-share.php',
|
||||
self::CODE_VENUES => 'all-in-one-event-calendar-venue/all-in-one-event-calendar-venue.php',
|
||||
self::CODE_IMPORT_FEEDS => '',
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class for Timely API communication related to Discover Events and Feeds.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.4
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
class Ai1ec_Api_Feeds extends Ai1ec_Api_Abstract {
|
||||
|
||||
// Feed status
|
||||
// c = Feed not migrated yet to API
|
||||
// a = Feed migrated to API (all events)
|
||||
// b = Feed migrated to API (individual events were selected)
|
||||
public static $FEED_NOT_MIGRATED_CODE = 'c';
|
||||
public static $FEED_API_ALL_EVENTS_CODE = 'a';
|
||||
public static $FEED_API_SOME_EVENTS_CODE = 'b';
|
||||
|
||||
/**
|
||||
* Post construction routine.
|
||||
*
|
||||
* Override this method to perform post-construction tasks.
|
||||
*
|
||||
* @return void Return from this method is ignored.
|
||||
*/
|
||||
protected function _initialize() {
|
||||
parent::_initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get static var (for PHP 5.2 compatibility)
|
||||
*
|
||||
* @param String $var
|
||||
*/
|
||||
public function getStaticVar($var) {
|
||||
return self::$$var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting a suggested events list.
|
||||
* @return stClass Response using the following format:
|
||||
* [total] => 10
|
||||
* [per_page] => 8
|
||||
* [current_page] => 1
|
||||
* [last_page] => 2
|
||||
* [next_page_url] =>
|
||||
* [prev_page_url] =>
|
||||
* [from] => 1
|
||||
* [to] => 8
|
||||
* [data] => Array list of suggested events
|
||||
*/
|
||||
public function get_suggested_events() {
|
||||
$calendar_id = $this->_get_ticket_calendar();
|
||||
if ( 0 >= $calendar_id ) {
|
||||
throw new Exception( 'Calendar ID not found' );
|
||||
}
|
||||
|
||||
$body = null;
|
||||
if (
|
||||
isset( $_POST[ 'lat' ] ) &&
|
||||
isset( $_POST[ 'lng' ] ) &&
|
||||
isset( $_POST[ 'radius' ] )
|
||||
) {
|
||||
$body = array(
|
||||
'lat' => $_POST[ 'lat' ],
|
||||
'lng' => $_POST[ 'lng' ],
|
||||
'radius' => $_POST[ 'radius' ]
|
||||
);
|
||||
}
|
||||
|
||||
$page = isset( $_POST[ 'page' ] ) ? $_POST[ 'page' ] : 1;
|
||||
$max = isset( $_POST[ 'max' ] ) ? $_POST[ 'max' ] : 8;
|
||||
$term = isset( $_POST[ 'term' ] ) && $_POST[ 'term' ]
|
||||
? urlencode( $_POST[ 'term' ] )
|
||||
: '*';
|
||||
$location = isset( $_POST[ 'location' ] ) && $_POST[ 'location' ]
|
||||
? '&location=' . urlencode( $_POST[ 'location' ] )
|
||||
: '';
|
||||
|
||||
$url = AI1EC_API_URL .
|
||||
"calendars/$calendar_id/discover/events?page=$page&max=$max&term=$term" .
|
||||
$location;
|
||||
|
||||
$response = $this->request_api( 'GET', $url,
|
||||
$body,
|
||||
true //decode body response
|
||||
);
|
||||
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
return $response->body;
|
||||
} else {
|
||||
$this->save_error_notification(
|
||||
$response,
|
||||
__( 'We were unable to get the Suggested Events from Time.ly Network', AI1EC_PLUGIN_NAME )
|
||||
);
|
||||
throw new Exception( 'We were unable to get the Suggested Events from Time.ly Network' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the API to Process and Import the Feed
|
||||
*/
|
||||
public function import_feed( $entry ) {
|
||||
$calendar_id = $this->_get_ticket_calendar();
|
||||
if ( 0 >= $calendar_id ) {
|
||||
throw new Exception( 'Calendar ID not found' );
|
||||
}
|
||||
$response = $this->request_api( 'POST', AI1EC_API_URL . 'calendars/' . $calendar_id . '/feeds/import',
|
||||
array(
|
||||
'url' => $entry['feed_url'],
|
||||
'categories' => $entry['feed_category'],
|
||||
'tags' => $entry['feed_tags'],
|
||||
'allow_comments' => $entry['comments_enabled'],
|
||||
'show_maps' => $entry['map_display_enabled'],
|
||||
'import_any_tag_and_categories' => $entry['keep_tags_categories'],
|
||||
'preserve_imported_events' => $entry['keep_old_events'],
|
||||
'assign_default_utc' => $entry['import_timezone']
|
||||
)
|
||||
);
|
||||
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
// Refresh list of subscriptions and limits
|
||||
$this->get_subscriptions( true );
|
||||
|
||||
return $response->body;
|
||||
} else {
|
||||
$this->save_error_notification(
|
||||
$response,
|
||||
__( 'We were unable to import feed', AI1EC_PLUGIN_NAME )
|
||||
);
|
||||
throw new Exception( $this->get_api_error_msg( $response->raw ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the API to get the feed
|
||||
*/
|
||||
public function get_feed( $feed_id ) {
|
||||
$calendar_id = $this->_get_ticket_calendar();
|
||||
if ( 0 >= $calendar_id ) {
|
||||
throw new Exception( 'Calendar ID not found' );
|
||||
}
|
||||
$response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $calendar_id . '/feeds/get/' . $feed_id,
|
||||
array( 'max' => '9999' )
|
||||
);
|
||||
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
return $response->body;
|
||||
} else {
|
||||
$this->save_error_notification(
|
||||
$response,
|
||||
__( 'We were unable to get feed data', AI1EC_PLUGIN_NAME )
|
||||
);
|
||||
throw new Exception( $this->get_api_error_msg( $response->raw ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the API to get list of feed subscriptions
|
||||
*/
|
||||
public function get_feed_subscriptions( $force_refresh = false ) {
|
||||
$feeds_subscriptions = get_transient( 'ai1ec_api_feeds_subscriptions' );
|
||||
|
||||
if ( $force_refresh || false === $feeds_subscriptions ) {
|
||||
$response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $this->_get_ticket_calendar() . '/feeds/list',
|
||||
null,
|
||||
true
|
||||
);
|
||||
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
$feeds_subscriptions = (array) $response->body;
|
||||
} else {
|
||||
$feeds_subscriptions = array();
|
||||
}
|
||||
|
||||
// Save for 5 minutes
|
||||
$minutes = 5;
|
||||
set_transient( 'ai1ec_api_feeds_subscriptions', $feeds_subscriptions, $minutes * 60 );
|
||||
}
|
||||
|
||||
return $feeds_subscriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync feed subscriptions
|
||||
*/
|
||||
public function get_and_sync_feed_subscriptions() {
|
||||
$feeds_subscriptions = $this->get_feed_subscriptions();
|
||||
|
||||
$db = $this->_registry->get( 'dbi.dbi' );
|
||||
$table_name = $db->get_table_name( 'ai1ec_event_feeds' );
|
||||
|
||||
// Select all feeds
|
||||
$rows = $db->select(
|
||||
$table_name,
|
||||
array(
|
||||
'feed_id',
|
||||
'feed_url',
|
||||
'feed_name',
|
||||
'feed_category',
|
||||
'feed_tags',
|
||||
'comments_enabled',
|
||||
'map_display_enabled',
|
||||
'keep_tags_categories',
|
||||
'keep_old_events',
|
||||
'import_timezone'
|
||||
)
|
||||
);
|
||||
|
||||
// Iterate over API response
|
||||
foreach( $feeds_subscriptions as $api_feed ) {
|
||||
$found = false;
|
||||
|
||||
foreach ( $rows as $row ) {
|
||||
// Check if URL is the same
|
||||
if ( trim( $row->feed_url ) === trim( $api_feed->url ) ) {
|
||||
$found = true;
|
||||
|
||||
// Update feed
|
||||
$db->update(
|
||||
$table_name,
|
||||
array(
|
||||
'comments_enabled' => $api_feed->allow_comments,
|
||||
'map_display_enabled' => $api_feed->show_maps,
|
||||
'keep_tags_categories' => $api_feed->import_any_tag_and_categories,
|
||||
'keep_old_events' => $api_feed->preserve_imported_events,
|
||||
'import_timezone' => $api_feed->assign_default_utc,
|
||||
'feed_name' => $api_feed->feed_id
|
||||
),
|
||||
array(
|
||||
'feed_id' => $row->feed_id
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Not found in local database.. Insert
|
||||
if ( ! $found ) {
|
||||
$entry = array(
|
||||
'feed_url' => $api_feed->url,
|
||||
'feed_name' => $api_feed->feed_id,
|
||||
'feed_category' => $api_feed->categories,
|
||||
'feed_tags' => $api_feed->tags,
|
||||
'comments_enabled' => $api_feed->allow_comments,
|
||||
'map_display_enabled' => $api_feed->show_maps,
|
||||
'keep_tags_categories' => $api_feed->import_any_tag_and_categories,
|
||||
'keep_old_events' => $api_feed->preserve_imported_events,
|
||||
'import_timezone' => $api_feed->assign_default_utc
|
||||
);
|
||||
$format = array( '%s', '%s', '%s', '%s', '%d', '%d', '%d', '%d', '%d' );
|
||||
$db->insert(
|
||||
$table_name,
|
||||
$entry,
|
||||
$format
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the API to subscribe feed
|
||||
*/
|
||||
public function subscribe_feed( $feed_id, $feed_event_uid = '' ) {
|
||||
$calendar_id = $this->_get_ticket_calendar();
|
||||
if ( 0 >= $calendar_id ) {
|
||||
throw new Exception( 'Calendar ID not found' );
|
||||
}
|
||||
|
||||
$response = $this->request_api( 'POST', AI1EC_API_URL . 'calendars/' . $calendar_id . '/feeds/subscribe',
|
||||
array(
|
||||
'feed_id' => $feed_id,
|
||||
'feed_event_uid' => $feed_event_uid
|
||||
)
|
||||
);
|
||||
|
||||
// Refresh list of subscriptions and limits
|
||||
$this->get_subscriptions( true );
|
||||
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
return $response->body;
|
||||
} else {
|
||||
$this->save_error_notification(
|
||||
$response,
|
||||
__( 'We were unable to subscribe feed', AI1EC_PLUGIN_NAME )
|
||||
);
|
||||
throw new Exception( $this->get_api_error_msg( $response->raw ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the API to unsubscribe feed
|
||||
*/
|
||||
public function unsubscribe_feed( $feed_id, $feed_event_uid = '' ) {
|
||||
$calendar_id = $this->_get_ticket_calendar();
|
||||
if ( 0 >= $calendar_id ) {
|
||||
throw new Exception( 'Calendar ID not found' );
|
||||
}
|
||||
|
||||
$response = $this->request_api( 'POST', AI1EC_API_URL . 'calendars/' . $calendar_id . '/feeds/unsubscribe',
|
||||
array(
|
||||
'feed_id' => $feed_id,
|
||||
'feed_event_uid' => $feed_event_uid
|
||||
)
|
||||
);
|
||||
|
||||
// Refresh list of subscriptions and limits
|
||||
$this->get_subscriptions( true );
|
||||
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
return $response->body;
|
||||
} else {
|
||||
$this->save_error_notification(
|
||||
$response,
|
||||
__( 'We were unable to unsubscribe feed', AI1EC_PLUGIN_NAME )
|
||||
);
|
||||
throw new Exception( $this->get_api_error_msg( $response->raw ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class for Timely API communication for Registration.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.4
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
class Ai1ec_Api_Registration extends Ai1ec_Api_Abstract {
|
||||
|
||||
/**
|
||||
* Post construction routine.
|
||||
*
|
||||
* Override this method to perform post-construction tasks.
|
||||
*
|
||||
* @return void Return from this method is ignored.
|
||||
*/
|
||||
protected function _initialize() {
|
||||
parent::_initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object Response body in JSON.
|
||||
*/
|
||||
public function signin() {
|
||||
$body['email'] = $_POST['ai1ec_email'];
|
||||
$body['password'] = $_POST['ai1ec_password'];
|
||||
$body['calendar_type'] = $_POST['ai1ec_calendar_type'];
|
||||
$response = $this->request_api( 'POST', AI1EC_API_URL . 'auth/authenticate', $body, true, array( 'Authorization' => null ) );
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
$response_body = (array) $response->body;
|
||||
// Save calendar ID as 0 first, otherwise the auth data won't be saved in the database before creating/finding the calendar
|
||||
$this->save_ticketing_settings( $response_body['message'], true, $response_body['auth_token'], 0, $body['email'] );
|
||||
// Now save the calendar ID
|
||||
$this->save_calendar_id( $this->_get_ticket_calendar() );
|
||||
$this->has_payment_settings();
|
||||
$this->get_subscriptions( true );
|
||||
$this->sync_api_settings();
|
||||
} else {
|
||||
$error_message = $this->save_error_notification( $response, __( 'We were unable to Sign you In for Time.ly Network', AI1EC_PLUGIN_NAME ) );
|
||||
$this->save_ticketing_settings( $error_message, false, '', 0, null );
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object Response body in JSON.
|
||||
*/
|
||||
public function signup() {
|
||||
$body['name'] = $_POST['ai1ec_name'];
|
||||
$body['email'] = $_POST['ai1ec_email'];
|
||||
$body['password'] = $_POST['ai1ec_password'];
|
||||
$body['password_confirmation'] = $_POST['ai1ec_password_confirmation'];
|
||||
$body['phone'] = $_POST['ai1ec_phone'];
|
||||
$body['calendar_type'] = $_POST['ai1ec_calendar_type'];
|
||||
$body['terms'] = $_POST['ai1ec_terms'];
|
||||
$response = $this->request_api( 'POST', AI1EC_API_URL . 'auth/register', $body, true );
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
$response_body = (array) $response->body;
|
||||
// Save calendar ID as 0 first, otherwise the auth data won't be saved in the database before creating the calendar
|
||||
$this->save_ticketing_settings( $response_body['Registration'], true, $response_body['auth_token'] , 0, $body['email'] );
|
||||
// Now save the calendar ID
|
||||
$this->save_calendar_id( $this->_create_calendar() );
|
||||
$this->has_payment_settings();
|
||||
$this->get_subscriptions( true );
|
||||
$this->sync_api_settings();
|
||||
} else {
|
||||
$error_message = $this->save_error_notification( $response, __( 'We were unable to Sign you Up for Time.ly Network', AI1EC_PLUGIN_NAME ) );
|
||||
$this->save_ticketing_settings( $error_message, false, '', 0, null );
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object Response body in JSON.
|
||||
*/
|
||||
protected function availability() {
|
||||
$api_features = get_transient( 'ai1ec_api_features' );
|
||||
|
||||
if ( false === $api_features || ( defined( 'AI1EC_DEBUG' ) && AI1EC_DEBUG ) ) {
|
||||
$response = $this->request_api( 'GET', AI1EC_API_URL . 'feature/availability', null, true );
|
||||
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
$api_features = (array) $response->body;
|
||||
} else {
|
||||
$api_features = array();
|
||||
}
|
||||
|
||||
// Save for 5 minutes
|
||||
$minutes = 5;
|
||||
set_transient( 'ai1ec_api_features', $api_features, $minutes * 60 );
|
||||
}
|
||||
|
||||
return $api_features;
|
||||
}
|
||||
|
||||
protected function is_feature_available( $feature_code ) {
|
||||
$availability = $this->availability();
|
||||
|
||||
if ( ! is_null( $availability ) ) {
|
||||
foreach ( $availability as $value ) {
|
||||
if ( isset( $value->code ) && $feature_code === $value->code
|
||||
&& isset( $value->available ) && true === $value->available ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object Response body in JSON.
|
||||
*/
|
||||
protected function settings() {
|
||||
$calendar_settings = get_transient( 'ai1ec_calendar_settings' );
|
||||
|
||||
if ( false === $calendar_settings || ( defined( 'AI1EC_DEBUG' ) && AI1EC_DEBUG ) ) {
|
||||
$response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $this->_get_ticket_calendar() . '/settings', null, true );
|
||||
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
$calendar_settings = (array) $response->body;
|
||||
} else {
|
||||
$calendar_settings = array();
|
||||
}
|
||||
|
||||
// Save for 5 minutes
|
||||
$minutes = 5;
|
||||
set_transient( 'ai1ec_calendar_settings', $calendar_settings, $minutes * 60 );
|
||||
}
|
||||
|
||||
return $calendar_settings;
|
||||
}
|
||||
|
||||
public function is_api_sign_up_available() {
|
||||
return $this->is_feature_available( Ai1ec_Api_Features::CODE_API_ACCESS );
|
||||
}
|
||||
|
||||
public function is_ticket_available() {
|
||||
return $this->is_feature_available( Ai1ec_Api_Features::CODE_TICKETING );
|
||||
}
|
||||
|
||||
public function is_ticket_enabled() {
|
||||
return $this->has_subscription_active( Ai1ec_Api_Features::CODE_TICKETING );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean the ticketing settings on WP database only
|
||||
*/
|
||||
public function signout() {
|
||||
$calendar_id = $this->_get_ticket_calendar( false );
|
||||
if ( 0 >= $calendar_id ) {
|
||||
$this->clear_ticketing_settings();
|
||||
return false;
|
||||
}
|
||||
$response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $calendar_id . '/signout', null, true );
|
||||
// Consider "Unauthorized" status (401) a valid response
|
||||
if ( $this->is_response_success( $response ) || 401 === wp_remote_retrieve_response_code( $response->raw ) ) {
|
||||
$this->clear_ticketing_settings();
|
||||
return array( 'message' => '' );
|
||||
} else {
|
||||
$error_message = $this->save_error_notification( $response, __( 'We were unable to Sign you Out of Time.ly Network', AI1EC_PLUGIN_NAME ) );
|
||||
return array( 'message' => $error_message );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object Response body from API.
|
||||
*/
|
||||
public function save_payment_preferences() {
|
||||
$calendar_id = $this->_get_ticket_calendar();
|
||||
if ( 0 >= $calendar_id ) {
|
||||
return false;
|
||||
}
|
||||
$settings = array(
|
||||
'payment_method' => $_POST['ai1ec_payment_method'],
|
||||
'paypal_email' => $_POST['ai1ec_paypal_email'],
|
||||
'first_name' => $_POST['ai1ec_first_name'],
|
||||
'last_name' => $_POST['ai1ec_last_name'],
|
||||
'currency' => $_POST['ai1ec_currency']
|
||||
);
|
||||
$custom_headers['content-type'] = 'application/x-www-form-urlencoded';
|
||||
$response = $this->request_api( 'PUT', AI1EC_API_URL . 'calendars/' . $calendar_id . '/payment',
|
||||
$settings,
|
||||
true, //decode response body
|
||||
$custom_headers
|
||||
);
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
$this->save_payment_settings( $settings );
|
||||
$notification = $this->_registry->get( 'notification.admin' );
|
||||
$notification->store(
|
||||
__( 'Payment preferences were saved.', AI1EC_PLUGIN_NAME ),
|
||||
'updated',
|
||||
0,
|
||||
array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
|
||||
false
|
||||
);
|
||||
return $response->body;
|
||||
} else {
|
||||
$this->save_error_notification( $response,
|
||||
__( 'Payment preferences were not saved.', AI1EC_PLUGIN_NAME )
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function _order_comparator( $order1, $order2 ) {
|
||||
return strcmp( $order1->created_at, $order2->created_at ) * -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object Response body in JSON.
|
||||
*/
|
||||
public function get_purchases() {
|
||||
$response = $this->request_api( 'GET', AI1EC_API_URL . 'calendars/' . $this->_get_ticket_calendar() . '/sales',
|
||||
null, //body
|
||||
true //decode response body
|
||||
);
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
$result = $response->body;
|
||||
if ( isset( $result->orders ) ) {
|
||||
usort( $result->orders, array( "Ai1ec_Api_Registration", "_order_comparator" ) );
|
||||
return $result->orders;
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
} else {
|
||||
$this->save_error_notification( $response,
|
||||
__( 'We were unable to get the Sales information from Time.ly Network', AI1EC_PLUGIN_NAME )
|
||||
);
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync settings from API after signing in
|
||||
*/
|
||||
public function sync_api_settings() {
|
||||
// Sync feeds subscriptions
|
||||
try {
|
||||
$api_feed = $this->_registry->get( 'model.api.api-feeds' );
|
||||
$api_feed->get_and_sync_feed_subscriptions();
|
||||
} catch ( Exception $e ) {
|
||||
$error_message = 'Some feeds were not imported to Time.ly Network. Error: ' . $e->getMessage();
|
||||
|
||||
$notification = $this->_registry->get( 'notification.admin' );
|
||||
$notification->store( $error_message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
class Ai1ec_Api_Settings {
|
||||
const FACEBOOK_API_KEY = 'facebook_api_key';
|
||||
}
|
||||
@@ -0,0 +1,901 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class for Timely API communication for Ticketing.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.4
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
class Ai1ec_Api_Ticketing extends Ai1ec_Api_Abstract {
|
||||
|
||||
const API_EVENT_DATA = '_ai1ec_api_event_id';
|
||||
|
||||
const ATTR_EVENT_ID = 'api_event_id';
|
||||
const ATTR_THUMBNAIL_ID = 'thumbnail_id';
|
||||
const ATTR_ICS_CHECKOUT_URL = 'ics_checkout_url';
|
||||
const ATTR_ICS_API_URL = 'ics_api_url';
|
||||
const ATTR_ACCOUNT = 'account';
|
||||
const ATTR_CALENDAR_ID = 'calendar_id';
|
||||
const ATTR_CURRENCY = 'currency';
|
||||
|
||||
const MAX_TICKET_TO_BUY_DEFAULT = 25;
|
||||
|
||||
/**
|
||||
* Post construction routine.
|
||||
*
|
||||
* Override this method to perform post-construction tasks.
|
||||
*
|
||||
* @return void Return from this method is ignored.
|
||||
*/
|
||||
protected function _initialize() {
|
||||
parent::_initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the valid Tickets Types (not removed) included inside the Ticket Event
|
||||
*/
|
||||
private function _count_valid_tickets( $post_ticket_types ) {
|
||||
if (false === isset( $post_ticket_types ) || 0 === count( $post_ticket_types ) ) {
|
||||
return 0;
|
||||
} else {
|
||||
$count = 0;
|
||||
foreach ( $post_ticket_types as $ticket_type_ite ) {
|
||||
if ( !isset( $ticket_type_ite['remove'] ) ) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an error if the Ticket Event is not owned by the Current account
|
||||
*/
|
||||
private function _prevent_update_ticket_event( Ai1ec_Event $event, $ajax_action = false ) {
|
||||
if ( $this->is_ticket_event_imported( $event->get( 'post_id' ) ) ) {
|
||||
//prevent changes on Ticket Events that were imported
|
||||
$error = __( 'This Event was replicated from another site. Changes are not allowed.', AI1EC_PLUGIN_NAME );
|
||||
if ( ! $ajax_action ) {
|
||||
$notification = $this->_registry->get( 'notification.admin' );
|
||||
$notification->store(
|
||||
$error,
|
||||
'error',
|
||||
0,
|
||||
array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
|
||||
false
|
||||
);
|
||||
}
|
||||
return $error;
|
||||
}
|
||||
if ( $this->is_ticket_event_from_another_account( $event->get( 'post_id' ) ) ) {
|
||||
//prevent changes on Ticket Events that were imported
|
||||
$error = sprintf(
|
||||
__( 'This Event was created using a different account %s. Changes are not allowed.', AI1EC_PLUGIN_NAME ),
|
||||
$this->get_api_event_account( $event->get( 'post_id' ) )
|
||||
);
|
||||
if ( ! $ajax_action ) {
|
||||
$notification = $this->_registry->get( 'notification.admin' );
|
||||
$notification->store(
|
||||
$error,
|
||||
'error',
|
||||
0,
|
||||
array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
|
||||
false
|
||||
);
|
||||
}
|
||||
return $error;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run some validations inside the _POST request to check if the Event
|
||||
* submmited is a valid event for Tickets
|
||||
* @return NULL in case of success or a Message in case of error
|
||||
*/
|
||||
private function _is_valid_post( Ai1ec_Event $event, $updating ) {
|
||||
$message = null;
|
||||
if ( ( isset( $_POST['ai1ec_rdate'] ) && ! empty( $_POST['ai1ec_rdate'] ) ) ||
|
||||
( isset( $_POST['ai1ec_repeat'] ) && ! empty( $_POST['ai1ec_repeat'] ) )
|
||||
) {
|
||||
$message = __( 'The Repeat option was selected but recurrence is not supported by Event with Tickets.', AI1EC_PLUGIN_NAME );
|
||||
} else if ( isset( $_POST['ai1ec_tickets_loading_error'] ) ) {
|
||||
//do not update tickets because is unsafe. There was a problem to load the tickets,
|
||||
//the customer received the same message when the event was loaded.
|
||||
$message = $_POST['ai1ec_tickets_loading_error'];
|
||||
} else if ( false === ai1ec_is_blank( $event->get( 'ical_feed_url' ) ) ) {
|
||||
//prevent ticket creating inside Regular Events Imported events
|
||||
$message = __( 'This Event was replicated from another site. Any changes on Tickets were discarded.', AI1EC_PLUGIN_NAME );
|
||||
} else {
|
||||
$error = $this->_prevent_update_ticket_event( $event );
|
||||
if ( null !== $error ) {
|
||||
$message = $error;
|
||||
} else if ( ! isset( $_POST['ai1ec_tickets'] ) || 0 === $this->_count_valid_tickets( $_POST['ai1ec_tickets'] ) ) {
|
||||
$message = __( 'The Event has the cost option Ticket selected but no ticket was included.', AI1EC_PLUGIN_NAME );
|
||||
} else if ( false === $this->has_payment_settings() ) {
|
||||
$message = __( 'You need to save the payments settings to create ticket events.', AI1EC_PLUGIN_NAME );
|
||||
} else if ( ! isset( $_POST['tax_options'] ) && ! $updating ) {
|
||||
$message = __( 'Tax and Invoice options are required.', AI1EC_PLUGIN_NAME );
|
||||
}
|
||||
}
|
||||
if ( null !== $message ) {
|
||||
$notification = $this->_registry->get( 'notification.admin' );
|
||||
$notification->store( $message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
|
||||
return $message;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a Ticket Event on API server
|
||||
* @return object Response body in JSON.
|
||||
*/
|
||||
public function store_event( Ai1ec_Event $event, WP_Post $post, $updating ) {
|
||||
|
||||
$error = $this->_is_valid_post( $event, $updating );
|
||||
if ( null !== $error ) {
|
||||
return $error;
|
||||
}
|
||||
$api_event_id = $this->get_api_event_id( $event->get( 'post_id' ) );
|
||||
$is_new = ! $api_event_id;
|
||||
$fields = array( 'visibility' => $_POST['visibility'] );
|
||||
if ( isset( $_POST['tax_options'] ) ) {
|
||||
$fields['tax_options'] = $_POST['tax_options'];
|
||||
}
|
||||
$body_data = $this->parse_event_fields_to_api_structure(
|
||||
$event,
|
||||
$post,
|
||||
$_POST['ai1ec_tickets'],
|
||||
$fields
|
||||
);
|
||||
$url = AI1EC_API_URL . 'events';
|
||||
if ( $api_event_id ) {
|
||||
$url = $url . '/' . $api_event_id;
|
||||
}
|
||||
|
||||
//get the thumbnail id saved previously
|
||||
$api_data = $this->get_api_event_data( $event->get( 'post_id' ) );
|
||||
if ( isset( $api_data[self::ATTR_THUMBNAIL_ID] ) ) {
|
||||
$event_thumbnail_id = $api_data[self::ATTR_THUMBNAIL_ID];
|
||||
} else {
|
||||
$event_thumbnail_id = 0;
|
||||
}
|
||||
//get the current thumbnail id
|
||||
$post_thumbnail_id = get_post_thumbnail_id( $event->get( 'post_id' ) );
|
||||
if ( false === isset( $post_thumbnail_id ) ) {
|
||||
$post_thumbnail_id = 0;
|
||||
}
|
||||
$update_image = ( $event_thumbnail_id !== $post_thumbnail_id );
|
||||
$payload = '';
|
||||
$custom_headers = null;
|
||||
|
||||
if ( true === $update_image && 0 < $post_thumbnail_id ) {
|
||||
$boundary = wp_generate_password( 24 );
|
||||
$custom_headers['Content-Type'] = 'multipart/form-data; boundary=' . $boundary;
|
||||
$body_data['update_image'] = '1';
|
||||
foreach ($body_data as $key => $value) {
|
||||
if ( is_array( $value ) ) {
|
||||
$index = 0;
|
||||
foreach ( $value as $arr_key => $arr_value ) {
|
||||
if ( is_array( $arr_value ) ) {
|
||||
foreach ( $arr_value as $child_key => $child_value ) {
|
||||
$payload .= '--' . $boundary;
|
||||
$payload .= "\r\n";
|
||||
$payload .= 'Content-Disposition: form-data; name="' . $key . '[' . $index . '][' . $child_key . ']"' . "\r\n";
|
||||
$payload .= "\r\n";
|
||||
$payload .= $child_value;
|
||||
$payload .= "\r\n";
|
||||
}
|
||||
} else {
|
||||
$payload .= '--' . $boundary;
|
||||
$payload .= "\r\n";
|
||||
$payload .= 'Content-Disposition: form-data; name="tax_options[' . $arr_key . ']"' . "\r\n";
|
||||
$payload .= "\r\n";
|
||||
$payload .= $arr_value;
|
||||
$payload .= "\r\n";
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
} else {
|
||||
$payload .= '--' . $boundary;
|
||||
$payload .= "\r\n";
|
||||
$payload .= 'Content-Disposition: form-data; name="' . $key . '"' . "\r\n";
|
||||
$payload .= "\r\n";
|
||||
$payload .= $value;
|
||||
$payload .= "\r\n";
|
||||
}
|
||||
}
|
||||
$file_path = get_attached_file ( $post_thumbnail_id );
|
||||
$file_type = wp_check_filetype ( $file_path );
|
||||
$payload .= '--' . $boundary;
|
||||
$payload .= "\r\n";
|
||||
$payload .= 'Content-Disposition: form-data; name="image_id"; filename="' . basename( $file_path ) . '"' . "\r\n";
|
||||
$payload .= 'Content-Type: ' . $file_type['type'] . "\r\n";
|
||||
$payload .= "\r\n";
|
||||
$payload .= file_get_contents( $file_path );
|
||||
$payload .= "\r\n";
|
||||
$payload .= '--' . $boundary . '--';
|
||||
} else {
|
||||
$body_data['update_image'] = (true === $update_image) ? '1' : '0';
|
||||
$payload = $body_data;
|
||||
}
|
||||
$response = $this->request_api( 'POST', $url, $payload,
|
||||
true, //true to decode response body
|
||||
$custom_headers
|
||||
);
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
$api_event_id = $response->body->id;
|
||||
if ( isset( $response->body->currency ) ) {
|
||||
$currency = $response->body->currency;
|
||||
} else {
|
||||
$currency = 'USD';
|
||||
}
|
||||
$currency = $response->body->currency;
|
||||
if ( $post_thumbnail_id <= 0 ) {
|
||||
$post_thumbnail_id = null;
|
||||
}
|
||||
$this->save_api_event_data( $event->get( 'post_id') , $api_event_id, null, null, $currency, $post_thumbnail_id );
|
||||
return true;
|
||||
} else {
|
||||
$error_message = '';
|
||||
if ( $is_new ) {
|
||||
$error_message = __( 'We were unable to create the Event on Time.ly Ticketing', AI1EC_PLUGIN_NAME );
|
||||
} else {
|
||||
$error_message = __( 'We were unable to update the Event on Time.ly Ticketing', AI1EC_PLUGIN_NAME );
|
||||
}
|
||||
return $this->save_error_notification( $response, $error_message );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the fields of an Event to the structure used by API
|
||||
*/
|
||||
public function parse_event_fields_to_api_structure( Ai1ec_Event $event , WP_Post $post, $post_ticket_types, $api_fields_values ) {
|
||||
$calendar_id = $this->_get_ticket_calendar();
|
||||
if ( $calendar_id <= 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//fields of ai1ec events table used by API
|
||||
$body['latitude'] = $event->get( 'latitude' );
|
||||
$body['longitude'] = $event->get( 'longitude' );
|
||||
$body['post_id'] = $event->get( 'post_id' );
|
||||
$body['calendar_id'] = $calendar_id;
|
||||
$body['dtstart'] = $event->get( 'start' )->format_to_javascript();
|
||||
$body['dtend'] = $event->getenddate()->format_to_javascript();
|
||||
$body['timezone'] = $event->get( 'timezone_name' );
|
||||
$body['venue_name'] = $event->get( 'venue' );
|
||||
$body['address'] = $event->get( 'address' );
|
||||
$body['city'] = $event->get( 'city' );
|
||||
$body['province'] = $event->get( 'province' );
|
||||
$body['postal_code'] = $event->get( 'postal_code' );
|
||||
$body['country'] = $event->get( 'country' );
|
||||
$body['contact_name'] = $event->get( 'contact_name' );
|
||||
$body['contact_phone'] = $event->get( 'contact_phone' );
|
||||
$body['contact_email'] = $event->get( 'contact_email' );
|
||||
$body['contact_website'] = $event->get( 'contact_url' );
|
||||
$body['uid'] = $event->get_uid();
|
||||
$body['title'] = $post->post_title;
|
||||
$body['description'] = $post->post_content;
|
||||
$body['url'] = get_permalink( $post->ID );
|
||||
$body['status'] = $post->post_status;
|
||||
|
||||
$utc_current_time = $this->_registry->get( 'date.time')->format_to_javascript();
|
||||
$body['created_at'] = $utc_current_time;
|
||||
$body['updated_at'] = $utc_current_time;
|
||||
|
||||
//removing blank values
|
||||
foreach ($body as $key => $value) {
|
||||
if ( ai1ec_is_blank( $value ) ) {
|
||||
unset( $body[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_null( $api_fields_values ) || 0 == count( $api_fields_values ) ) {
|
||||
$api_fields_values = array( 'status' => 'closed', 'ai1ec_version' => AI1EC_VERSION );
|
||||
} else {
|
||||
if ( ! isset( $api_fields_values['ai1ec_version'] ) ) {
|
||||
$api_fields_values['ai1ec_version'] = AI1EC_VERSION;
|
||||
}
|
||||
foreach ( $api_fields_values as $key => $value ) {
|
||||
$body[$key] = $api_fields_values[$key];
|
||||
if ( 'visibility' === $key ) {
|
||||
if ( 0 === strcasecmp( 'private', $value ) ) {
|
||||
$body['status'] = 'private';
|
||||
} else if ( 0 === strcasecmp( 'password', $value ) ) {
|
||||
$body['status'] = 'password';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tickets_types = array();
|
||||
if ( ! is_null( $post_ticket_types ) ) {
|
||||
$index = 0;
|
||||
foreach ( $post_ticket_types as $ticket_type_ite ) {
|
||||
if ( false === isset( $ticket_type_ite['id'] ) &&
|
||||
isset( $ticket_type_ite['remove'] ) ) {
|
||||
//ignoring new tickets that didn't go to api yet
|
||||
continue;
|
||||
}
|
||||
$tickets_types[$index++] = $this->_parse_tickets_type_post_to_api_structure(
|
||||
$ticket_type_ite,
|
||||
$event
|
||||
);
|
||||
}
|
||||
}
|
||||
$body['ticket_types'] = $tickets_types;
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the fields of a Ticket Type to the structure used by API
|
||||
*/
|
||||
protected function _parse_tickets_type_post_to_api_structure( $ticket_type_ite, $event ) {
|
||||
$utc_current_time = $this->_registry->get( 'date.time' )->format_to_javascript();
|
||||
if ( isset( $ticket_type_ite['id'] ) ) {
|
||||
$ticket_type['id'] = $ticket_type_ite['id'];
|
||||
$ticket_type['created_at'] = $ticket_type_ite['created_at'];
|
||||
} else {
|
||||
$ticket_type['created_at'] = $utc_current_time;
|
||||
}
|
||||
if ( isset( $ticket_type_ite['remove'] ) ) {
|
||||
$ticket_type['deleted_at'] = $utc_current_time;
|
||||
}
|
||||
$ticket_type['name'] = $ticket_type_ite['ticket_name'];
|
||||
$ticket_type['description'] = $ticket_type_ite['description'];
|
||||
$ticket_type['price'] = $ticket_type_ite['ticket_price'];
|
||||
if ( 0 === strcasecmp( 'on', $ticket_type_ite['unlimited'] ) ) {
|
||||
$ticket_type['quantity'] = null;
|
||||
} else {
|
||||
$ticket_type['quantity'] = $ticket_type_ite['quantity'];
|
||||
}
|
||||
$ticket_type['buy_min_qty'] = $ticket_type_ite['buy_min_limit'];
|
||||
if ( ai1ec_is_blank( $ticket_type_ite['buy_max_limit'] ) ) {
|
||||
$ticket_type['buy_max_qty'] = null;
|
||||
} else {
|
||||
$ticket_type['buy_max_qty'] = $ticket_type_ite['buy_max_limit'];
|
||||
}
|
||||
if ( 0 === strcasecmp( 'on', $ticket_type_ite['availibility'] ) ) {
|
||||
//immediate availability
|
||||
$timezone_start_time = $this->_registry->get( 'date.time' );
|
||||
$timezone_start_time->set_timezone( $event->get('timezone_name') );
|
||||
$ticket_type['immediately'] = true;
|
||||
$ticket_type['sale_start_date'] = $timezone_start_time->format_to_javascript( $event->get('timezone_name') );
|
||||
$ticket_type['sale_end_date'] = $event->get( 'end' )->format_to_javascript();
|
||||
} else {
|
||||
$ticket_type['immediately'] = false;
|
||||
$ticket_type['sale_start_date'] = $ticket_type_ite['ticket_sale_start_date'];
|
||||
$ticket_type['sale_end_date'] = $ticket_type_ite['ticket_sale_end_date'];
|
||||
}
|
||||
$ticket_type['updated_at'] = $utc_current_time;
|
||||
$ticket_type['status'] = $ticket_type_ite['ticket_status'];
|
||||
return $ticket_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unparse the fields of API structure to the Ticket Type
|
||||
*/
|
||||
protected function _unparse_tickets_type_from_api_structure( $ticket_type_api ) {
|
||||
$ticket_type = $ticket_type_api;
|
||||
$ticket_type->ticket_name = $ticket_type_api->name;
|
||||
$ticket_type->ticket_price = $ticket_type_api->price;
|
||||
$ticket_type->buy_min_limit = $ticket_type_api->buy_min_qty;
|
||||
if ( null === $ticket_type_api->buy_max_qty ) {
|
||||
$ticket_type->buy_max_limit = self::MAX_TICKET_TO_BUY_DEFAULT;
|
||||
} else {
|
||||
$ticket_type->buy_max_limit = $ticket_type_api->buy_max_qty;
|
||||
}
|
||||
if ( true === ( ( bool ) $ticket_type_api->immediately ) ) {
|
||||
$ticket_type->availibility = 'on';
|
||||
} else {
|
||||
$ticket_type->availibility = 'off';
|
||||
}
|
||||
$ticket_type->ticket_sale_start_date = $ticket_type_api->sale_start_date; //YYYY-MM-YY HH:NN:SS
|
||||
$ticket_type->ticket_sale_end_date = $ticket_type_api->sale_end_date; //YYYY-MM-YY HH:NN:SS
|
||||
$ticket_type->ticket_status = $ticket_type_api->status;
|
||||
if ( 'open' === $ticket_type_api->status ) {
|
||||
$ticket_type->ticket_status_label = __( 'Open for sale', AI1EC_PLUGIN_NAME );
|
||||
} else if ( 'closed' === $ticket_type_api->status ) {
|
||||
$ticket_type->ticket_status_label = __( 'Sale ended', AI1EC_PLUGIN_NAME );
|
||||
} else if ( 'canceled' === $ticket_type_api->status ) {
|
||||
$ticket_type->ticket_status_label = __( 'Canceled', AI1EC_PLUGIN_NAME );
|
||||
} else {
|
||||
$ticket_type->ticket_status_label = $ticket_type_api->status;
|
||||
}
|
||||
if ( false === isset( $ticket_type_api->quantity ) ||
|
||||
null === $ticket_type_api->quantity ) {
|
||||
$ticket_type->unlimited = 'on';
|
||||
} else {
|
||||
$ticket_type->unlimited = 'off';
|
||||
}
|
||||
$ticket_type->ticket_type_id = $ticket_type_api->id;
|
||||
$ticket_type->available = $ticket_type_api->available;
|
||||
$ticket_type->availability = $this->_parse_availability_message( $ticket_type_api->availability );
|
||||
|
||||
//derived property to set the max quantity of dropdown
|
||||
if ( $ticket_type->available !== null ) {
|
||||
if ( $ticket_type->available > $ticket_type->buy_max_limit ) {
|
||||
$ticket_type->buy_max_available = $ticket_type->buy_max_limit;
|
||||
} else {
|
||||
$ticket_type->buy_max_available = $ticket_type->available;
|
||||
}
|
||||
} else {
|
||||
$ticket_type->buy_max_available = $ticket_type->buy_max_limit;
|
||||
}
|
||||
return $ticket_type;
|
||||
}
|
||||
|
||||
public function _parse_availability_message( $availability ){
|
||||
if ( ai1ec_is_blank ( $availability ) ) {
|
||||
return null;
|
||||
} else {
|
||||
switch ($availability) {
|
||||
case 'past_event':
|
||||
return __( 'Past Event' );
|
||||
case 'event_closed':
|
||||
return __( 'Event closed' );
|
||||
case 'not_available_yet':
|
||||
return __( 'Not available yet' );
|
||||
case 'sale_closed':
|
||||
return __( 'Sale closed' );
|
||||
case 'sold_out':
|
||||
return __( 'Sold out' );
|
||||
default:
|
||||
return __( 'Not available' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function get_event( $post_id ) {
|
||||
$api_event_id = $this->get_api_event_id( $post_id );
|
||||
if ( ! $api_event_id ) {
|
||||
return (object) array( 'data' => array() );
|
||||
}
|
||||
$response = $this->request_api( 'GET', $this->get_api_event_url( $post_id ) . 'events/' . $api_event_id . '/edit' );
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
if ( isset( $response->body->ticket_types ) ) {
|
||||
foreach ( $response->body->ticket_types as $ticket_api ) {
|
||||
$this->_unparse_tickets_type_from_api_structure( $ticket_api );
|
||||
}
|
||||
}
|
||||
return (object) array( 'data' => $response->body );
|
||||
} else {
|
||||
$error_message = $this->_transform_error_message(
|
||||
__( 'We were unable to get the Event Details from Time.ly Ticketing', AI1EC_PLUGIN_NAME ),
|
||||
$response->raw, $response->url,
|
||||
true
|
||||
);
|
||||
return (object) array( 'data' => array(), 'error' => $error_message );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string JSON.
|
||||
*/
|
||||
public function get_ticket_types( $post_id, $get_canceled = true ) {
|
||||
$api_event_id = $this->get_api_event_id( $post_id );
|
||||
if ( ! $api_event_id ) {
|
||||
return json_encode( array( 'data' => array() ) );
|
||||
}
|
||||
$response = $this->request_api( 'GET', $this->get_api_event_url( $post_id ) . 'events/' . $api_event_id . '/ticket_types',
|
||||
array( 'get_canceled' => ( true === $get_canceled ? 1 : 0 ) )
|
||||
);
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
if ( isset( $response->body->ticket_types ) ) {
|
||||
foreach ( $response->body->ticket_types as $ticket_api ) {
|
||||
$this->_unparse_tickets_type_from_api_structure( $ticket_api );
|
||||
}
|
||||
return json_encode( array( 'data' => $response->body->ticket_types ) );
|
||||
} else {
|
||||
return json_encode( array( 'data' => array() ) );
|
||||
}
|
||||
} else {
|
||||
$error_message = $this->_transform_error_message(
|
||||
__( 'We were unable to get the Tickets Details from Time.ly Ticketing', AI1EC_PLUGIN_NAME ),
|
||||
$response->raw, $response->url,
|
||||
true
|
||||
);
|
||||
return json_encode( array( 'data' => array(), 'error' => $error_message ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object Response body in JSON.
|
||||
*/
|
||||
public function get_tickets( $post_id ) {
|
||||
$api_event_id = $this->get_api_event_id( $post_id );
|
||||
if ( ! $api_event_id ) {
|
||||
return json_encode( array( 'data' => array() ) );
|
||||
}
|
||||
$request = array(
|
||||
'headers' => $this->_get_headers(),
|
||||
'timeout' => parent::DEFAULT_TIMEOUT
|
||||
);
|
||||
$url = $this->get_api_event_url( $post_id ) . 'events/' . $api_event_id . '/tickets';
|
||||
$response = wp_remote_get( $url, $request );
|
||||
$response_code = wp_remote_retrieve_response_code( $response );
|
||||
if ( 200 === $response_code ) {
|
||||
return $response['body'];
|
||||
} else {
|
||||
$error_message = $this->_transform_error_message(
|
||||
__( 'We were unable to get the Tickets Attendees from Time.ly Ticketing', AI1EC_PLUGIN_NAME ),
|
||||
$response, $url,
|
||||
true
|
||||
);
|
||||
return json_encode( array( 'data' => array(), 'error' => $error_message ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a Ticket Event was imported from an ICS Feed
|
||||
*/
|
||||
public function is_ticket_event_imported( $post_id ) {
|
||||
$data = $this->get_api_event_data( $post_id );
|
||||
if ( isset( $data[self::ATTR_EVENT_ID] ) && isset( $data[self::ATTR_ICS_API_URL] ) ) {
|
||||
return ( ! ai1ec_is_blank ( $data[self::ATTR_ICS_API_URL] ) );
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Ticket Event was created using a different account
|
||||
* The user probably created the event from one account, signed out and
|
||||
* is currently signed in with a new account
|
||||
*/
|
||||
public function is_ticket_event_from_another_account( $post_id ) {
|
||||
$data = $this->get_api_event_data( $post_id );
|
||||
if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
|
||||
if ( isset( $data[self::ATTR_ACCOUNT] ) ) {
|
||||
return ( $this->get_current_account() != $data[self::ATTR_ACCOUNT] );
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the API account where the event was created
|
||||
* @param int $post_id Post ID
|
||||
* @param bool $default_null True to return NULL if the value does not exist, false to return the configured API URL
|
||||
*/
|
||||
public function get_api_event_account( $post_id ) {
|
||||
$data = $this->get_api_event_data( $post_id );
|
||||
if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
|
||||
if ( isset( $data[self::ATTR_ACCOUNT] ) ) {
|
||||
return $data[self::ATTR_ACCOUNT];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the response that came from the API is the event not found
|
||||
*/
|
||||
private function _is_event_notfound_error( $response ) {
|
||||
if ( isset( $response->response_code ) && 404 === $response->response_code ) {
|
||||
if ( isset( $response->body ) ) {
|
||||
if ( is_array( $response->body ) &&
|
||||
isset( $response->body['message'] ) ) {
|
||||
if ( false !== stripos( $response->body['message'], 'event not found') ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NULL in case of success or an error string in case of error
|
||||
*/
|
||||
public function update_api_event_fields( WP_Post $post, $api_fields_values, $post_action = 'trash', $ajax_action = false ) {
|
||||
$post_id = $post->ID;
|
||||
$api_event_id = $this->get_api_event_id( $post_id );
|
||||
if ( ! $api_event_id ) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
$event = $this->_registry->get( 'model.event', $post_id );
|
||||
} catch ( Ai1ec_Event_Not_Found_Exception $excpt ) {
|
||||
$message = __( 'Event not found inside the database.', AI1EC_PLUGIN_NAME );
|
||||
$notification = $this->_registry->get( 'notification.admin' );
|
||||
$notification->store( $message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
|
||||
return $message;
|
||||
}
|
||||
if ( 'update' === $post_action ) {
|
||||
$error = $this->_prevent_update_ticket_event( $event, $ajax_action );
|
||||
if ( null !== $error ) {
|
||||
return $error;
|
||||
}
|
||||
} else {
|
||||
if ( $this->is_ticket_event_imported( $post_id ) ) {
|
||||
return null;
|
||||
}
|
||||
if ( $this->is_ticket_event_from_another_account( $post_id ) ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
$headers = $this->_get_headers();
|
||||
$body_data = $this->parse_event_fields_to_api_structure(
|
||||
$event,
|
||||
$post,
|
||||
null, //does not update ticket types, just chaging the api fields specified
|
||||
$api_fields_values
|
||||
);
|
||||
$response = $this->request_api( 'POST',
|
||||
AI1EC_API_URL . 'events/' . $api_event_id,
|
||||
$body_data,
|
||||
true //true to decode response body
|
||||
);
|
||||
if ( ! $this->is_response_success( $response ) ) {
|
||||
if ( $this->_is_event_notfound_error( $response ) ) {
|
||||
if ( isset( $api_fields_values['status'] ) &&
|
||||
'trash' === $api_fields_values['status'] ) {
|
||||
//this is an exception, the event was deleted on API server, but for some reason
|
||||
//the metada was not unset, in this case leave the event be
|
||||
//move to trash
|
||||
return null;
|
||||
}
|
||||
}
|
||||
$message = $this->save_error_notification( $response, __( 'We were unable to Update the Event on Time.ly Network', AI1EC_PLUGIN_NAME ) );
|
||||
return $message;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the API event
|
||||
* @return NULL in case of success or an error string in case of error
|
||||
*/
|
||||
public function delete_api_event( $post_id, $post_action = 'delete', $ajax_action = false ) {
|
||||
$api_event_id = $this->get_api_event_id( $post_id );
|
||||
if ( ! $api_event_id ) {
|
||||
return null;
|
||||
}
|
||||
if ( 'update' === $post_action ) {
|
||||
try {
|
||||
$event = $this->_registry->get( 'model.event', $post_id );
|
||||
} catch ( Ai1ec_Event_Not_Found_Exception $excpt ) {
|
||||
$message = __( 'Event not found inside the database.', AI1EC_PLUGIN_NAME );
|
||||
$notification = $this->_registry->get( 'notification.admin' );
|
||||
$notification->store( $message, 'error', 0, array( Ai1ec_Notification_Admin::RCPT_ADMIN ), false );
|
||||
return $message;
|
||||
}
|
||||
$error = $this->_prevent_update_ticket_event( $event, $ajax_action );
|
||||
if ( null !== $error ) {
|
||||
return $error;
|
||||
}
|
||||
} else {
|
||||
if ( $this->is_ticket_event_imported( $post_id ) ) {
|
||||
$this->clear_event_metadata( $post_id );
|
||||
return null;
|
||||
}
|
||||
if ( $this->is_ticket_event_from_another_account( $post_id ) ) {
|
||||
$this->clear_event_metadata( $post_id );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
$response = $this->request_api( 'DELETE',
|
||||
AI1EC_API_URL . 'events/' . $api_event_id,
|
||||
true //true to decode response body
|
||||
);
|
||||
if ( $this->is_response_success( $response ) ) {
|
||||
$this->clear_event_metadata( $post_id );
|
||||
return null;
|
||||
} else {
|
||||
if ( $this->_is_event_notfound_error( $response ) ) {
|
||||
$this->clear_event_metadata( $post_id );
|
||||
return null;
|
||||
}
|
||||
$message = $this->save_error_notification( $response, __( 'We were unable to remove the Event on Time.ly Network', AI1EC_PLUGIN_NAME ) );
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the event metadata used by Event from the post id
|
||||
* @param int $post_id Post ID
|
||||
*/
|
||||
public function clear_event_metadata( $post_id ) {
|
||||
delete_post_meta( $post_id, self::API_EVENT_DATA );
|
||||
}
|
||||
|
||||
public function get_api_event_data( $post_id ) {
|
||||
$data = get_post_meta(
|
||||
$post_id,
|
||||
self::API_EVENT_DATA,
|
||||
true
|
||||
);
|
||||
if ( ai1ec_is_blank ( $data ) ) {
|
||||
return null;
|
||||
} else if ( is_numeric( $data ) ) {
|
||||
//migrate the old metadata into one
|
||||
$new_data[self::ATTR_EVENT_ID] = $data;
|
||||
$value = get_post_meta( $post_id, '_ai1ec_thumbnail_id', true );
|
||||
if ( false === ai1ec_is_blank( $value ) ) {
|
||||
$new_data[self::ATTR_THUMBNAIL_ID] = $value;
|
||||
}
|
||||
$value = get_post_meta( $post_id, '_ai1ec_ics_checkout_url', true );
|
||||
if ( false === ai1ec_is_blank( $value ) ) {
|
||||
$new_data[self::ATTR_ICS_CHECKOUT_URL] = $value;
|
||||
}
|
||||
$value = get_post_meta( $post_id, '_ai1ec_ics_api_url' , true );
|
||||
if ( ai1ec_is_blank( $value ) ) {
|
||||
//not imported ticket event
|
||||
$new_data[self::ATTR_ACCOUNT] = $this->get_current_account();
|
||||
$new_data[self::ATTR_CALENDAR_ID] = $this->get_current_calendar();
|
||||
} else {
|
||||
$new_data[self::ATTR_ICS_API_URL] = $value;
|
||||
}
|
||||
$new_data[self::ATTR_CURRENCY] = 'USD';
|
||||
update_post_meta( $post_id, self::API_EVENT_DATA, $new_data );
|
||||
return $new_data;
|
||||
} else if ( is_array( $data ) ) {
|
||||
return $data;
|
||||
} else {
|
||||
wp_die( 'Error geting the api data' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the event on the API
|
||||
* @param int $post_id Post ID
|
||||
*/
|
||||
public function get_api_event_id( $post_id ) {
|
||||
$data = $this->get_api_event_data( $post_id );
|
||||
if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
|
||||
return $data[self::ATTR_EVENT_ID];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the API URL of the event
|
||||
* @param int $post_id Post ID
|
||||
* @param bool $default_null True to return NULL if the value does not exist, false to return the configured API URL
|
||||
*/
|
||||
public function get_api_event_url ( $post_id ) {
|
||||
$data = $this->get_api_event_data( $post_id );
|
||||
if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
|
||||
if ( isset( $data[self::ATTR_ICS_API_URL] ) ) {
|
||||
return $data[self::ATTR_ICS_API_URL];
|
||||
} else {
|
||||
return AI1EC_API_URL;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Currency of the event
|
||||
* @param int $post_id Post ID
|
||||
*/
|
||||
public function get_api_event_currency ( $post_id ) {
|
||||
$data = $this->get_api_event_data( $post_id );
|
||||
if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
|
||||
if ( isset( $data[self::ATTR_CURRENCY] ) ) {
|
||||
return $data[self::ATTR_CURRENCY];
|
||||
} else {
|
||||
return 'USD';
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Checkout url of the event
|
||||
* @param int $post_id Post ID
|
||||
*/
|
||||
public function get_api_event_checkout_url ( $post_id ) {
|
||||
$data = $this->get_api_event_data( $post_id );
|
||||
if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
|
||||
if ( isset( $data[self::ATTR_ICS_CHECKOUT_URL] ) ) {
|
||||
return $data[self::ATTR_ICS_CHECKOUT_URL];
|
||||
} else {
|
||||
return AI1EC_TICKETS_CHECKOUT_URL;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Buy Ticket URL of the event
|
||||
* @param int $post_id Post ID
|
||||
*/
|
||||
public function get_api_event_buy_ticket_url ( $post_id ) {
|
||||
$data = $this->get_api_event_data( $post_id );
|
||||
if ( isset( $data[self::ATTR_EVENT_ID] ) ) {
|
||||
$api_event_id = $data[self::ATTR_EVENT_ID];
|
||||
if ( isset( $data[self::ATTR_ICS_CHECKOUT_URL] ) ) {
|
||||
$checkout_url = $data[self::ATTR_ICS_CHECKOUT_URL];
|
||||
} else {
|
||||
$checkout_url = AI1EC_TICKETS_CHECKOUT_URL;
|
||||
}
|
||||
return str_replace( '{event_id}', $api_event_id, $checkout_url );
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tax options modal
|
||||
* @param int $event_id Event ID (optional)
|
||||
*/
|
||||
public function get_tax_options_modal( $post_id = null ) {
|
||||
$calendar_id = $this->_get_ticket_calendar();
|
||||
$event_id = $this->get_api_event_id( $post_id );
|
||||
$response = $this->request_api( 'GET',
|
||||
AI1EC_API_URL . 'calendars/' . $calendar_id . '/tax_options' .
|
||||
( is_null( $event_id ) ? '' : '?event_id=' . $event_id )
|
||||
);
|
||||
return (object) array( 'data' => $response->raw, 'error' => false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tax options modal
|
||||
* @param int $event_id Event ID (optional)
|
||||
*/
|
||||
public function get_tax_options_modal_ep() {
|
||||
$calendar_id = $this->_get_ticket_calendar();
|
||||
$response = $this->request_api( 'GET',
|
||||
AI1EC_API_URL . 'eventpromote/' . $calendar_id . '/tax_options'
|
||||
);
|
||||
return (object) array( 'data' => $response->raw, 'error' => false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the API event data
|
||||
* @param int $post_id Post ID
|
||||
* @param int $api_event_id (optional) Id of the event on the API
|
||||
* @param string $ics_api_url (optional) API URL of the event on the API (used when importing an ICS feed)
|
||||
* @param string $ics_checkout_url (optional) API CHECKOUT URL of the event on the API (used when importing an ICS feed)
|
||||
* @param string $currency (optional) Currency code of the event
|
||||
* @param string $thumbnail_id (optional) Id of the Thumbnail (Featured Image id)
|
||||
*/
|
||||
public function save_api_event_data( $post_id, $api_event_id, $ics_api_url = null, $ics_checkout_url = null, $currency = null, $thumbnail_id = null ) {
|
||||
if ( ai1ec_is_blank( $api_event_id ) ) {
|
||||
throw new Error( 'Api event id should never be null' );
|
||||
}
|
||||
$api_data[self::ATTR_EVENT_ID] = $api_event_id;
|
||||
$api_data[self::ATTR_ICS_API_URL] = $ics_api_url;
|
||||
$api_data[self::ATTR_ICS_CHECKOUT_URL] = $ics_checkout_url;
|
||||
$api_data[self::ATTR_CURRENCY] = $currency;
|
||||
$api_data[self::ATTR_THUMBNAIL_ID] = $thumbnail_id;
|
||||
if ( ai1ec_is_blank( $ics_api_url ) ) {
|
||||
$api_data[self::ATTR_ACCOUNT] = $this->get_current_account();
|
||||
$api_data[self::ATTR_CALENDAR_ID] = $this->get_current_calendar();
|
||||
}
|
||||
$previous_data = $this->get_api_event_data( $post_id );
|
||||
$new_data = array();
|
||||
if ( is_array( $previous_data ) ) {
|
||||
foreach ( $previous_data as $key => $value) {
|
||||
$new_data[$key] = $value;
|
||||
}
|
||||
}
|
||||
foreach ( $api_data as $key => $value ) {
|
||||
if ( ai1ec_is_blank( $value ) ) {
|
||||
unset( $new_data[$key] );
|
||||
} else {
|
||||
$new_data[$key] = $api_data[$key];
|
||||
}
|
||||
}
|
||||
return update_post_meta( $post_id, self::API_EVENT_DATA, $new_data, $previous_data );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Base application model.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.0
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
class Ai1ec_App extends Ai1ec_Base {
|
||||
|
||||
/**
|
||||
* Initiate base objects.
|
||||
*
|
||||
* @param Ai1ec_Registry_Object $registry
|
||||
* @internal param \Ai1ec_Registry_Object $system Injectable system object.
|
||||
*/
|
||||
public function __construct( Ai1ec_Registry_Object $registry ) {
|
||||
parent::__construct( $registry );
|
||||
$this->_initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Post construction routine.
|
||||
*
|
||||
* Override this method to perform post-construction tasks.
|
||||
*
|
||||
* @return void Return from this method is ignored.
|
||||
*/
|
||||
protected function _initialize() {}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Model representing an event or an event instance.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.1
|
||||
* @instantiator Ai1ec_Factory_Event.create_event_instance
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
class Ai1ec_Event_Compatibility extends Ai1ec_Event {
|
||||
|
||||
/**
|
||||
* Getter.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
*
|
||||
* @return mixed Property value.
|
||||
*/
|
||||
public function __get( $name ) {
|
||||
$value = $this->get( $name );
|
||||
if ( null !== $value ) {
|
||||
return $value;
|
||||
}
|
||||
return $this->get_runtime( $name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Isset magic function.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
*
|
||||
* @return bool True of false.
|
||||
*/
|
||||
public function __isset( $name ) {
|
||||
$method_name = 'get' . $name;
|
||||
if ( method_exists( $this, $method_name ) ) {
|
||||
return false;
|
||||
}
|
||||
return ( null !== $this->$name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Twig timespan short method.
|
||||
*
|
||||
* @return string Value.
|
||||
*/
|
||||
public function gettimespan_short() {
|
||||
return $this->_registry->get( 'view.event.time' )
|
||||
->get_timespan_html( $this, 'short' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Twig is_allday method.
|
||||
*
|
||||
* @return bool Value.
|
||||
*/
|
||||
public function getis_allday() {
|
||||
return $this->is_allday();
|
||||
}
|
||||
|
||||
/**
|
||||
* Twig is_multiday method.
|
||||
*
|
||||
* @return bool Value.
|
||||
*/
|
||||
public function getis_multiday() {
|
||||
return $this->is_multiday();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Event instance permalink for FER compatibility.
|
||||
*
|
||||
* @return string Event instance permalink.
|
||||
*/
|
||||
public function getpermalink() {
|
||||
return $this->get_runtime( 'instance_permalink' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Event timespan for popup.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getpopup_timespan() {
|
||||
return $this->_registry->get( 'twig.ai1ec-extension' )
|
||||
->timespan( $this, 'short' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Avatar not wrapped in <a> tag.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getavatar_not_wrapped() {
|
||||
return $this->getavatar( false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Event avatar URL.
|
||||
*
|
||||
* @return string Event avatar URL.
|
||||
* @throws Ai1ec_Bootstrap_Exception
|
||||
*/
|
||||
public function getavatar_url() {
|
||||
return $this->_registry->get(
|
||||
'view.event.avatar'
|
||||
)->get_event_avatar_url( $this );
|
||||
}
|
||||
}
|
||||
895
wp-content/plugins/all-in-one-event-calendar/app/model/event.php
Normal file
895
wp-content/plugins/all-in-one-event-calendar/app/model/event.php
Normal file
@@ -0,0 +1,895 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Model representing an event or an event instance.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.0
|
||||
* @instantiator Ai1ec_Factory_Event.create_event_instance
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
class Ai1ec_Event extends Ai1ec_Base {
|
||||
|
||||
/**
|
||||
* @var Ai1ec_Event_Entity Data store object reference.
|
||||
*/
|
||||
protected $_entity = null;
|
||||
|
||||
/**
|
||||
* @var array Map of fields that require special care during set/get
|
||||
* operations. Values have following meanings:
|
||||
* [0] - both way care required;
|
||||
* [1] - only `set` operations require care;
|
||||
* [-1] - only `get` (for storage) operations require care.
|
||||
*/
|
||||
protected $_swizzable = array(
|
||||
'cost' => 0,
|
||||
'start' => -1,
|
||||
'end' => -1,
|
||||
'timezone_name' => -1,
|
||||
'recurrence_dates' => 1,
|
||||
'exception_dates' => 1,
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array Runtime properties
|
||||
*/
|
||||
protected $_runtime_props = array();
|
||||
|
||||
/**
|
||||
* @var bool|null Boolean cache-definition indicating if event is multiday.
|
||||
*/
|
||||
protected $_is_multiday = null;
|
||||
|
||||
/**
|
||||
* Wrapper to get property value.
|
||||
*
|
||||
* @param string $property Name of property to get.
|
||||
* @param mixed $default Default value to return.
|
||||
*
|
||||
* @return mixed Actual property.
|
||||
*/
|
||||
public function get( $property, $default = null ) {
|
||||
return $this->_entity->get( $property, $default );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get properties generated at runtime
|
||||
*
|
||||
* @param string $property
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_runtime( $property, $default = '' ) {
|
||||
return isset( $this->_runtime_props[$property] ) ?
|
||||
$this->_runtime_props[$property] :
|
||||
$default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set properties generated at runtime
|
||||
*
|
||||
* @param string $property
|
||||
* @param string $value
|
||||
*/
|
||||
public function set_runtime( $property, $value ) {
|
||||
$this->_runtime_props[$property] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle property initiation.
|
||||
*
|
||||
* Decides, how to extract value stored in permanent storage.
|
||||
*
|
||||
* @param string $property Name of property to handle
|
||||
* @param mixed $value Value, read from permanent storage
|
||||
*
|
||||
* @return bool Success
|
||||
*/
|
||||
public function set( $property, $value ) {
|
||||
if (
|
||||
isset( $this->_swizzable[$property] ) &&
|
||||
$this->_swizzable[$property] >= 0
|
||||
) {
|
||||
$method = '_handle_property_construct_' . $property;
|
||||
$value = $this->{$method}( $value );
|
||||
}
|
||||
$this->_entity->set( $property, $value );
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the event is all day, during the specified number of days
|
||||
*
|
||||
* @param number $length
|
||||
*/
|
||||
public function set_all_day( $length = 1 ) {
|
||||
// set allday as true
|
||||
$this->set( 'allday', true );
|
||||
$start = $this->get( 'start' );
|
||||
// reset time component
|
||||
$start->set_time( 0, 0, 0 );
|
||||
$end = $this->_registry->get( 'date.time', $start );
|
||||
// set the correct length
|
||||
$end->adjust_day( $length );
|
||||
$this->set( 'end', $end );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the event as if it has no end time
|
||||
*/
|
||||
public function set_no_end_time() {
|
||||
$this->set( 'instant_event', true );
|
||||
$start = $this->get( 'start' );
|
||||
$end = $this->_registry->get( 'date.time', $start );
|
||||
$end->set_time(
|
||||
$start->format( 'H' ),
|
||||
$start->format( 'i' ) + 15,
|
||||
$start->format( 's' )
|
||||
);
|
||||
$this->set( 'end', $end );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set object fields from arbitrary array.
|
||||
*
|
||||
* @param array $data Supposedly map of fields to initiate.
|
||||
*
|
||||
* @return Ai1ec_Event Instance of self for chaining.
|
||||
*/
|
||||
public function initialize_from_array( array $data ) {
|
||||
|
||||
// =======================================================
|
||||
// = Assign each event field the value from the database =
|
||||
// =======================================================
|
||||
foreach ( $this->_entity->list_properties() as $property ) {
|
||||
if ( 'post' !== $property && isset( $data[$property] ) ) {
|
||||
$this->set( $property, $data[$property] );
|
||||
unset( $data[$property] );
|
||||
}
|
||||
}
|
||||
if ( isset( $data['post'] ) ) {
|
||||
$this->set( 'post', (object)$data['post'] );
|
||||
} else {
|
||||
// ========================================
|
||||
// = Remaining fields are the post fields =
|
||||
// ========================================
|
||||
$this->set( 'post', (object)$data );
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the events from all tables
|
||||
*/
|
||||
public function delete() {
|
||||
// delete post (this will trigger deletion of cached events, and
|
||||
// remove the event from events table)
|
||||
wp_delete_post( $this->get( 'post_id' ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize object from ID.
|
||||
*
|
||||
* Attempts to retrieve entity from database and if succeeds - uses
|
||||
* {@see self::initialize_from_array} to initiate actual values.
|
||||
*
|
||||
* @param int $post_id ID of post (event) to initiate.
|
||||
* @param int|bool $instance ID of event instance, false for base event.
|
||||
*
|
||||
* @return Ai1ec_Event Instance of self for chaining.
|
||||
*
|
||||
* @throws Ai1ec_Event_Not_Found_Exception If entity is not locatable.
|
||||
*/
|
||||
public function initialize_from_id( $post_id, $instance = false ) {
|
||||
$post = get_post( $post_id );
|
||||
if ( ! $post || $post->post_status == 'auto-draft' ) {
|
||||
throw new Ai1ec_Event_Not_Found_Exception(
|
||||
'Post with ID \'' . $post_id .
|
||||
'\' could not be retrieved from the database.'
|
||||
);
|
||||
}
|
||||
$post_id = (int)$post_id;
|
||||
$dbi = $this->_registry->get( 'dbi.dbi' );
|
||||
|
||||
$left_join = '';
|
||||
$select_sql = '
|
||||
e.post_id,
|
||||
e.timezone_name,
|
||||
e.recurrence_rules,
|
||||
e.exception_rules,
|
||||
e.allday,
|
||||
e.instant_event,
|
||||
e.recurrence_dates,
|
||||
e.exception_dates,
|
||||
e.venue,
|
||||
e.country,
|
||||
e.address,
|
||||
e.city,
|
||||
e.province,
|
||||
e.postal_code,
|
||||
e.show_map,
|
||||
e.contact_name,
|
||||
e.contact_phone,
|
||||
e.contact_email,
|
||||
e.contact_url,
|
||||
e.cost,
|
||||
e.ticket_url,
|
||||
e.ical_feed_url,
|
||||
e.ical_source_url,
|
||||
e.ical_organizer,
|
||||
e.ical_contact,
|
||||
e.ical_uid,
|
||||
e.longitude,
|
||||
e.latitude,
|
||||
e.show_coordinates,
|
||||
GROUP_CONCAT( ttc.term_id ) AS categories,
|
||||
GROUP_CONCAT( ttt.term_id ) AS tags
|
||||
';
|
||||
|
||||
if (
|
||||
false !== $instance &&
|
||||
is_numeric( $instance ) &&
|
||||
$instance > 0
|
||||
) {
|
||||
$select_sql .= ', IF( aei.start IS NOT NULL, aei.start, e.start ) as start,' .
|
||||
' IF( aei.start IS NOT NULL, aei.end, e.end ) as end ';
|
||||
|
||||
$instance = (int)$instance;
|
||||
$this->set( 'instance_id', $instance );
|
||||
$left_join = 'LEFT JOIN ' . $dbi->get_table_name( 'ai1ec_event_instances' ) .
|
||||
' aei ON aei.id = ' . $instance . ' AND e.post_id = aei.post_id ';
|
||||
} else {
|
||||
$select_sql .= ', e.start as start, e.end as end, e.allday ';
|
||||
if ( -1 === (int)$instance ) {
|
||||
$select_sql .= ', aei.id as instance_id ';
|
||||
$left_join = 'LEFT JOIN ' .
|
||||
$dbi->get_table_name( 'ai1ec_event_instances' ) .
|
||||
' aei ON e.post_id = aei.post_id ' .
|
||||
'AND e.start = aei.start AND e.end = aei.end ';
|
||||
}
|
||||
}
|
||||
|
||||
// =============================
|
||||
// = Fetch event from database =
|
||||
// =============================
|
||||
$query = 'SELECT ' . $select_sql . '
|
||||
FROM ' . $dbi->get_table_name( 'ai1ec_events' ) . ' e
|
||||
LEFT JOIN ' .
|
||||
$dbi->get_table_name( 'term_relationships' ) . ' tr
|
||||
ON ( e.post_id = tr.object_id )
|
||||
LEFT JOIN ' . $dbi->get_table_name( 'term_taxonomy' ) . ' ttc
|
||||
ON (
|
||||
tr.term_taxonomy_id = ttc.term_taxonomy_id AND
|
||||
ttc.taxonomy = \'events_categories\'
|
||||
)
|
||||
LEFT JOIN ' . $dbi->get_table_name( 'term_taxonomy' ) . ' ttt
|
||||
ON (
|
||||
tr.term_taxonomy_id = ttt.term_taxonomy_id AND
|
||||
ttt.taxonomy = \'events_tags\'
|
||||
)
|
||||
' . $left_join . '
|
||||
WHERE e.post_id = ' . $post_id . '
|
||||
GROUP BY e.post_id';
|
||||
|
||||
$event = $dbi->get_row( $query, ARRAY_A );
|
||||
if ( null === $event || null === $event['post_id'] ) {
|
||||
throw new Ai1ec_Event_Not_Found_Exception(
|
||||
'Event with ID \'' . $post_id .
|
||||
'\' could not be retrieved from the database.'
|
||||
);
|
||||
}
|
||||
|
||||
$event['post'] = $post;
|
||||
return $this->initialize_from_array( $event );
|
||||
}
|
||||
|
||||
public function getenddate() {
|
||||
$end = $this->get( 'end' );
|
||||
if ( $this->is_allday() ) {
|
||||
$end->set_time(
|
||||
$end->format( 'H' ),
|
||||
$end->format( 'i' ),
|
||||
$end->format( 's' ) - 1
|
||||
);
|
||||
}
|
||||
return $end;
|
||||
}
|
||||
/**
|
||||
* Returns enddate specific info.
|
||||
*
|
||||
* @return array Date info structure.
|
||||
*/
|
||||
public function getenddate_info() {
|
||||
$end = $this->getenddate();
|
||||
return array(
|
||||
'month' => $this->get( 'end' )->format_i18n( 'M' ),
|
||||
'day' => $this->get( 'end' )->format_i18n( 'j' ),
|
||||
'weekday' => $this->get( 'end' )->format_i18n( 'D' ),
|
||||
'year' => $this->get( 'end' )->format_i18n( 'Y' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new event object, using provided data for initialization.
|
||||
*
|
||||
* @param Ai1ec_Registry_Object $registry Injected object registry.
|
||||
* @param int|array|null $data Look up post with id $data, or
|
||||
* initialize fields with associative
|
||||
* array $data containing both post
|
||||
* and event fields.
|
||||
* @param int|bool $instance Optionally instance ID. When ID
|
||||
* value is -1 then it is
|
||||
* retrieved from db.
|
||||
*
|
||||
* @throws Ai1ec_Invalid_Argument_Exception When $data is not one
|
||||
* of int|array|null.
|
||||
* @throws Ai1ec_Event_Not_Found_Exception When $data relates to
|
||||
* non-existent ID.
|
||||
*
|
||||
*/
|
||||
function __construct(
|
||||
Ai1ec_Registry_Object $registry,
|
||||
$data = null,
|
||||
$instance = false
|
||||
) {
|
||||
parent::__construct( $registry );
|
||||
$this->_entity = $this->_registry->get( 'model.event.entity' );
|
||||
if ( null === $data ) {
|
||||
return; // empty object
|
||||
} else if ( is_numeric( $data ) ) {
|
||||
$this->initialize_from_id( $data, $instance );
|
||||
} else if ( is_array( $data ) ) {
|
||||
$this->initialize_from_array( $data );
|
||||
} else {
|
||||
throw new Ai1ec_Invalid_Argument_Exception(
|
||||
'Argument to constructor must be integer, array or null' .
|
||||
', not ' . var_export( $data, true )
|
||||
);
|
||||
}
|
||||
|
||||
if ( $this->is_allday() ) {
|
||||
try {
|
||||
$timezone = $this->_registry->get( 'date.timezone' )
|
||||
->get( $this->get( 'timezone_name' ) );
|
||||
$this->_entity->set_preferred_timezone( $timezone );
|
||||
} catch ( Exception $excpt ) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Twig method for retrieving avatar.
|
||||
*
|
||||
* @param bool $wrap_permalink Whether to wrap avatar in <a> element or not
|
||||
*
|
||||
* @return string Avatar markup
|
||||
*/
|
||||
public function getavatar( $wrap_permalink = true ) {
|
||||
return $this->_registry->
|
||||
get( 'view.event.avatar' )->get_event_avatar(
|
||||
$this,
|
||||
$this->_registry->get( 'view.calendar.fallbacks' )->get_all(),
|
||||
'',
|
||||
$wrap_permalink
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether Event has geo information.
|
||||
*
|
||||
* @return bool True or false.
|
||||
*/
|
||||
public function has_geoinformation() {
|
||||
$latitude = floatval( $this->get( 'latitude') );
|
||||
$longitude = floatval( $this->get( 'longitude' ) );
|
||||
return (
|
||||
(
|
||||
$latitude >= 0.000000000000001 ||
|
||||
$latitude <= -0.000000000000001
|
||||
) &&
|
||||
(
|
||||
$longitude >= 0.000000000000001 ||
|
||||
$longitude <= -0.000000000000001
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected function _handle_property_construct_recurrence_dates( $value ) {
|
||||
if ( $value ) {
|
||||
$this->_entity->set( 'recurrence_rules', 'RDATE=' . $value );
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function _handle_property_construct_exception_dates( $value ) {
|
||||
if ( $value ) {
|
||||
$this->_entity->set( 'exception_rules', 'EXDATE=' . $value );
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle `cost` value reading from permanent storage.
|
||||
*
|
||||
* @param string $value Value stored in permanent storage
|
||||
*
|
||||
* @return bool Success: true, always
|
||||
*/
|
||||
protected function _handle_property_construct_cost( $value ) {
|
||||
$test_value = false;
|
||||
if (
|
||||
isset( $value{1} ) && (
|
||||
':' === $value{1} || ';' === $value{1}
|
||||
)
|
||||
) {
|
||||
$test_value = unserialize( $value );
|
||||
}
|
||||
$cost = $is_free = NULL;
|
||||
if ( false === $test_value ) {
|
||||
$cost = trim( $value );
|
||||
$is_free = false;
|
||||
} else {
|
||||
extract( $test_value, EXTR_IF_EXISTS );
|
||||
}
|
||||
$this->_entity->set( 'is_free', (bool)$is_free );
|
||||
return (string)$cost;
|
||||
}
|
||||
|
||||
public function get_uid_pattern() {
|
||||
static $format = null;
|
||||
if ( null === $format ) {
|
||||
$site_url = parse_url( ai1ec_get_site_url() );
|
||||
$format = 'ai1ec-%d@' . $site_url['host'];
|
||||
if ( isset( $site_url['path'] ) ) {
|
||||
$format .= $site_url['path'];
|
||||
}
|
||||
}
|
||||
return $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get UID to be used for current event.
|
||||
*
|
||||
* The generated format is cached in static variable within this function
|
||||
* to re-use when generating UIDs for different entries.
|
||||
*
|
||||
* @return string Generated UID.
|
||||
*
|
||||
* @staticvar string $format Cached format.
|
||||
*/
|
||||
public function get_uid() {
|
||||
$ical_uid = $this->get( 'ical_uid' );
|
||||
if ( ! empty( $ical_uid ) ) {
|
||||
return $ical_uid;
|
||||
}
|
||||
return sprintf( $this->get_uid_pattern(), $this->get( 'post_id' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if event is free.
|
||||
*
|
||||
* @return bool Free status.
|
||||
*/
|
||||
public function is_free() {
|
||||
return (bool)$this->get( 'is_free' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if event is taking all day.
|
||||
*
|
||||
* @return bool True for all-day long events.
|
||||
*/
|
||||
public function is_allday() {
|
||||
return (bool)$this->get( 'allday' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if event has virtually no time.
|
||||
*
|
||||
* @return bool True for instant events.
|
||||
*/
|
||||
public function is_instant() {
|
||||
return (bool)$this->get( 'instant_event' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if event is taking multiple days.
|
||||
*
|
||||
* Uses object-wide variable {@see self::$_is_multiday} to store
|
||||
* calculated value after first call.
|
||||
*
|
||||
* @return bool True for multiday events.
|
||||
*/
|
||||
public function is_multiday() {
|
||||
if ( null === $this->_is_multiday ) {
|
||||
$start = $this->get( 'start' );
|
||||
$end = $this->get( 'end' );
|
||||
$diff = $end->diff_sec( $start );
|
||||
$this->_is_multiday = $diff > 86400 &&
|
||||
$start->format( 'Y-m-d' ) !== $end->format( 'Y-m-d' );
|
||||
}
|
||||
return $this->_is_multiday;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the duration of the event
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
public function get_duration() {
|
||||
$duration = $this->get_runtime( 'duration', null );
|
||||
if ( null === $duration ) {
|
||||
$duration = $this->get( 'end' )->format() -
|
||||
$this->get( 'start' )->format();
|
||||
$this->set_runtime( 'duration', $duration );
|
||||
}
|
||||
return $duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create/update entity representation.
|
||||
*
|
||||
* Saves the current event data to the database. If $this->post_id exists,
|
||||
* but $update is false, creates a new record in the ai1ec_events table of
|
||||
* this event data, but does not try to create a new post. Else if $update
|
||||
* is true, updates existing event record. If $this->post_id is empty,
|
||||
* creates a new post AND record in the ai1ec_events table for this event.
|
||||
*
|
||||
* @param bool $update Whether to update an existing event or create a
|
||||
* new one
|
||||
* @param bool $backward_compatibility The (wpdb) ofr the new wordpress 4.4
|
||||
* now inserts NULL as null values. The previous version, if you insert a NULL
|
||||
* value in an int value, the values saved would be 0 instead of null.
|
||||
* @return int The post_id of the new or existing event.
|
||||
*/
|
||||
function save( $update = false, $backward_compatibility = true ) {
|
||||
do_action( 'ai1ec_pre_save_event', $this, $update );
|
||||
if ( ! $update ) {
|
||||
$response = apply_filters( 'ai1ec_event_save_new', $this );
|
||||
if ( is_wp_error( $response ) ) {
|
||||
throw new Ai1ec_Event_Create_Exception(
|
||||
'Failed to create event: ' . $response->get_error_message()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$dbi = $this->_registry->get( 'dbi.dbi' );
|
||||
$columns = $this->prepare_store_entity();
|
||||
$format = $this->prepare_store_format( $columns, $backward_compatibility );
|
||||
$table_name = $dbi->get_table_name( 'ai1ec_events' );
|
||||
$post_id = $columns['post_id'];
|
||||
|
||||
if ( $this->get( 'end' )->is_empty() ) {
|
||||
$this->set_no_end_time();
|
||||
}
|
||||
if ( $post_id ) {
|
||||
$success = false;
|
||||
if ( ! $update ) {
|
||||
$success = $dbi->insert(
|
||||
$table_name,
|
||||
$columns,
|
||||
$format
|
||||
);
|
||||
} else {
|
||||
$success = $dbi->update(
|
||||
$table_name,
|
||||
$columns,
|
||||
array( 'post_id' => $columns['post_id'] ),
|
||||
$format,
|
||||
array( '%d' )
|
||||
);
|
||||
}
|
||||
if ( false === $success ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// ===================
|
||||
// = Insert new post =
|
||||
// ===================
|
||||
$post_id = wp_insert_post( $this->get( 'post' ), false );
|
||||
if ( 0 === $post_id ) {
|
||||
return false;
|
||||
}
|
||||
$this->set( 'post_id', $post_id );
|
||||
$columns['post_id'] = $post_id;
|
||||
|
||||
// =========================
|
||||
// = Insert new event data =
|
||||
// =========================
|
||||
if ( false === $dbi->insert( $table_name, $columns, $format ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$taxonomy = $this->_registry->get(
|
||||
'model.event.taxonomy',
|
||||
$post_id
|
||||
);
|
||||
$cats = $this->get( 'categories' );
|
||||
if (
|
||||
is_array( $cats ) &&
|
||||
! empty( $cats )
|
||||
) {
|
||||
$taxonomy->set_categories( $cats );
|
||||
}
|
||||
$tags = $this->get( 'tags' );
|
||||
if (
|
||||
is_array( $tags ) &&
|
||||
! empty( $tags )
|
||||
) {
|
||||
$taxonomy->set_tags( $tags );
|
||||
}
|
||||
|
||||
if (
|
||||
$feed = $this->get( 'feed' ) &&
|
||||
isset( $feed->feed_id )
|
||||
) {
|
||||
$taxonomy->set_feed( $feed );
|
||||
}
|
||||
|
||||
// give other plugins / extensions the ability to do things
|
||||
// when saving, like fetching authors which i removed as it's not core.
|
||||
do_action( 'ai1ec_save_event' );
|
||||
|
||||
$instance_model = $this->_registry->get( 'model.event.instance' );
|
||||
$instance_model->recreate( $this );
|
||||
|
||||
do_action( 'ai1ec_event_saved', $post_id, $this, $update );
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare fields format flags to use in database operations.
|
||||
*
|
||||
* @param array $columns Array of columns with data to insert.
|
||||
*
|
||||
* @return array List of format flags to use in integrations with DBI.
|
||||
*/
|
||||
public function prepare_store_format( array &$columns, $backward_compatibility = true ) {
|
||||
$format = array(
|
||||
'%d', // post_id
|
||||
'%d', // start
|
||||
'%d', // end
|
||||
'%s', // timezone_name
|
||||
'%d', // allday
|
||||
'%d', // instant_event
|
||||
'%s', // recurrence_rules
|
||||
'%s', // exception_rules
|
||||
'%s', // recurrence_dates
|
||||
'%s', // exception_dates
|
||||
'%s', // venue
|
||||
'%s', // country
|
||||
'%s', // address
|
||||
'%s', // city
|
||||
'%s', // province
|
||||
'%s', // postal_code
|
||||
'%d', // show_map
|
||||
'%s', // contact_name
|
||||
'%s', // contact_phone
|
||||
'%s', // contact_email
|
||||
'%s', // contact_url
|
||||
'%s', // cost
|
||||
'%s', // ticket_url
|
||||
'%s', // ical_feed_url
|
||||
'%s', // ical_source_url
|
||||
'%s', // ical_uid
|
||||
'%d', // show_coordinates
|
||||
'%f', // latitude
|
||||
'%f', // longitude
|
||||
);
|
||||
|
||||
if ( $backward_compatibility ) {
|
||||
$columns_count = count( $columns );
|
||||
if ( count( $format ) !== $columns_count ) {
|
||||
throw new Ai1ec_Event_Not_Found_Exception(
|
||||
'Data columns count differs from format columns count'
|
||||
);
|
||||
}
|
||||
$index = 0;
|
||||
foreach ( $columns as $key => $value ) {
|
||||
if ( '%d' === $format[ $index ] ) {
|
||||
if ( is_null( $value ) ) {
|
||||
$columns[ $key ] = 0;
|
||||
}
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
|
||||
return $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare event entity {@see self::$_entity} for persistent storage.
|
||||
*
|
||||
* Creates an array of database fields and corresponding values.
|
||||
*
|
||||
* @return array Map of fields to store.
|
||||
*/
|
||||
public function prepare_store_entity() {
|
||||
$entity = array(
|
||||
'post_id' => $this->storage_format( 'post_id' ),
|
||||
'start' => $this->storage_format( 'start' ),
|
||||
'end' => $this->storage_format( 'end' ),
|
||||
'timezone_name' => $this->storage_format( 'timezone_name' ),
|
||||
'allday' => $this->storage_format( 'allday' ),
|
||||
'instant_event' => $this->storage_format( 'instant_event' ),
|
||||
'recurrence_rules' => $this->storage_format( 'recurrence_rules' ),
|
||||
'exception_rules' => $this->storage_format( 'exception_rules' ),
|
||||
'recurrence_dates' => $this->storage_format( 'recurrence_dates' ),
|
||||
'exception_dates' => $this->storage_format( 'exception_dates' ),
|
||||
'venue' => $this->storage_format( 'venue' ),
|
||||
'country' => $this->storage_format( 'country' ),
|
||||
'address' => $this->storage_format( 'address' ),
|
||||
'city' => $this->storage_format( 'city' ),
|
||||
'province' => $this->storage_format( 'province' ),
|
||||
'postal_code' => $this->storage_format( 'postal_code' ),
|
||||
'show_map' => $this->storage_format( 'show_map' ),
|
||||
'contact_name' => $this->storage_format( 'contact_name' ),
|
||||
'contact_phone' => $this->storage_format( 'contact_phone' ),
|
||||
'contact_email' => $this->storage_format( 'contact_email' ),
|
||||
'contact_url' => $this->storage_format( 'contact_url' ),
|
||||
'cost' => $this->storage_format( 'cost' ),
|
||||
'ticket_url' => $this->storage_format( 'ticket_url' ),
|
||||
'ical_feed_url' => $this->storage_format( 'ical_feed_url' ),
|
||||
'ical_source_url' => $this->storage_format( 'ical_source_url' ),
|
||||
'ical_uid' => $this->storage_format( 'ical_uid' ),
|
||||
'show_coordinates' => $this->storage_format( 'show_coordinates' ),
|
||||
'latitude' => $this->storage_format( 'latitude', '' ),
|
||||
'longitude' => $this->storage_format( 'longitude', '' ),
|
||||
);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact field for writing to persistent storage.
|
||||
*
|
||||
* @param string $field Name of field to compact.
|
||||
* @param mixed $default Default value to use for undescribed fields.
|
||||
*
|
||||
* @return mixed Value or $default.
|
||||
*/
|
||||
public function storage_format( $field, $default = null ) {
|
||||
$value = $this->_entity->get( $field, $default );
|
||||
if (
|
||||
isset( $this->_swizzable[$field] ) &&
|
||||
$this->_swizzable[$field] <= 0
|
||||
) {
|
||||
$value = $this->{ '_handle_property_destruct_' . $field }( $value );
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow properties to be modified after cloning.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone() {
|
||||
$this->_entity = clone $this->_entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode timezone to use for event.
|
||||
*
|
||||
* Following algorythm is used to detect a value:
|
||||
* - take value provided in input;
|
||||
* - if empty - take value associated with start time;
|
||||
* - if empty - take current environment timezone.
|
||||
*
|
||||
* @param string $timezone_name Timezone provided in input.
|
||||
*
|
||||
* @return string Timezone name to use for event in future.
|
||||
*/
|
||||
protected function _handle_property_destruct_timezone_name(
|
||||
$timezone_name
|
||||
) {
|
||||
if ( empty( $timezone_name ) ) {
|
||||
$timezone_name = $this->get( 'start' )->get_timezone();
|
||||
if ( empty( $timezone_name ) ) {
|
||||
$timezone_name = $this->_registry->get( 'date.timezone' )
|
||||
->get_default_timezone();
|
||||
}
|
||||
}
|
||||
return $timezone_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format datetime to UNIX timestamp for storage.
|
||||
*
|
||||
* @param Ai1ec_Date_Time $start Datetime object to compact.
|
||||
*
|
||||
* @return int UNIX timestamp.
|
||||
*/
|
||||
protected function _handle_property_destruct_start( Ai1ec_Date_Time $start ) {
|
||||
return $start->format_to_gmt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format datetime to UNIX timestamp for storage.
|
||||
*
|
||||
* @param Ai1ec_Date_Time $end Datetime object to compact.
|
||||
*
|
||||
* @return int UNIX timestamp.
|
||||
*/
|
||||
protected function _handle_property_destruct_end( Ai1ec_Date_Time $end ) {
|
||||
return $end->format_to_gmt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle `cost` writing to permanent storage.
|
||||
*
|
||||
* @param string $cost Value of cost.
|
||||
*
|
||||
* @return string Serialized value to store.
|
||||
*/
|
||||
protected function _handle_property_destruct_cost( $cost ) {
|
||||
$cost = array(
|
||||
'cost' => $cost,
|
||||
'is_free' => false,
|
||||
);
|
||||
if ( $this->get( 'is_free' ) ) {
|
||||
$cost['is_free'] = true;
|
||||
}
|
||||
return serialize( $cost );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the submitter information array
|
||||
* @return array (
|
||||
* is_organizer => 1 if the organizer is the submitter,
|
||||
* email => if is_organizer is 0, them this property has the email of the submitter,
|
||||
* name => if is_organizer is 0, them this property has the name of the submitter
|
||||
* )
|
||||
*/
|
||||
public function get_submitter_info() {
|
||||
$post_id = $this->get( 'post_id' );
|
||||
if ( empty( $post_id ) ) {
|
||||
return null;
|
||||
}
|
||||
$submitter_info = get_post_meta(
|
||||
$post_id,
|
||||
'_submitter_info',
|
||||
true
|
||||
);
|
||||
if ( false == ai1ec_is_blank( $submitter_info ) ) {
|
||||
$submitter_info = json_decode( $submitter_info, true );
|
||||
if ( is_array( $submitter_info ) ) {
|
||||
return $submitter_info;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the submitter information into post metadata
|
||||
*/
|
||||
public function save_submitter_info( $is_submitter, $submitter_email, $submitter_name ) {
|
||||
$post_id = $this->get( 'post_id' );
|
||||
if ( empty( $post_id ) ) {
|
||||
throw new Exception( 'Post id empty' );
|
||||
}
|
||||
$save = false;
|
||||
if ( 1 === intval( $is_submitter ) ) {
|
||||
$submitter_info['is_organizer'] = 1;
|
||||
if ( false === ai1ec_is_blank( $this->get( 'contact_email' ) ) ) {
|
||||
$save = true;
|
||||
}
|
||||
} else {
|
||||
$submitter_info['is_organizer'] = 0;
|
||||
if ( false === ai1ec_is_blank( $submitter_email ) ) {
|
||||
$submitter_info['email'] = trim( $submitter_email );
|
||||
$submitter_info['name'] = trim( $submitter_name );
|
||||
$save = true;
|
||||
}
|
||||
}
|
||||
if ( $save ) {
|
||||
update_post_meta( $post_id, '_submitter_info', json_encode( $submitter_info ) );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Handles create/update operations.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
*
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Model
|
||||
*/
|
||||
class Ai1ec_Event_Creating extends Ai1ec_Base {
|
||||
|
||||
protected function is_valid_event( $post ) {
|
||||
// verify this came from the our screen and with proper authorization,
|
||||
// because save_post can be triggered at other times
|
||||
if (
|
||||
! isset( $_POST[AI1EC_POST_TYPE] ) ||
|
||||
! wp_verify_nonce( $_POST[AI1EC_POST_TYPE], 'ai1ec' )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
isset( $post->post_status ) &&
|
||||
'auto-draft' === $post->post_status
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// verify if this is not inline-editing
|
||||
if (
|
||||
isset( $_REQUEST['action'] ) &&
|
||||
'inline-save' === $_REQUEST['action']
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// verify that the post_type is that of an event
|
||||
if ( $post->post_type !== AI1EC_POST_TYPE ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function _parse_post_to_event( $post_id ) {
|
||||
|
||||
/**
|
||||
* =====================================================================
|
||||
*
|
||||
* CHANGE CODE BELOW TO HAVE FOLLOWING PROPERTIES:
|
||||
* - be initializiable from model;
|
||||
* - have sane defaults;
|
||||
* - avoid that cluster of isset and ternary operator.
|
||||
*
|
||||
* =====================================================================
|
||||
*/
|
||||
|
||||
$all_day = isset( $_POST['ai1ec_all_day_event'] ) ? 1 : 0;
|
||||
$instant_event = isset( $_POST['ai1ec_instant_event'] ) ? 1 : 0;
|
||||
$timezone_name = isset( $_POST['ai1ec_timezone_name'] ) ? sanitize_text_field( $_POST['ai1ec_timezone_name'] ) : 'sys.default';
|
||||
$start_time = isset( $_POST['ai1ec_start_time'] ) ? sanitize_text_field( $_POST['ai1ec_start_time'] ) : '';
|
||||
$end_time = isset( $_POST['ai1ec_end_time'] ) ? sanitize_text_field( $_POST['ai1ec_end_time'] ) : '';
|
||||
$venue = isset( $_POST['ai1ec_venue'] ) ? sanitize_text_field( $_POST['ai1ec_venue'] ) : '';
|
||||
$address = isset( $_POST['ai1ec_address'] ) ? sanitize_text_field( $_POST['ai1ec_address'] ) : '';
|
||||
$city = isset( $_POST['ai1ec_city'] ) ? sanitize_text_field( $_POST['ai1ec_city'] ) : '';
|
||||
$province = isset( $_POST['ai1ec_province'] ) ? sanitize_text_field( $_POST['ai1ec_province'] ) : '';
|
||||
$postal_code = isset( $_POST['ai1ec_postal_code'] ) ? sanitize_text_field( $_POST['ai1ec_postal_code'] ) : '';
|
||||
$country = isset( $_POST['ai1ec_country'] ) ? sanitize_text_field( $_POST['ai1ec_country'] ) : '';
|
||||
$google_map = isset( $_POST['ai1ec_google_map'] ) ? 1 : 0;
|
||||
$cost = isset( $_POST['ai1ec_cost'] ) ? sanitize_text_field( $_POST['ai1ec_cost'] ) : '';
|
||||
$is_free = isset( $_POST['ai1ec_is_free'] ) ? (bool)$_POST['ai1ec_is_free'] : false;
|
||||
$ticket_url = isset( $_POST['ai1ec_ticket_url'] ) ? sanitize_text_field( $_POST['ai1ec_ticket_url'] ) : '';
|
||||
$contact_name = isset( $_POST['ai1ec_contact_name'] ) ? sanitize_text_field( $_POST['ai1ec_contact_name'] ) : '';
|
||||
$contact_phone = isset( $_POST['ai1ec_contact_phone'] ) ? sanitize_text_field( $_POST['ai1ec_contact_phone'] ) : '';
|
||||
$contact_email = isset( $_POST['ai1ec_contact_email'] ) ? sanitize_text_field( $_POST['ai1ec_contact_email'] ) : '';
|
||||
$contact_url = isset( $_POST['ai1ec_contact_url'] ) ? sanitize_text_field( $_POST['ai1ec_contact_url'] ) : '';
|
||||
$show_coordinates = isset( $_POST['ai1ec_input_coordinates'] )? 1 : 0;
|
||||
$longitude = isset( $_POST['ai1ec_longitude'] ) ? sanitize_text_field( $_POST['ai1ec_longitude'] ) : '';
|
||||
$latitude = isset( $_POST['ai1ec_latitude'] ) ? sanitize_text_field( $_POST['ai1ec_latitude'] ) : '';
|
||||
$cost_type = isset( $_POST['ai1ec_cost_type'] ) ? sanitize_text_field( $_POST['ai1ec_cost_type'] ) : '';
|
||||
$rrule = null;
|
||||
$exrule = null;
|
||||
$exdate = null;
|
||||
$rdate = null;
|
||||
|
||||
if ( 'external' !== $cost_type ) {
|
||||
$ticket_url = '';
|
||||
}
|
||||
|
||||
$this->_remap_recurrence_dates();
|
||||
// if rrule is set, convert it from local to UTC time
|
||||
if (
|
||||
isset( $_POST['ai1ec_repeat'] ) &&
|
||||
! empty( $_POST['ai1ec_repeat'] )
|
||||
) {
|
||||
$rrule = $_POST['ai1ec_rrule'];
|
||||
}
|
||||
|
||||
// add manual dates
|
||||
if (
|
||||
isset( $_POST['ai1ec_exdate'] ) &&
|
||||
! empty( $_POST['ai1ec_exdate'] )
|
||||
) {
|
||||
$exdate = $_POST['ai1ec_exdate'];
|
||||
}
|
||||
if (
|
||||
isset( $_POST['ai1ec_rdate'] ) &&
|
||||
! empty( $_POST['ai1ec_rdate'] )
|
||||
) {
|
||||
$rdate = $_POST['ai1ec_rdate'];
|
||||
}
|
||||
|
||||
// if exrule is set, convert it from local to UTC time
|
||||
if (
|
||||
isset( $_POST['ai1ec_exclude'] ) &&
|
||||
! empty( $_POST['ai1ec_exclude'] ) &&
|
||||
( null !== $rrule || null !== $rdate ) // no point for exclusion, if repetition is not set
|
||||
) {
|
||||
$exrule = $this->_registry->get( 'recurrence.rule' )->merge_exrule(
|
||||
$_POST['ai1ec_exrule'],
|
||||
$rrule
|
||||
);
|
||||
}
|
||||
|
||||
$is_new = false;
|
||||
try {
|
||||
$event = $this->_registry->get(
|
||||
'model.event',
|
||||
$post_id ? $post_id : null
|
||||
);
|
||||
} catch ( Ai1ec_Event_Not_Found_Exception $excpt ) {
|
||||
// Post exists, but event data hasn't been saved yet. Create new event
|
||||
// object.
|
||||
$is_new = true;
|
||||
$event = $this->_registry->get( 'model.event' );
|
||||
}
|
||||
$formatted_timezone = $this->_registry->get( 'date.timezone' )
|
||||
->get_name( $timezone_name );
|
||||
if ( empty( $timezone_name ) || ! $formatted_timezone ) {
|
||||
$timezone_name = 'sys.default';
|
||||
}
|
||||
|
||||
unset( $formatted_timezone );
|
||||
$start_time_entry = $this->_registry
|
||||
->get( 'date.time', $start_time, $timezone_name );
|
||||
$end_time_entry = $this->_registry
|
||||
->get( 'date.time', $end_time, $timezone_name );
|
||||
|
||||
$timezone_name = $start_time_entry->get_timezone();
|
||||
if ( null === $timezone_name ) {
|
||||
$timezone_name = $start_time_entry->get_default_format_timezone();
|
||||
}
|
||||
|
||||
$event->set( 'post_id', $post_id );
|
||||
$event->set( 'start', $start_time_entry );
|
||||
if ( $instant_event ) {
|
||||
$event->set_no_end_time();
|
||||
} else {
|
||||
$event->set( 'end', $end_time_entry );
|
||||
$event->set( 'instant_event', false );
|
||||
}
|
||||
$event->set( 'timezone_name', $timezone_name );
|
||||
$event->set( 'allday', $all_day );
|
||||
$event->set( 'venue', $venue );
|
||||
$event->set( 'address', $address );
|
||||
$event->set( 'city', $city );
|
||||
$event->set( 'province', $province );
|
||||
$event->set( 'postal_code', $postal_code );
|
||||
$event->set( 'country', $country );
|
||||
$event->set( 'show_map', $google_map );
|
||||
$event->set( 'cost', $cost );
|
||||
$event->set( 'is_free', $is_free );
|
||||
$event->set( 'ticket_url', $ticket_url );
|
||||
$event->set( 'contact_name', $contact_name );
|
||||
$event->set( 'contact_phone', $contact_phone );
|
||||
$event->set( 'contact_email', $contact_email );
|
||||
$event->set( 'contact_url', $contact_url );
|
||||
$event->set( 'recurrence_rules', $rrule );
|
||||
$event->set( 'exception_rules', $exrule );
|
||||
$event->set( 'exception_dates', $exdate );
|
||||
$event->set( 'recurrence_dates', $rdate );
|
||||
$event->set( 'show_coordinates', $show_coordinates );
|
||||
$event->set( 'longitude', trim( $longitude ) );
|
||||
$event->set( 'latitude', trim( $latitude ) );
|
||||
$event->set( 'ical_uid', $event->get_uid() );
|
||||
|
||||
return array(
|
||||
'event' => $event,
|
||||
'is_new' => $is_new
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves meta post data.
|
||||
*
|
||||
* @wp_hook save_post
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param object $post Post object.
|
||||
* @param update
|
||||
*
|
||||
* @return object|null Saved Ai1ec_Event object if successful or null.
|
||||
*/
|
||||
public function save_post( $post_id, $post, $update ) {
|
||||
|
||||
if ( false === $this->is_valid_event( $post ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// LABEL:magicquotes
|
||||
// remove WordPress `magical` slashes - we work around it ourselves
|
||||
$_POST = stripslashes_deep( $_POST );
|
||||
|
||||
$data = $this->_parse_post_to_event( $post_id );
|
||||
if ( ! $data ) {
|
||||
return null;
|
||||
}
|
||||
$event = $data['event'];
|
||||
$is_new = $data['is_new'];
|
||||
|
||||
$banner_image = isset( $_POST['ai1ec_banner_image'] ) ? sanitize_text_field( $_POST['ai1ec_banner_image'] ) : '';
|
||||
$cost_type = isset( $_POST['ai1ec_cost_type'] ) ? sanitize_text_field( $_POST['ai1ec_cost_type'] ) : '';
|
||||
|
||||
update_post_meta( $post_id, 'ai1ec_banner_image', $banner_image );
|
||||
if ( $cost_type ) {
|
||||
update_post_meta( $post_id, '_ai1ec_cost_type', $cost_type );
|
||||
}
|
||||
$api = $this->_registry->get( 'model.api.api-ticketing' );
|
||||
if ( $update === false ) {
|
||||
//this method just creates the API event, the update action
|
||||
//is treated by another hook (pre_update_event inside api )
|
||||
if ( 'tickets' === $cost_type ) {
|
||||
$result = $api->store_event( $event, $post, false );
|
||||
if ( true !== $result ) {
|
||||
$_POST['_ticket_store_event_error'] = $result;
|
||||
} else {
|
||||
update_post_meta(
|
||||
$post_id,
|
||||
'_ai1ec_timely_tickets_url',
|
||||
$api->get_api_event_buy_ticket_url( $event->get( 'post_id' ) )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( 'tickets' === $cost_type ) {
|
||||
update_post_meta(
|
||||
$post_id,
|
||||
'_ai1ec_timely_tickets_url',
|
||||
$api->get_api_event_buy_ticket_url( $event->get( 'post_id' ) )
|
||||
);
|
||||
} else {
|
||||
delete_post_meta(
|
||||
$post_id,
|
||||
'_ai1ec_timely_tickets_url'
|
||||
);
|
||||
}
|
||||
|
||||
// let other extensions save their fields.
|
||||
do_action( 'ai1ec_save_post', $event );
|
||||
|
||||
$event->save( ! $is_new );
|
||||
|
||||
// LABEL:magicquotes
|
||||
// restore `magic` WordPress quotes to maintain compatibility
|
||||
$_POST = add_magic_quotes( $_POST );
|
||||
|
||||
$api = $this->_registry->get( 'model.api.api-registration' );
|
||||
$api->check_settings();
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
private function get_sendback_page( $post_id ) {
|
||||
$sendback = wp_get_referer();
|
||||
$page_base = Ai1ec_Wp_Uri_Helper::get_pagebase( $sendback ); //$_SERVER['REQUEST_URI'] );
|
||||
if ( 'post.php' === $page_base ) {
|
||||
return get_edit_post_link( $post_id, 'url' );
|
||||
} else {
|
||||
return admin_url( 'edit.php?post_type=ai1ec_event' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle PRE (ticket event) update.
|
||||
* Just handle the Ticket Events, other kind of post are ignored
|
||||
* @wp_hook pre_post_update
|
||||
*
|
||||
*/
|
||||
public function pre_post_update ( $post_id, $new_post_data ) {
|
||||
|
||||
// LABEL:magicquotes
|
||||
// remove WordPress `magical` slashes - we work around it ourselves
|
||||
$_POST = stripslashes_deep( $_POST );
|
||||
|
||||
$api = $this->_registry->get( 'model.api.api-ticketing' );
|
||||
$action = $this->current_action();
|
||||
switch( $action ) {
|
||||
case 'inline-save': //quick edit from edit page
|
||||
$fields = array();
|
||||
if ( false === ai1ec_is_blank( $_REQUEST['post_title'] ) ) {
|
||||
$fields['title'] = sanitize_text_field( $_REQUEST['post_title'] );
|
||||
}
|
||||
if ( false === ai1ec_is_blank( $_REQUEST['_status'] ) ) {
|
||||
$fields['status'] = $_REQUEST['_status'];
|
||||
}
|
||||
if ( isset( $_REQUEST['keep_private'] ) && 'private' === $_REQUEST['keep_private'] ) {
|
||||
$fields['visibility'] = 'private';
|
||||
} else if ( isset( $_REQUEST['post_password'] ) && false === ai1ec_is_blank( $_REQUEST['post_password'] ) ) {
|
||||
$fields['visibility'] = 'password';
|
||||
}
|
||||
if ( 0 < count( $fields ) ) {
|
||||
$post = get_post( $post_id );
|
||||
$ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
|
||||
$message = $api->update_api_event_fields( $post, $fields, 'update', $ajax );
|
||||
if ( null !== $message ) {
|
||||
if ( $ajax ) {
|
||||
wp_die( $message );
|
||||
} else {
|
||||
wp_redirect( $this->get_sendback_page( $post_id ) );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
case 'edit': //bulk edition from edit page
|
||||
$fields = array();
|
||||
if ( false === ai1ec_is_blank( $_REQUEST['_status'] ) ) {
|
||||
$fields['status'] = $_REQUEST['_status'];
|
||||
}
|
||||
if ( 0 < count( $fields ) ) {
|
||||
$post = get_post( $post_id );
|
||||
$ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
|
||||
$message = $api->update_api_event_fields( $post, $fields, 'update', $ajax );
|
||||
if ( null !== $message ) {
|
||||
if ( $ajax ) {
|
||||
wp_die( $message );
|
||||
} else {
|
||||
wp_redirect( $this->get_sendback_page( $post_id ) );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
case 'editpost': //edition from post page
|
||||
$new_post_data['ID'] = $post_id;
|
||||
$post = new WP_Post( (object) $new_post_data );
|
||||
if ( false === $this->is_valid_event( $post ) ) {
|
||||
break;
|
||||
}
|
||||
$data = $this->_parse_post_to_event( $post_id );
|
||||
if ( ! $data ) {
|
||||
break;
|
||||
}
|
||||
$event = $data['event'];
|
||||
$cost_type = isset( $_REQUEST['ai1ec_cost_type'] ) ? $_REQUEST['ai1ec_cost_type'] : '';
|
||||
if ( 'tickets' === $cost_type ) {
|
||||
$result = $api->store_event( $event, $post, true );
|
||||
if ( true !== $result ) {
|
||||
wp_redirect( $this->get_sendback_page( $post_id ) );
|
||||
exit();
|
||||
}
|
||||
} else {
|
||||
$message = $api->delete_api_event( $post_id, 'update', false );
|
||||
if ( null !== $message ) {
|
||||
wp_redirect( $this->get_sendback_page( $post_id ) );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// LABEL:magicquotes
|
||||
// restore `magic` WordPress quotes to maintain compatibility
|
||||
$_POST = add_magic_quotes( $_POST );
|
||||
}
|
||||
|
||||
protected function current_action() {
|
||||
$action = '';
|
||||
if ( isset( $_REQUEST['delete_all'] ) || isset( $_REQUEST['delete_all2'] ) ) {
|
||||
$action = 'delete';
|
||||
} else {
|
||||
if ( isset( $_REQUEST['action'] ) && -1 != $_REQUEST['action'] ) {
|
||||
$action = $_REQUEST['action'];
|
||||
}
|
||||
if ( isset( $_REQUEST['action2'] ) && -1 != $_REQUEST['action2'] ) {
|
||||
$action = $_REQUEST['action2'];
|
||||
}
|
||||
}
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* _create_duplicate_post method
|
||||
*
|
||||
* Create copy of event by calling {@uses wp_insert_post} function.
|
||||
* Using 'post_parent' to add hierarchy.
|
||||
*
|
||||
* @param array $data Event instance data to copy
|
||||
*
|
||||
* @return int|bool New post ID or false on failure
|
||||
**/
|
||||
public function create_duplicate_post() {
|
||||
if ( ! isset( $_POST['post_ID'] ) ) {
|
||||
return false;
|
||||
}
|
||||
$clean_fields = array(
|
||||
'ai1ec_repeat' => NULL,
|
||||
'ai1ec_rrule' => '',
|
||||
'ai1ec_exrule' => '',
|
||||
'ai1ec_exdate' => '',
|
||||
'post_ID' => NULL,
|
||||
'post_name' => NULL,
|
||||
'ai1ec_instance_id' => NULL,
|
||||
);
|
||||
$old_post_id = $_POST['post_ID'];
|
||||
$instance_id = $_POST['ai1ec_instance_id'];
|
||||
foreach ( $clean_fields as $field => $to_value ) {
|
||||
if ( NULL === $to_value ) {
|
||||
unset( $_POST[$field] );
|
||||
} else {
|
||||
$_POST[$field] = $to_value;
|
||||
}
|
||||
}
|
||||
$_POST = _wp_translate_postdata( false, $_POST );
|
||||
$_POST['post_parent'] = $old_post_id;
|
||||
if ( isset( $_POST['post_title'] ) ) {
|
||||
$_POST['post_title'] = sanitize_text_field( $_POST['post_title'] );
|
||||
}
|
||||
$post_id = wp_insert_post( $_POST );
|
||||
$this->_registry->get( 'model.event.parent' )->event_parent(
|
||||
$post_id,
|
||||
$old_post_id,
|
||||
$instance_id
|
||||
);
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans calendar shortcodes from event content.
|
||||
*
|
||||
* @param array $data An array of slashed post data.
|
||||
* @param array $postarr An array of sanitized, but otherwise unmodified post data.
|
||||
*
|
||||
* @return array An array of slashed post data.
|
||||
*/
|
||||
public function wp_insert_post_data( $data ) {
|
||||
global $shortcode_tags;
|
||||
if (
|
||||
! isset( $data['post_type'] ) ||
|
||||
! isset( $data['post_content'] ) ||
|
||||
AI1EC_POST_TYPE !== $data['post_type'] ||
|
||||
empty( $shortcode_tags ) ||
|
||||
! is_array( $shortcode_tags ) ||
|
||||
false === strpos( $data['post_content'], '[' )
|
||||
) {
|
||||
return $data;
|
||||
}
|
||||
$pattern = get_shortcode_regex();
|
||||
$data['post_content'] = preg_replace_callback(
|
||||
"/$pattern/s",
|
||||
array( $this, 'strip_shortcode_tag' ),
|
||||
$data['post_content']
|
||||
);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reutrns shortcode or stripped content for given shortcode.
|
||||
* Currently regex callback function passes as $tag argument 7-element long
|
||||
* array.
|
||||
* First element ($tag[0]) is not modified full shortcode text.
|
||||
* Third element ($tag[2]) is pure shortcode identifier.
|
||||
* Sixth element ($tag[5]) contains shortcode content if any
|
||||
* [ai1ec_test]content[/ai1ec].
|
||||
*
|
||||
* @param array $tag Incoming data.
|
||||
*
|
||||
* @return string Shortcode replace tag.
|
||||
*/
|
||||
public function strip_shortcode_tag( $tag ) {
|
||||
if (
|
||||
count( $tag ) < 7 ||
|
||||
'ai1ec' !== substr( $tag[2], 0, 5 ) ||
|
||||
! apply_filters( 'ai1ec_content_remove_shortcode_' . $tag[2], false )
|
||||
) {
|
||||
return $tag[0];
|
||||
}
|
||||
return $tag[5];
|
||||
}
|
||||
|
||||
protected function _remap_recurrence_dates() {
|
||||
if (
|
||||
isset( $_POST['ai1ec_exclude'] ) &&
|
||||
'EXDATE' === substr( $_POST['ai1ec_exrule'], 0, 6 )
|
||||
) {
|
||||
$_POST['ai1ec_exdate'] = substr( $_POST['ai1ec_exrule'], 7 );
|
||||
unset( $_POST['ai1ec_exclude'], $_POST['ai1ec_exrule'] );
|
||||
}
|
||||
if (
|
||||
isset( $_POST['ai1ec_repeat'] ) &&
|
||||
'RDATE' === substr( $_POST['ai1ec_rrule'], 0, 5 )
|
||||
) {
|
||||
$_POST['ai1ec_rdate'] = substr( $_POST['ai1ec_rrule'], 6 );
|
||||
unset( $_POST['ai1ec_repeat'], $_POST['ai1ec_rrule'] );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Event internal structure representation. Plain value object.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.0
|
||||
* @instantiator new
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
class Ai1ec_Event_Entity extends Ai1ec_Base {
|
||||
|
||||
/**
|
||||
* Get list of object properties.
|
||||
*
|
||||
* Special value `registry` ({@see Ai1ec_Registry_Object}) is excluded.
|
||||
*
|
||||
* @return array List of accessible properties.
|
||||
*
|
||||
* @staticvar array $known List of properties.
|
||||
*/
|
||||
public function list_properties() {
|
||||
static $known = null;
|
||||
if ( null === $known ) {
|
||||
$known = array();
|
||||
foreach ( $this as $name => $value ) {
|
||||
$name = substr( $name, 1 );
|
||||
if ( 'registry' === $name ) {
|
||||
continue;
|
||||
}
|
||||
$known[] = $name;
|
||||
}
|
||||
}
|
||||
return $known;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle cloning properly to resist property changes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone() {
|
||||
$this->_start = $this->_registry->get( 'date.time', $this->_start );
|
||||
$this->_end = $this->_registry->get( 'date.time', $this->_end );
|
||||
$this->_post = clone $this->_post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change stored property.
|
||||
*
|
||||
* @param string $name Name of property to change.
|
||||
* @param mixed $value Arbitrary value to use.
|
||||
*
|
||||
* @return Ai1ec_Event_Entity Instance of self for chaining.
|
||||
*
|
||||
* @staticvar array $time_fields Map of fields holding a value of
|
||||
* {@see Ai1ec_Date_Time}, which
|
||||
* require modification instead of
|
||||
* replacement.
|
||||
*/
|
||||
public function set( $name, $value ) {
|
||||
static $time_fields = array(
|
||||
'start' => true,
|
||||
'end' => true,
|
||||
);
|
||||
if ( 'registry' === $name ) {
|
||||
return $this; // short-circuit: protection mean.
|
||||
}
|
||||
if ( 'timezone_name' === $name && empty( $value ) ) {
|
||||
return $this; // protection against invalid TZ values.
|
||||
}
|
||||
$field = '_' . $name;
|
||||
if ( isset( $time_fields[$name] ) ) {
|
||||
// object of Ai1ec_Date_Time type is now handled in it itself
|
||||
$this->{$field}->set_date_time(
|
||||
$value,
|
||||
( null === $this->_timezone_name )
|
||||
? 'UTC'
|
||||
: $this->_timezone_name
|
||||
);
|
||||
$this->adjust_preferred_timezone();
|
||||
} else {
|
||||
$this->{$field} = $value;
|
||||
}
|
||||
if ( 'timezone_name' === $name ) {
|
||||
$this->_start->set_timezone( $value );
|
||||
$this->_end ->set_timezone( $value );
|
||||
$this->adjust_preferred_timezone();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally adjust preferred (display) timezone.
|
||||
*
|
||||
* @return bool|DateTimeZone False or new timezone.
|
||||
*
|
||||
* @staticvar bool $do_adjust True when adjustment should be performed.
|
||||
*/
|
||||
public function adjust_preferred_timezone() {
|
||||
static $do_adjust = null;
|
||||
if ( null === $do_adjust ) {
|
||||
$do_adjust = !$this->_registry
|
||||
->get( 'model.settings' )
|
||||
->get( 'always_use_calendar_timezone', false );
|
||||
}
|
||||
if ( ! $do_adjust ) {
|
||||
return false;
|
||||
}
|
||||
$timezone = $this->_registry->get( 'date.timezone' )->get(
|
||||
$this->_timezone_name
|
||||
);
|
||||
$this->set_preferred_timezone( $timezone );
|
||||
return $timezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set preferred timezone to datetime fields.
|
||||
*
|
||||
* @param DateTimeZone $timezone Preferred timezone instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_preferred_timezone( DateTimeZone $timezone ) {
|
||||
$this->_start->set_preferred_timezone( $timezone );
|
||||
$this->_end ->set_preferred_timezone( $timezone );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value of some property.
|
||||
*
|
||||
* @param string $name Name of property to get.
|
||||
* @param mixed $default Value to return if property is not defined.
|
||||
*
|
||||
* @return mixed Found value or $default.
|
||||
*/
|
||||
public function get( $name, $default = null ) {
|
||||
if ( ! isset( $this->{ '_' . $name } ) ) {
|
||||
return $default;
|
||||
}
|
||||
return $this->{ '_' . $name };
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize values to some sane defaults.
|
||||
*
|
||||
* @param Ai1ec_Registry_Object $registry Injected registry.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( Ai1ec_Registry_Object $registry ) {
|
||||
parent::__construct( $registry );
|
||||
$this->_start = $this->_registry->get( 'date.time' );
|
||||
$this->_end = $this->_registry->get( 'date.time', '+1 hour' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @var object Instance of WP_Post object.
|
||||
*/
|
||||
private $_post;
|
||||
|
||||
/**
|
||||
* @var int Post ID.
|
||||
*/
|
||||
private $_post_id;
|
||||
|
||||
/**
|
||||
* @var int|null Uniquely identifies the recurrence instance of this event
|
||||
* object. Value may be null.
|
||||
*/
|
||||
private $_instance_id;
|
||||
|
||||
/**
|
||||
* @var string Name of timezone to use for event times.
|
||||
*/
|
||||
private $_timezone_name;
|
||||
|
||||
/**
|
||||
* @var Ai1ec_Date_Time Start date-time specifier
|
||||
*/
|
||||
private $_start;
|
||||
|
||||
/**
|
||||
* @var Ai1ec_Date_Time End date-time specifier
|
||||
*/
|
||||
private $_end;
|
||||
|
||||
/**
|
||||
* @var bool Whether this copy of the event was broken up for rendering and
|
||||
* the start time is not its "real" start time.
|
||||
*/
|
||||
private $_start_truncated;
|
||||
|
||||
/**
|
||||
* @var bool Whether this copy of the event was broken up for rendering and
|
||||
* the end time is not its "real" end time.
|
||||
*/
|
||||
private $_end_truncated;
|
||||
|
||||
/**
|
||||
* @var int If event is all-day long
|
||||
*/
|
||||
private $_allday;
|
||||
|
||||
/**
|
||||
* @var int If event has no duration
|
||||
*/
|
||||
private $_instant_event;
|
||||
|
||||
/**
|
||||
* ==========================
|
||||
* = Recurrence information =
|
||||
* ==========================
|
||||
*/
|
||||
|
||||
/**
|
||||
* @var string Recurrence rules
|
||||
*/
|
||||
private $_recurrence_rules;
|
||||
|
||||
/**
|
||||
* @var string Exception rules
|
||||
*/
|
||||
private $_exception_rules;
|
||||
|
||||
/**
|
||||
* @var string Recurrence dates
|
||||
*/
|
||||
private $_recurrence_dates;
|
||||
|
||||
/**
|
||||
* @var string Exception dates
|
||||
*/
|
||||
private $_exception_dates;
|
||||
|
||||
/**
|
||||
* @var string Venue name - free text
|
||||
*/
|
||||
private $_venue;
|
||||
|
||||
/**
|
||||
* @var string Country name - free text
|
||||
*/
|
||||
private $_country;
|
||||
|
||||
/**
|
||||
* @var string Address information - free text
|
||||
*/
|
||||
private $_address;
|
||||
|
||||
/**
|
||||
* @var string City name - free text
|
||||
*/
|
||||
private $_city;
|
||||
|
||||
/**
|
||||
* @var string Province free text definition
|
||||
*/
|
||||
private $_province;
|
||||
|
||||
/**
|
||||
* @var int Postal code
|
||||
*/
|
||||
private $_postal_code;
|
||||
|
||||
/**
|
||||
* @var int Set to true to display map
|
||||
*/
|
||||
private $_show_map;
|
||||
|
||||
/**
|
||||
* @var int Set to true to show coordinates in description
|
||||
*/
|
||||
private $_show_coordinates;
|
||||
|
||||
/**
|
||||
* @var float GEO information - longitude
|
||||
*/
|
||||
private $_longitude;
|
||||
|
||||
/**
|
||||
* @var float GEO information - latitude
|
||||
*/
|
||||
private $_latitude;
|
||||
|
||||
/**
|
||||
* @var string Event contact information - contact person
|
||||
*/
|
||||
private $_contact_name;
|
||||
|
||||
/**
|
||||
* @var string Event contact information - phone number
|
||||
*/
|
||||
private $_contact_phone;
|
||||
|
||||
/**
|
||||
* @var string Event contact information - email address
|
||||
*/
|
||||
private $_contact_email;
|
||||
|
||||
/**
|
||||
* @var string Event contact information - external URL.
|
||||
*/
|
||||
private $_contact_url;
|
||||
|
||||
/**
|
||||
* @var string Defines event cost.
|
||||
*/
|
||||
private $_cost;
|
||||
|
||||
/**
|
||||
* @var bool Indicates, whereas event is free.
|
||||
*/
|
||||
private $_is_free;
|
||||
|
||||
/**
|
||||
* @var string Link to buy tickets
|
||||
*/
|
||||
private $_ticket_url;
|
||||
|
||||
// ====================================
|
||||
// = iCalendar feed (.ics) properties =
|
||||
// ====================================
|
||||
|
||||
/**
|
||||
* @var string URI of source ICAL feed.
|
||||
*/
|
||||
private $_ical_feed_url;
|
||||
|
||||
/**
|
||||
* @var string|null URI of source ICAL entity.
|
||||
*/
|
||||
private $_ical_source_url;
|
||||
|
||||
/**
|
||||
* @var string Organiser details
|
||||
*/
|
||||
private $_ical_organizer;
|
||||
|
||||
/**
|
||||
* @var string Contact details
|
||||
*/
|
||||
private $_ical_contact;
|
||||
|
||||
/**
|
||||
* @var string|int UID of ICAL feed
|
||||
*/
|
||||
private $_ical_uid;
|
||||
|
||||
// ===============================
|
||||
// = taxonomy-related properties =
|
||||
// ===============================
|
||||
|
||||
/**
|
||||
* @var string Associated event tag names (*not* IDs), joined by commas.
|
||||
*/
|
||||
private $_tags;
|
||||
|
||||
/**
|
||||
* @var string Associated event category IDs, joined by commas.
|
||||
*/
|
||||
private $_categories;
|
||||
|
||||
/**
|
||||
* @var string Associated event feed object
|
||||
*/
|
||||
private $_feed;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Indicates a failure that happens during event save operation.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.1
|
||||
*
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Model
|
||||
*/
|
||||
class Ai1ec_Event_Create_Exception extends Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
<?php
|
||||
|
||||
use Kigkonsult\Icalcreator\Util\UtilRecur;
|
||||
|
||||
/**
|
||||
* Event instance management model.
|
||||
*
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.0
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
class Ai1ec_Event_Instance extends Ai1ec_Base {
|
||||
|
||||
/**
|
||||
* @var Ai1ec_Dbi Instance of database abstraction.
|
||||
*/
|
||||
protected $_dbi = null;
|
||||
|
||||
/**
|
||||
* DBI utils.
|
||||
*
|
||||
* @var Ai1ec_Dbi_Utils
|
||||
*/
|
||||
protected $_dbi_utils;
|
||||
|
||||
/**
|
||||
* Store locally instance of Ai1ec_Dbi.
|
||||
*
|
||||
* @param Ai1ec_Registry_Object $registry Injected object registry.
|
||||
*
|
||||
*/
|
||||
public function __construct( Ai1ec_Registry_Object $registry ) {
|
||||
parent::__construct( $registry );
|
||||
$this->_dbi = $this->_registry->get( 'dbi.dbi' );
|
||||
$this->_dbi_utils = $this->_registry->get( 'dbi.dbi-utils' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove entries for given post. Optionally delete particular instance.
|
||||
*
|
||||
* @param int $post_id Event ID to remove instances for.
|
||||
* @param int|null $instance_id Instance ID, or null for all.
|
||||
*
|
||||
* @return int|bool Number of entries removed, or false on failure.
|
||||
*/
|
||||
public function clean( $post_id, $instance_id = null ) {
|
||||
$where = array( 'post_id' => $post_id );
|
||||
$format = array( '%d' );
|
||||
if ( null !== $instance_id ) {
|
||||
$where['id'] = $instance_id;
|
||||
$format[] = '%d';
|
||||
}
|
||||
return $this->_dbi->delete( 'ai1ec_event_instances', $where, $format );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove and then create instance entries for given event.
|
||||
*
|
||||
* @param Ai1ec_Event $event Instance of event to recreate entries for.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function recreate( Ai1ec_Event $event ) {
|
||||
$old_instances = $this->_load_instances( $event->get( 'post_id' ) );
|
||||
$instances = $this->_create_instances_collection( $event );
|
||||
$insert = array();
|
||||
foreach ( $instances as $instance ) {
|
||||
if ( ! isset( $old_instances[$instance['start'] . ':' . $instance['end']] ) ) {
|
||||
$insert[] = $instance;
|
||||
continue;
|
||||
}
|
||||
unset( $old_instances[$instance['start'] . ':' . $instance['end']] );
|
||||
}
|
||||
$this->_remove_instances_by_ids( array_values( $old_instances ) );
|
||||
$this->_add_instances( $insert );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create list of recurrent instances.
|
||||
*
|
||||
* @param Ai1ec_Event $event Event to generate instances for.
|
||||
* @param array $event_instance First instance contents.
|
||||
* @param int $_start Timestamp of first occurence.
|
||||
* @param int $duration Event duration in seconds.
|
||||
* @param string $timezone Target timezone.
|
||||
*
|
||||
* @return array List of event instances.
|
||||
*/
|
||||
public function create_instances_by_recurrence(
|
||||
Ai1ec_Event $event,
|
||||
array $event_instance,
|
||||
$_start,
|
||||
$duration,
|
||||
$timezone
|
||||
) {
|
||||
$restore_timezone = date_default_timezone_get();
|
||||
$recurrence_parser = $this->_registry->get( 'recurrence.rule' );
|
||||
$events = array();
|
||||
|
||||
$start = $event_instance['start'];
|
||||
$wdate = $startdate = $enddate
|
||||
= $this->_parsed_date_array( $_start, $timezone );
|
||||
$enddate['year'] = $enddate['year'] + 10;
|
||||
$exclude_dates = array();
|
||||
$exception_rules = $event->get( 'exception_dates' );
|
||||
$recurrence_dates = array();
|
||||
$recurrence_rules = $event->get( 'recurrence_dates' );
|
||||
if ( $recurrence_rules ) {
|
||||
$recurrence_dates = $this->_populate_recurring_dates(
|
||||
$recurrence_rules,
|
||||
$startdate,
|
||||
$timezone
|
||||
);
|
||||
}
|
||||
if ( $exception_rules ) {
|
||||
$exclude_dates = $this->_populate_recurring_dates(
|
||||
$exception_rules,
|
||||
$startdate,
|
||||
$timezone
|
||||
);
|
||||
}
|
||||
if ( $event->get( 'exception_rules' ) ) {
|
||||
// creat an array for the rules
|
||||
$exception_rules = $recurrence_parser
|
||||
->build_recurrence_rules_array(
|
||||
$event->get( 'exception_rules' )
|
||||
);
|
||||
unset($exception_rules['EXDATE']);
|
||||
if ( ! empty( $exception_rules ) ) {
|
||||
$exception_rules = UtilRecur::setRexrule(
|
||||
$exception_rules
|
||||
);
|
||||
$result = array();
|
||||
date_default_timezone_set( $timezone );
|
||||
// The first array is the result and it is passed by reference
|
||||
UtilRecur::recur2date(
|
||||
$exclude_dates,
|
||||
$exception_rules,
|
||||
$wdate,
|
||||
$startdate,
|
||||
$enddate
|
||||
);
|
||||
// Get start date time
|
||||
$startHour = isset( $startdate['hour'] ) ? sprintf( "%02d", $startdate['hour'] ) : '00';
|
||||
$startMinute = isset( $startdate['min'] ) ? sprintf( "%02d", $startdate['min'] ) : '00';
|
||||
$startSecond = isset( $startdate['sec'] ) ? sprintf( "%02d", $startdate['sec'] ) : '00';
|
||||
$startTime = $startHour . $startMinute . $startSecond;
|
||||
// Convert to timestamp
|
||||
if ( is_array( $exclude_dates ) ) {
|
||||
$new_exclude_dates = array();
|
||||
foreach ( $exclude_dates as $key => $value ) {
|
||||
$timestamp = strtotime( $key . 'T' . $startTime );
|
||||
$new_exclude_dates[$timestamp] = $value;
|
||||
}
|
||||
$exclude_dates = $new_exclude_dates;
|
||||
}
|
||||
date_default_timezone_set( $restore_timezone );
|
||||
}
|
||||
}
|
||||
$recurrence_rules = $recurrence_parser
|
||||
->build_recurrence_rules_array(
|
||||
$event->get( 'recurrence_rules' )
|
||||
);
|
||||
|
||||
$recurrence_rules = UtilRecur::setRexrule( $recurrence_rules );
|
||||
if ( $recurrence_rules ) {
|
||||
date_default_timezone_set( $timezone );
|
||||
UtilRecur::recur2date(
|
||||
$recurrence_dates,
|
||||
$recurrence_rules,
|
||||
$wdate,
|
||||
$startdate,
|
||||
$enddate
|
||||
);
|
||||
|
||||
// Get start date time
|
||||
$startHour = isset( $startdate['hour'] ) ? sprintf( "%02d", $startdate['hour'] ) : '00';
|
||||
$startMinute = isset( $startdate['min'] ) ? sprintf( "%02d", $startdate['min'] ) : '00';
|
||||
$startSecond = isset( $startdate['sec'] ) ? sprintf( "%02d", $startdate['sec'] ) : '00';
|
||||
$startTime = $startHour . $startMinute . $startSecond;
|
||||
// Convert to timestamp
|
||||
if ( is_array( $recurrence_dates ) ) {
|
||||
$new_recurrence_dates = array();
|
||||
foreach ( $recurrence_dates as $key => $value ) {
|
||||
$timestamp = strtotime( $key . 'T' . $startTime );
|
||||
$new_recurrence_dates[$timestamp] = $value;
|
||||
}
|
||||
$recurrence_dates = $new_recurrence_dates;
|
||||
}
|
||||
date_default_timezone_set( $restore_timezone );
|
||||
}
|
||||
|
||||
if ( ! is_array( $recurrence_dates ) ) {
|
||||
$recurrence_dates = array();
|
||||
}
|
||||
$recurrence_dates = array_keys( $recurrence_dates );
|
||||
// Add the instances
|
||||
foreach ( $recurrence_dates as $timestamp ) {
|
||||
// The arrays are in the form timestamp => true so an isset call is what we need
|
||||
if ( ! isset( $exclude_dates[$timestamp] ) ) {
|
||||
$event_instance['start'] = $timestamp;
|
||||
$event_instance['end'] = $timestamp + $duration;
|
||||
$events[$timestamp] = $event_instance;
|
||||
}
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and store instance entries in database for given event.
|
||||
*
|
||||
* @param Ai1ec_Event $event Instance of event to create entries for.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function create( Ai1ec_Event $event ) {
|
||||
$instances = $this->_create_instances_collection( $event );
|
||||
$this->_add_instances( $instances );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given date match dates in EXDATES rule.
|
||||
*
|
||||
* @param string $date Date to check.
|
||||
* @param string $ics_rule ICS EXDATES rule.
|
||||
* @param string $timezone Timezone to evaluate value in.
|
||||
*
|
||||
* @return bool True if given date is in rule.
|
||||
*/
|
||||
public function date_match_exdates( $date, $ics_rule, $timezone ) {
|
||||
$ranges = $this->_get_date_ranges( $ics_rule, $timezone );
|
||||
foreach ( $ranges as $interval ) {
|
||||
if ( $date >= $interval[0] && $date <= $interval[1] ) {
|
||||
return true;
|
||||
}
|
||||
if ( $date <= $interval[0] ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare date range list for fast exdate search.
|
||||
*
|
||||
* NOTICE: timezone is relevant in only first run.
|
||||
*
|
||||
* @param string $date_list ICS list provided from data model.
|
||||
* @param string $timezone Timezone in which to evaluate.
|
||||
*
|
||||
* @return array List of date ranges, sorted in increasing order.
|
||||
*/
|
||||
protected function _get_date_ranges( $date_list, $timezone ) {
|
||||
static $ranges = array();
|
||||
if ( ! isset( $ranges[$date_list] ) ) {
|
||||
$ranges[$date_list] = array();
|
||||
$exploded = explode( ',', $date_list );
|
||||
sort( $exploded );
|
||||
foreach ( $exploded as $date ) {
|
||||
// COMMENT on `rtrim( $date, 'Z' )`:
|
||||
// user selects exclusion date in event timezone thus it
|
||||
// must be parsed as such as opposed to UTC which happen
|
||||
// when 'Z' is preserved.
|
||||
$date = $this->_registry
|
||||
->get( 'date.time', rtrim( $date, 'Z' ), $timezone )
|
||||
->format_to_gmt();
|
||||
$ranges[$date_list][] = array(
|
||||
$date,
|
||||
$date + (24 * 60 * 60) - 1
|
||||
);
|
||||
}
|
||||
}
|
||||
return $ranges[$date_list];
|
||||
}
|
||||
|
||||
protected function _populate_recurring_dates( $rule, array $start_struct, $timezone ) {
|
||||
$start = clone $start_struct['_dt'];
|
||||
$dates = array();
|
||||
foreach ( explode( ',', $rule ) as $date ) {
|
||||
$i_date = clone $start;
|
||||
$spec = sscanf( $date, '%04d%02d%02d' );
|
||||
$i_date->set_date(
|
||||
$spec[0],
|
||||
$spec[1],
|
||||
$spec[2]
|
||||
);
|
||||
$dates[$i_date->format_to_gmt()] = $i_date;
|
||||
}
|
||||
return $dates;
|
||||
}
|
||||
|
||||
protected function _parsed_date_array( $startdate, $timezone ) {
|
||||
$datetime = $this->_registry->get( 'date.time', $startdate, $timezone );
|
||||
$parsed = array(
|
||||
'year' => intval( $datetime->format( 'Y' ) ),
|
||||
'month' => intval( $datetime->format( 'm' ) ),
|
||||
'day' => intval( $datetime->format( 'd' ) ),
|
||||
'hour' => intval( $datetime->format( 'H' ) ),
|
||||
'min' => intval( $datetime->format( 'i' ) ),
|
||||
'sec' => intval( $datetime->format( 's' ) ),
|
||||
'tz' => $datetime->get_timezone(),
|
||||
'_dt' => $datetime,
|
||||
);
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current instances map.
|
||||
*
|
||||
* @param int post_id Post ID.
|
||||
*
|
||||
* @return array Array of data.
|
||||
*/
|
||||
protected function _load_instances( $post_id ) {
|
||||
$query = $this->_dbi->prepare(
|
||||
'SELECT `id`, `start`, `end` FROM ' .
|
||||
$this->_dbi->get_table_name( 'ai1ec_event_instances' ) .
|
||||
' WHERE post_id = %d',
|
||||
$post_id
|
||||
);
|
||||
$results = $this->_dbi->get_results( $query );
|
||||
$instances = array();
|
||||
foreach ( $results as $result ) {
|
||||
$instances[(int)$result->start . ':' . (int)$result->end] = (int)$result->id;
|
||||
}
|
||||
return $instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and store instance entries in database for given event.
|
||||
*
|
||||
* @param Ai1ec_Event $event Instance of event to create entries for.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
protected function _create_instances_collection( Ai1ec_Event $event ) {
|
||||
$events = array();
|
||||
$event_item = array(
|
||||
'post_id' => $event->get( 'post_id' ),
|
||||
'start' => $event->get( 'start' )->format_to_gmt(),
|
||||
'end' => $event->get( 'end' )->format_to_gmt(),
|
||||
);
|
||||
$duration = $event->get( 'end' )->diff_sec( $event->get( 'start' ) );
|
||||
|
||||
$_start = $event->get( 'start' )->format_to_gmt();
|
||||
$_end = $event->get( 'end' )->format_to_gmt();
|
||||
|
||||
// Always cache initial instance
|
||||
$events[$_start] = $event_item;
|
||||
|
||||
if ( $event->get( 'recurrence_rules' ) || $event->get( 'recurrence_dates' ) ) {
|
||||
$start_timezone = $this->_registry->get( 'model.option' )
|
||||
->get( 'timezone_string' );
|
||||
if ( empty( $start_timezone ) ) {
|
||||
$start_timezone = $this->_registry->get( 'date.timezone' )->get_default_timezone();
|
||||
}
|
||||
|
||||
$events += $this->create_instances_by_recurrence(
|
||||
$event,
|
||||
$event_item,
|
||||
$_start,
|
||||
$duration,
|
||||
$start_timezone
|
||||
);
|
||||
}
|
||||
|
||||
$search_helper = $this->_registry->get( 'model.search' );
|
||||
foreach ( $events as &$event_item ) {
|
||||
// Find out if this event instance is already accounted for by an
|
||||
// overriding 'RECURRENCE-ID' of the same iCalendar feed (by comparing the
|
||||
// UID, start date, recurrence). If so, then do not create duplicate
|
||||
// instance of event.
|
||||
$start = $event_item['start'];
|
||||
$matching_event_id = null;
|
||||
if ( $event->get( 'ical_uid' ) ) {
|
||||
$matching_event_id = $search_helper->get_matching_event_id(
|
||||
$event->get( 'ical_uid' ),
|
||||
$event->get( 'ical_feed_url' ),
|
||||
$event->get( 'start' ),
|
||||
false,
|
||||
$event->get( 'post_id' )
|
||||
);
|
||||
}
|
||||
|
||||
// If no other instance was found
|
||||
if ( null !== $matching_event_id ) {
|
||||
$event_item = false;
|
||||
}
|
||||
}
|
||||
|
||||
return array_filter( $events );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes ai1ec_event_instances entries using their IDS.
|
||||
*
|
||||
* @param array $ids Collection of IDS.
|
||||
*
|
||||
* @return bool Result.
|
||||
*/
|
||||
protected function _remove_instances_by_ids( array $ids ) {
|
||||
if ( empty( $ids ) ) {
|
||||
return false;
|
||||
}
|
||||
$query = 'DELETE FROM ' . $this->_dbi->get_table_name(
|
||||
'ai1ec_event_instances'
|
||||
) . ' WHERE id IN (';
|
||||
$ids = array_filter( array_map( 'intval', $ids ) );
|
||||
$query .= implode( ',', $ids ) . ')';
|
||||
$this->_dbi->query( $query );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new instances collection.
|
||||
*
|
||||
* @param array $instances Collection of instances.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _add_instances( array $instances ) {
|
||||
$chunks = array_chunk( $instances, 50 );
|
||||
foreach ( $chunks as $chunk ) {
|
||||
$query = 'INSERT INTO ' . $this->_dbi->get_table_name(
|
||||
'ai1ec_event_instances'
|
||||
) . '(`post_id`, `start`, `end`) VALUES';
|
||||
$chunk = array_map(
|
||||
array( $this->_dbi_utils, 'array_value_to_sql_value' ),
|
||||
$chunk
|
||||
);
|
||||
$query .= implode( ',', $chunk );
|
||||
$this->_dbi->query( $query );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Some argument was of invalid type.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
*
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Model
|
||||
*/
|
||||
class Ai1ec_Invalid_Argument_Exception extends Ai1ec_Exception {
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Model representing a legacy event.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.0
|
||||
* @instantiator new
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
class Ai1ec_Event_Legacy extends Ai1ec_Event {
|
||||
|
||||
/**
|
||||
* @var array map of method => class for legacy code.
|
||||
*/
|
||||
protected static $_classes = array(
|
||||
'get_category_colors' => 'taxonomy',
|
||||
'get_color_style' => 'taxonomy',
|
||||
'get_categories_html' => 'taxonomy',
|
||||
'get_tags_html' => 'taxonomy',
|
||||
'get_category_text_color' => 'taxonomy',
|
||||
'get_category_bg_color' => 'taxonomy',
|
||||
'get_faded_color' => 'color',
|
||||
'get_rgba_color' => 'color',
|
||||
'get_event_avatar' => 'avatar',
|
||||
'get_event_avatar_url' => 'avatar',
|
||||
'get_post_thumbnail_url' => 'avatar',
|
||||
'get_content_img_url' => 'avatar',
|
||||
'get_short_location' => 'location',
|
||||
'get_location' => 'location',
|
||||
'get_map_view' => 'location',
|
||||
'get_latlng' => 'location',
|
||||
'get_gmap_url' => 'location',
|
||||
'get_tickets_url_label' => 'ticket',
|
||||
'get_contact_html' => 'ticket',
|
||||
'get_timespan_html' => 'time',
|
||||
'get_exclude_html' => 'time',
|
||||
'get_back_to_calendar_button_html' => 'content',
|
||||
'get_post_excerpt' => 'content',
|
||||
);
|
||||
|
||||
public function get_long_end_date( $adjust = 0 ) {
|
||||
$time = $this->_registry->get( 'view.event.time' );
|
||||
$end = $this->_registry->get( 'date.time', $this->get( 'end' ) );
|
||||
if ( ! empty( $adjust ) ) {
|
||||
$end->set_time(
|
||||
$end->format( 'H' ),
|
||||
$end->format( 'i' ),
|
||||
$adjust
|
||||
);
|
||||
}
|
||||
return $time->get_long_date( $end );
|
||||
}
|
||||
|
||||
public function get_long_start_date() {
|
||||
$time = $this->_registry->get( 'view.event.time' );
|
||||
return $time->get_long_date( $this->get( 'start' ) );
|
||||
}
|
||||
|
||||
public function get_multiday() {
|
||||
return $this->is_multiday();
|
||||
}
|
||||
|
||||
public function get_recurrence_html() {
|
||||
$rrule = $this->_registry->get( 'recurrence.rule' );
|
||||
return $rrule->rrule_to_text( $this->get( 'recurrence_rules' ) );
|
||||
}
|
||||
|
||||
public function get_short_end_date() {
|
||||
$time = $this->_registry->get( 'view.event.time' );
|
||||
$end = $this->_registry->get( 'date.time', $this->get( 'end' ) );
|
||||
$end->set_time(
|
||||
$end->format( 'H' ),
|
||||
$end->format( 'i' ),
|
||||
-1
|
||||
);
|
||||
return $time->get_short_date( $end );
|
||||
}
|
||||
|
||||
public function get_short_end_time() {
|
||||
$time = $this->_registry->get( 'view.event.time' );
|
||||
return $time->get_short_time( $this->get( 'end' ) );
|
||||
}
|
||||
|
||||
public function get_short_start_date() {
|
||||
$time = $this->_registry->get( 'view.event.time' );
|
||||
return $time->get_short_date( $this->get( 'start' ) );
|
||||
}
|
||||
|
||||
public function get_short_start_time() {
|
||||
$time = $this->_registry->get( 'view.event.time' );
|
||||
return $time->get_short_time( $this->get( 'start' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles legacy property setters.
|
||||
*
|
||||
* @param string $property Name of property being set.
|
||||
* @param mixed $value Value attempted to set.
|
||||
*
|
||||
* @return Ai1ec_Event Instance of self for chaining.
|
||||
*/
|
||||
public function __set( $property, $value ) {
|
||||
return $this->set( $property, $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle property accessors.
|
||||
*
|
||||
* @param string $name Property name
|
||||
*
|
||||
* @return mixed Property value
|
||||
*/
|
||||
public function __get( $name ) {
|
||||
$method = 'get_' . $name;
|
||||
if ( method_exists( $this, $name ) ) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
return $this->get( $name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle legacy methods calls.
|
||||
*
|
||||
* @param string $method Legacy method name.
|
||||
* @param array $arguments Arguments passed to method.
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws Ai1ec_Invalid_Argument_Exception If there is no method handler.
|
||||
*/
|
||||
public function __call( $method, $arguments ) {
|
||||
if ( ! isset( self::$_classes[$method] ) ) {
|
||||
throw new Ai1ec_Invalid_Argument_Exception(
|
||||
'Requested method \'' . $method . '\' is unknown'
|
||||
);
|
||||
}
|
||||
array_unshift( $arguments, $this );
|
||||
$class = 'view.event.' . self::$_classes[$method];
|
||||
return $this->_registry->dispatch(
|
||||
$class,
|
||||
$method,
|
||||
$arguments
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Event not found.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
*
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Model
|
||||
*/
|
||||
class Ai1ec_Event_Not_Found_Exception extends Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
<?php
|
||||
/**
|
||||
* Class which represnt event parent/child relationship.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.0
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
class Ai1ec_Event_Parent extends Ai1ec_Base {
|
||||
|
||||
/**
|
||||
* event_parent method
|
||||
*
|
||||
* Get/set event parent
|
||||
*
|
||||
* @param int $event_id ID of checked event
|
||||
* @param int $parent_id ID of new parent [optional=NULL, acts as getter]
|
||||
* @param int $instance_id ID of old instance id
|
||||
*
|
||||
* @return int|bool Value depends on mode:
|
||||
* Getter: {@see self::get_parent_event()} for details
|
||||
* Setter: true on success.
|
||||
*/
|
||||
public function event_parent(
|
||||
$event_id,
|
||||
$parent_id = null,
|
||||
$instance_id = null
|
||||
) {
|
||||
$meta_key = '_ai1ec_event_parent';
|
||||
if ( null === $parent_id ) {
|
||||
return $this->get_parent_event( $event_id );
|
||||
}
|
||||
$meta_value = json_encode( array(
|
||||
'created' => $this->_registry->get( 'date.system' )->current_time(),
|
||||
'instance' => $instance_id,
|
||||
) );
|
||||
return add_post_meta( $event_id, $meta_key, $meta_value, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parent ID for given event
|
||||
*
|
||||
* @param int $current_id Current event ID
|
||||
*
|
||||
* @return int|bool ID of parent event or bool(false)
|
||||
*/
|
||||
public function get_parent_event( $current_id ) {
|
||||
static $parents = null;
|
||||
if ( null === $parents ) {
|
||||
$parents = $this->_registry->get( 'cache.memory' );
|
||||
}
|
||||
$current_id = (int)$current_id;
|
||||
if ( null === ( $parent_id = $parents->get( $current_id ) ) ) {
|
||||
$db = $this->_registry->get( 'dbi.dbi' );
|
||||
/* @var $db Ai1ec_Dbi */
|
||||
$query = '
|
||||
SELECT parent.ID, parent.post_status
|
||||
FROM
|
||||
' . $db->get_table_name( 'posts' ) . ' AS child
|
||||
INNER JOIN ' . $db->get_table_name( 'posts' ) . ' AS parent
|
||||
ON ( parent.ID = child.post_parent )
|
||||
WHERE child.ID = ' . $current_id;
|
||||
$parent = $db->get_row( $query );
|
||||
if (
|
||||
empty( $parent ) ||
|
||||
'trash' === $parent->post_status
|
||||
) {
|
||||
$parent_id = false;
|
||||
} else {
|
||||
$parent_id = $parent->ID;
|
||||
}
|
||||
$parents->set( $current_id, $parent_id );
|
||||
unset( $query );
|
||||
}
|
||||
return $parent_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of modified (children) event objects
|
||||
*
|
||||
* @param int $parent_id ID of parent event
|
||||
* @param bool $include_trash Includes trashed when `true` [optional=false]
|
||||
*
|
||||
* @return array List (might be empty) of Ai1ec_Event objects
|
||||
*/
|
||||
public function get_child_event_objects(
|
||||
$parent_id,
|
||||
$include_trash = false
|
||||
) {
|
||||
$db = $this->_registry->get( 'dbi.dbi' );
|
||||
/* @var $db Ai1ec_Dbi */
|
||||
$parent_id = (int)$parent_id;
|
||||
$sql_query = 'SELECT ID FROM ' . $db->get_table_name( 'posts' ) .
|
||||
' WHERE post_parent = ' . $parent_id;
|
||||
$children = (array)$db->get_col( $sql_query );
|
||||
$objects = array();
|
||||
foreach ( $children as $child_id ) {
|
||||
try {
|
||||
$instance = $this->_registry->get( 'model.event', $child_id );
|
||||
if (
|
||||
$include_trash ||
|
||||
'trash' !== $instance->get( 'post' )->post_status
|
||||
) {
|
||||
$objects[$child_id] = $instance;
|
||||
}
|
||||
} catch ( Ai1ec_Event_Not_Found_Exception $exception ) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* admin_init_post method
|
||||
*
|
||||
* Bind to admin_action_editpost action to override default save
|
||||
* method when user is editing single instance.
|
||||
* New post is created with some fields unset.
|
||||
*/
|
||||
public function admin_init_post( ) {
|
||||
if (
|
||||
isset( $_POST['ai1ec_instance_id'] ) &&
|
||||
isset( $_POST['action'] ) &&
|
||||
'editpost' === $_POST['action']
|
||||
) {
|
||||
$old_post_id = $_POST['post_ID'];
|
||||
$instance_id = $_POST['ai1ec_instance_id'];
|
||||
$post_id = $this->_registry->get( 'model.event.creating' )
|
||||
->create_duplicate_post();
|
||||
if ( false !== $post_id ) {
|
||||
$this->_handle_instances(
|
||||
$this->_registry->get( 'model.event', $post_id ),
|
||||
$this->_registry->get( 'model.event', $old_post_id ),
|
||||
$instance_id
|
||||
);
|
||||
$this->_registry->get( 'model.event.instance' )->clean(
|
||||
$old_post_id,
|
||||
$instance_id
|
||||
);
|
||||
$location = add_query_arg(
|
||||
'message',
|
||||
1,
|
||||
get_edit_post_link( $post_id, 'url' )
|
||||
);
|
||||
wp_redirect(
|
||||
apply_filters(
|
||||
'redirect_post_location',
|
||||
$location,
|
||||
$post_id
|
||||
)
|
||||
);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject base event edit link for modified instances
|
||||
*
|
||||
* Modified instances are events, belonging to some parent having recurrence
|
||||
* rule, and having some of it's properties altered.
|
||||
*
|
||||
* @param array $actions List of defined actions
|
||||
* @param stdClass $post Instance being rendered (WP_Post class instance in WP 3.5+)
|
||||
*
|
||||
* @return array Optionally modified $actions list
|
||||
*/
|
||||
public function post_row_actions( $actions, $post ) {
|
||||
if ( $this->_registry->get( 'acl.aco' )->is_our_post_type( $post ) ) {
|
||||
$parent_post_id = $this->event_parent( $post->ID );
|
||||
if (
|
||||
$parent_post_id &&
|
||||
NULL !== ( $parent_post = get_post( $parent_post_id ) ) &&
|
||||
isset( $parent_post->post_status ) &&
|
||||
'trash' !== $parent_post->post_status
|
||||
) {
|
||||
$parent_link = get_edit_post_link(
|
||||
$parent_post_id,
|
||||
'display'
|
||||
);
|
||||
$actions['ai1ec_parent'] = sprintf(
|
||||
'<a href="%s" title="%s">%s</a>',
|
||||
wp_nonce_url( $parent_link ),
|
||||
sprintf(
|
||||
__( 'Edit “%s”', AI1EC_PLUGIN_NAME ),
|
||||
apply_filters(
|
||||
'the_title',
|
||||
$parent_post->post_title,
|
||||
$parent_post->ID
|
||||
)
|
||||
),
|
||||
__( 'Base Event', AI1EC_PLUGIN_NAME )
|
||||
);
|
||||
}
|
||||
}
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* add_exception_date method
|
||||
*
|
||||
* Add exception (date) to event.
|
||||
*
|
||||
* @param int $post_id Event edited post ID
|
||||
* @param mixed $date Parseable date representation to exclude
|
||||
*
|
||||
* @return bool Success
|
||||
*/
|
||||
public function add_exception_date( $post_id, Ai1ec_Date_Time $date ) {
|
||||
$event = $this->_registry->get( 'model.event', $post_id );
|
||||
$dates_list = explode( ',', $event->get( 'exception_dates' ) );
|
||||
if ( empty( $dates_list[0] ) ) {
|
||||
unset( $dates_list[0] );
|
||||
}
|
||||
$date->set_time( 0, 0, 0 );
|
||||
$dates_list[] = $date->format(
|
||||
'Ymd\THis\Z'
|
||||
);
|
||||
$event->set( 'exception_dates', implode( ',', $dates_list ) );
|
||||
return $event->save( true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles instances saving and switching if needed. If original event
|
||||
* and created event have different start dates proceed in old style
|
||||
* otherwise find next instance, switch original start date to next
|
||||
* instance start date. If there are no next instances mark event as
|
||||
* non recurring. Filter also exception dates if are past.
|
||||
*
|
||||
* @param Ai1ec_Event $created_event Created event object.
|
||||
* @param Ai1ec_Event $original_event Original event object.
|
||||
*
|
||||
* @return void Method does not return.
|
||||
*/
|
||||
protected function _handle_instances(
|
||||
Ai1ec_Event $created_event,
|
||||
Ai1ec_Event $original_event,
|
||||
$instance_id
|
||||
) {
|
||||
$ce_start = $created_event->get( 'start' );
|
||||
$oe_start = $original_event->get( 'start' );
|
||||
if (
|
||||
$ce_start->format() !== $oe_start->format()
|
||||
) {
|
||||
$this->add_exception_date(
|
||||
$original_event->get( 'post_id' ),
|
||||
$ce_start
|
||||
);
|
||||
return;
|
||||
}
|
||||
$next_instance = $this->_find_next_instance(
|
||||
$original_event->get( 'post_id' ),
|
||||
$instance_id
|
||||
);
|
||||
if ( ! $next_instance ) {
|
||||
$original_event->set( 'recurrence_rules', null );
|
||||
$original_event->save( true );
|
||||
return;
|
||||
}
|
||||
$original_event->set(
|
||||
'start',
|
||||
$this->_registry->get( 'date.time', $next_instance->get( 'start' ) )
|
||||
);
|
||||
$original_event->set(
|
||||
'end',
|
||||
$this->_registry->get( 'date.time', $next_instance->get( 'end' ) )
|
||||
);
|
||||
$edates = $this->_filter_exception_dates( $original_event );
|
||||
$original_event->set( 'exception_dates', implode( ',', $edates ) );
|
||||
$recurrence_rules = $original_event->get( 'recurrence_rules' );
|
||||
$rules_info = $this->_registry->get( 'recurrence.rule' )
|
||||
->build_recurrence_rules_array( $recurrence_rules );
|
||||
if ( isset( $rules_info['COUNT'] ) ) {
|
||||
$next_instances_count = $this->_count_next_instances(
|
||||
$original_event->get( 'post_id' ),
|
||||
$instance_id
|
||||
);
|
||||
$rules_info['COUNT'] = (int)$next_instances_count + count( $edates );
|
||||
$rules = '';
|
||||
if ( $rules_info['COUNT'] <= 1 ) {
|
||||
$rules_info = array();
|
||||
}
|
||||
foreach ( $rules_info as $key => $value ) {
|
||||
$rules .= $key . '=' . $value . ';';
|
||||
}
|
||||
$original_event->set(
|
||||
'recurrence_rules',
|
||||
$rules
|
||||
);
|
||||
}
|
||||
$original_event->save( true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns next instance.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param int $instance_id Instance ID.
|
||||
*
|
||||
* @return null|Ai1ec_Event Result.
|
||||
*/
|
||||
protected function _find_next_instance( $post_id, $instance_id ) {
|
||||
$dbi = $this->_registry->get( 'dbi.dbi' );
|
||||
$table_instances = $dbi->get_table_name( 'ai1ec_event_instances' );
|
||||
$table_posts = $dbi->get_table_name( 'posts' );
|
||||
$query = $dbi->prepare(
|
||||
'SELECT i.id FROM ' . $table_instances . ' i JOIN ' .
|
||||
$table_posts . ' p ON (p.ID = i.post_id) ' .
|
||||
'WHERE i.post_id = %d AND i.id > %d ' .
|
||||
'AND p.post_status = \'publish\' ' .
|
||||
'ORDER BY id ASC LIMIT 1',
|
||||
$post_id,
|
||||
$instance_id
|
||||
);
|
||||
$next_instance_id = $dbi->get_var( $query );
|
||||
if ( ! $next_instance_id ) {
|
||||
return null;
|
||||
}
|
||||
return $this->_registry->get( 'model.search' )
|
||||
->get_event( $post_id, $next_instance_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts future instances.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param int $instance_id Instance ID.
|
||||
*
|
||||
* @return int Result.
|
||||
*/
|
||||
protected function _count_next_instances( $post_id, $instance_id ) {
|
||||
$dbi = $this->_registry->get( 'dbi.dbi' );
|
||||
$table_instances = $dbi->get_table_name( 'ai1ec_event_instances' );
|
||||
$table_posts = $dbi->get_table_name( 'posts' );
|
||||
$query = $dbi->prepare(
|
||||
'SELECT COUNT(i.id) FROM ' . $table_instances . ' i JOIN ' .
|
||||
$table_posts . ' p ON (p.ID = i.post_id) ' .
|
||||
'WHERE i.post_id = %d AND i.id > %d ' .
|
||||
'AND p.post_status = \'publish\'',
|
||||
$post_id,
|
||||
$instance_id
|
||||
);
|
||||
return (int)$dbi->get_var( $query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters past or out of range exception dates.
|
||||
*
|
||||
* @param Ai1ec_Event $event Event.
|
||||
*
|
||||
* @return array Filtered exception dates.
|
||||
*/
|
||||
protected function _filter_exception_dates( Ai1ec_Event $event ) {
|
||||
$start = (int)$event->get( 'start' )->format();
|
||||
$exception_dates = explode( ',', $event->get( 'exception_dates' ) );
|
||||
$dates = array();
|
||||
foreach ( $exception_dates as $date ) {
|
||||
$ex_date = (int)$this->_registry->get( 'date.time', $date )->format();
|
||||
if ( $ex_date > $start ) {
|
||||
$dates[] = $date;
|
||||
}
|
||||
}
|
||||
return $dates;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Modal class representing an event or an event instance.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.0
|
||||
* @instantiator new
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
|
||||
class Ai1ec_Event_Taxonomy extends Ai1ec_Base {
|
||||
|
||||
/**
|
||||
* @var string Name of categories taxonomy.
|
||||
*/
|
||||
const CATEGORIES = 'events_categories';
|
||||
|
||||
/**
|
||||
* @var string Name of tags taxonomy.
|
||||
*/
|
||||
const TAGS = 'events_tags';
|
||||
|
||||
/**
|
||||
* @var string Name of feeds taxonomy.
|
||||
*/
|
||||
const FEEDS = 'events_feeds';
|
||||
|
||||
/**
|
||||
* @var int ID of related post object
|
||||
*/
|
||||
protected $_post_id = 0;
|
||||
|
||||
/**
|
||||
* Store event ID in local variable.
|
||||
*
|
||||
* @param int $post_id ID of post being managed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( Ai1ec_Registry_Object $registry, $post_id = 0 ) {
|
||||
parent::__construct( $registry );
|
||||
$this->_post_id = (int)$post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ID of term. Optionally create it if it doesn't exist.
|
||||
*
|
||||
* @param string $term Name of term to create.
|
||||
* @param string $taxonomy Name of taxonomy to contain term within.
|
||||
* @param bool $is_id Set to true if $term is ID.
|
||||
* @param array $attrs Attributes to creatable entity.
|
||||
*
|
||||
* @return array|bool Associative array with term_id
|
||||
* and taxonomy keys or false on error
|
||||
*/
|
||||
public function initiate_term(
|
||||
$term,
|
||||
$taxonomy,
|
||||
$is_id = false,
|
||||
array $attrs = array()
|
||||
) {
|
||||
// cast to int to have it working with term_exists
|
||||
$term = ( $is_id ) ? (int) $term : $term;
|
||||
$term_to_check = term_exists( $term, $taxonomy );
|
||||
$to_return = array(
|
||||
'taxonomy' => $taxonomy
|
||||
);
|
||||
// if term doesn't exist, create it.
|
||||
if ( 0 === $term_to_check || null === $term_to_check ) {
|
||||
$alias_to_use = apply_filters( 'ai1ec_ics_import_alias', $term );
|
||||
// the filter will either return null, the term_id to use or the original $term
|
||||
// if the filter is not run. Thus in need to check that $term !== $alias_to_use
|
||||
if ( $alias_to_use && $alias_to_use !== $term ) {
|
||||
$to_return['term_id'] = (int) $alias_to_use;
|
||||
// check that the term matches the taxonomy
|
||||
$tax = $this->get_taxonomy_for_term_id( term_exists( (int) $alias_to_use ) );
|
||||
$to_return['taxonomy'] = $tax->taxonomy;
|
||||
} else {
|
||||
$term_to_check = wp_insert_term( $term, $taxonomy, $attrs );
|
||||
if ( is_wp_error( $term_to_check ) ) {
|
||||
return false;
|
||||
}
|
||||
$term_to_check = (object)$term_to_check;
|
||||
$to_return['term_id'] = (int)$term_to_check->term_id;
|
||||
}
|
||||
} else {
|
||||
$term_id = is_array( $term_to_check )
|
||||
? $term_to_check['term_id']
|
||||
: $term_to_check;
|
||||
$to_return['term_id'] = (int)$term_id;
|
||||
// when importing categories, use the mapping of the current site
|
||||
// so place the term in the current taxonomy
|
||||
if ( self::CATEGORIES === $taxonomy ) {
|
||||
// check that the term matches the taxonomy
|
||||
$tax = $this->get_taxonomy_for_term_id( $term_id );
|
||||
$to_return['taxonomy'] = $tax->taxonomy;
|
||||
}
|
||||
|
||||
}
|
||||
return $to_return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for terms setting to post.
|
||||
*
|
||||
* @param array $terms List of terms to set.
|
||||
* @param string $taxonomy Name of taxonomy to set terms to.
|
||||
* @param bool $append When true post may have multiple same instances.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function set_terms( array $terms, $taxonomy, $append = false ) {
|
||||
$result = wp_set_post_terms(
|
||||
$this->_post_id,
|
||||
$terms,
|
||||
$taxonomy,
|
||||
$append
|
||||
);
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return false;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update event categories.
|
||||
*
|
||||
* @param array $categories List of category IDs.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function set_categories( array $categories ) {
|
||||
return $this->set_terms( $categories, self::CATEGORIES );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update event tags.
|
||||
*
|
||||
* @param array $tags List of tag IDs.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function set_tags( array $tags ) {
|
||||
return $this->set_terms( $tags, self::TAGS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update event feed description.
|
||||
*
|
||||
* @param object $feed Feed object.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function set_feed( $feed ) {
|
||||
$feed_name = $feed->feed_url;
|
||||
// If the feed is not from an imported file, parse the url.
|
||||
if ( ! isset( $feed->feed_imported_file ) ) {
|
||||
$url_components = parse_url( $feed->feed_url );
|
||||
$feed_name = $url_components['host'];
|
||||
}
|
||||
$term = $this->initiate_term(
|
||||
$feed_name,
|
||||
self::FEEDS,
|
||||
false,
|
||||
array(
|
||||
'description' => $feed->feed_url,
|
||||
)
|
||||
);
|
||||
if ( false === $term ) {
|
||||
return false;
|
||||
}
|
||||
$term_id = $term['term_id'];
|
||||
return $this->set_terms( array( $term_id ), self::FEEDS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the taxonomy name from term id
|
||||
*
|
||||
* @param int $term
|
||||
*
|
||||
* @return stdClass The taxonomy nane
|
||||
*/
|
||||
public function get_taxonomy_for_term_id( $term_id ) {
|
||||
$db = $this->_registry->get( 'dbi.dbi' );
|
||||
return $db->get_row(
|
||||
$db->prepare(
|
||||
'SELECT terms_taxonomy.taxonomy FROM ' . $db->get_table_name( 'terms' ) .
|
||||
' AS terms INNER JOIN ' .
|
||||
$db->get_table_name( 'term_taxonomy' ) .
|
||||
' AS terms_taxonomy USING(term_id) '.
|
||||
'WHERE terms.term_id = %d LIMIT 1', $term_id )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Handles trash/delete operations.
|
||||
*
|
||||
* NOTICE: only operations on events entries themselve is handled.
|
||||
* If plugins need some extra handling - they must bind to appropriate
|
||||
* actions on their will.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
*
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Model
|
||||
*/
|
||||
class Ai1ec_Event_Trashing extends Ai1ec_Base {
|
||||
|
||||
/**
|
||||
* Trash/untrash/deletes child posts
|
||||
*
|
||||
* @param id $post_id
|
||||
* @param string $action
|
||||
*/
|
||||
protected function _manage_children( $post_id, $action ) {
|
||||
try {
|
||||
$ai1ec_event = $this->_registry->get( 'model.event', $post_id );
|
||||
if (
|
||||
$ai1ec_event->get( 'post' ) &&
|
||||
$ai1ec_event->get( 'recurrence_rules' )
|
||||
) {
|
||||
// when untrashing also get trashed object
|
||||
$children = $this->_registry->get( 'model.event.parent' )
|
||||
->get_child_event_objects( $ai1ec_event->get( 'post_id' ), $action === 'untrash' );
|
||||
$function = 'wp_' . $action . '_post';
|
||||
foreach ( $children as $child ) {
|
||||
$function( $child->get( 'post_id' ) );
|
||||
}
|
||||
}
|
||||
} catch ( Ai1ec_Event_Not_Found_Exception $exception ) {
|
||||
// ignore - not an event
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trashes child posts
|
||||
*
|
||||
* @param int $post_id
|
||||
*/
|
||||
public function trash_children( $post_id ) {
|
||||
$this->_manage_children( $post_id, 'trash' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete child posts
|
||||
*
|
||||
* @param int $post_id
|
||||
*/
|
||||
public function delete_children( $post_id ) {
|
||||
$this->_manage_children( $post_id, 'delete' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Untrashes child posts
|
||||
*
|
||||
* @param int $post_id
|
||||
*/
|
||||
public function untrash_children( $post_id ) {
|
||||
$this->_manage_children( $post_id, 'untrash' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle PRE (event) trashing.
|
||||
*
|
||||
* @wp_hook trash_post
|
||||
*
|
||||
* @param int $post_id ID of post, which was trashed.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function trash_post( $post_id ) {
|
||||
$api = $this->_registry->get( 'model.api.api-ticketing' );
|
||||
$post = get_post( $post_id );
|
||||
$restored_status = get_post_meta( $post_id, '_wp_trash_meta_status', true );
|
||||
$fields = array(
|
||||
'status' => 'trash'
|
||||
);
|
||||
$ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
|
||||
$message = $api->update_api_event_fields( $post, $fields, 'trash', $ajax );
|
||||
if ( null !== $message ) {
|
||||
if ( $ajax ) {
|
||||
wp_die( $message );
|
||||
} else {
|
||||
wp_redirect( $this->get_sendback_page( $post_id ) );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle POST (event) trashing.
|
||||
*
|
||||
* @wp_hook trashed_post
|
||||
*
|
||||
* @param int $post_id ID of post, which was trashed.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function trashed_post( $post_id ) {
|
||||
return $this->trash_children( $post_id );
|
||||
}
|
||||
|
||||
private function get_sendback_page( $post_id ) {
|
||||
$sendback = wp_get_referer();
|
||||
$page_base = Ai1ec_Wp_Uri_Helper::get_pagebase( $sendback ); //$_SERVER['REQUEST_URI'] );
|
||||
if ( 'post.php' === $page_base ) {
|
||||
return get_edit_post_link( $post_id, 'url' );
|
||||
} else {
|
||||
return admin_url( 'edit.php?post_type=ai1ec_event' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle PRE (event) untrashing.
|
||||
*
|
||||
* @wp_hook untrash_post
|
||||
*
|
||||
* @param int $post_id ID of post, which was untrashed.
|
||||
*
|
||||
* @return bool Success. Interrupt the action with exit is
|
||||
* the integration with API fails
|
||||
*/
|
||||
public function untrash_post ( $post_id ) {
|
||||
$api = $this->_registry->get( 'model.api.api-ticketing' );
|
||||
$post = get_post( $post_id );
|
||||
$restored_status = get_post_meta( $post_id, '_wp_trash_meta_status', true );
|
||||
$fields = array(
|
||||
'status' => $restored_status
|
||||
);
|
||||
$ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
|
||||
$message = $api->update_api_event_fields( $post, $fields, 'untrash', $ajax );
|
||||
if ( null !== $message ) {
|
||||
if ( $ajax ) {
|
||||
wp_die( $message );
|
||||
} else {
|
||||
wp_redirect( $this->get_sendback_page( $post_id ) );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle POST (event) untrashing.
|
||||
*
|
||||
* @wp_hook untrashed_post
|
||||
*
|
||||
* @param int $post_id ID of post, which was untrashed.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function untrashed_post( $post_id ) {
|
||||
return $this->untrash_children( $post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle PRE (event) deletion.
|
||||
*
|
||||
* Executed before post is deleted, but after meta is removed.
|
||||
*
|
||||
* @wp_hook delete_post
|
||||
*
|
||||
* @param int $post_id ID of post, which was trashed.
|
||||
*
|
||||
* @return bool Success. Interrupt the action with exit is
|
||||
* the integration with API fails
|
||||
*/
|
||||
public function before_delete_post( $post_id ) {
|
||||
$api = $this->_registry->get( 'model.api.api-ticketing' );
|
||||
$ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
|
||||
$message = $api->delete_api_event( $post_id, 'delete', $ajax );
|
||||
if ( null !== $message ) {
|
||||
if ( $ajax ) {
|
||||
wp_die( $message );
|
||||
} else {
|
||||
wp_redirect( $this->get_sendback_page( $post_id ) );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle POST (event) deletion.
|
||||
*
|
||||
* Executed before post is deleted, but after meta is removed.
|
||||
*
|
||||
* @wp_hook delete_post
|
||||
*
|
||||
* @param int $post_id ID of post, which was trashed.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function delete( $post_id ) {
|
||||
$post_id = (int)$post_id;
|
||||
$where = array( 'post_id' => (int)$post_id );
|
||||
$format = array( '%d' );
|
||||
$dbi = $this->_registry->get( 'dbi.dbi' );
|
||||
$success = $this->delete_children( $post_id );
|
||||
$success = $dbi->delete( 'ai1ec_events', $where, $format );
|
||||
$success = $this->_registry->get( 'model.event.instance' )->clean( $post_id );
|
||||
unset( $where, $dbi );
|
||||
return $success;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Authors filtering implementation.
|
||||
*
|
||||
* @instantiator new
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Filter
|
||||
*/
|
||||
class Ai1ec_Filter_Authors extends Ai1ec_Filter_Int {
|
||||
|
||||
public function get_field() {
|
||||
return 'p.post_author';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Categories filtering implementation.
|
||||
*
|
||||
* @instantiator new
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Filter
|
||||
*/
|
||||
class Ai1ec_Filter_Categories extends Ai1ec_Filter_Taxonomy {
|
||||
|
||||
public function get_taxonomy() {
|
||||
return 'events_categories';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Posts (events) filtering implementation.
|
||||
*
|
||||
* @instantiator new
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Filter
|
||||
*/
|
||||
class Ai1ec_Filter_Posts_By_Instance extends Ai1ec_Filter_Int {
|
||||
|
||||
public function get_field() {
|
||||
return 'i.id';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Base class for integers-based filters.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
*
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Filter
|
||||
*/
|
||||
abstract class Ai1ec_Filter_Int implements Ai1ec_Filter_Interface {
|
||||
|
||||
/**
|
||||
* @var Ai1ec_Registry_Object Injected object registry.
|
||||
*/
|
||||
protected $_registry = null;
|
||||
|
||||
/**
|
||||
* @var array Sanitized input values with only positive integers kept.
|
||||
*/
|
||||
protected $_values = array();
|
||||
|
||||
/**
|
||||
* Sanitize input values upon construction.
|
||||
*
|
||||
* @param Ai1ec_Registry_Object $registry Injected registry.
|
||||
* @param array $filter_values Values to sanitize.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
Ai1ec_Registry_Object $registry,
|
||||
array $filter_values = array()
|
||||
) {
|
||||
$this->_registry = $registry;
|
||||
$this->_values = array_filter(
|
||||
array_map(
|
||||
array( $this->_registry->get( 'primitive.int' ), 'positive' ),
|
||||
$filter_values
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* These simple filters does not require new joins.
|
||||
*
|
||||
* @return string Empty string is returned.
|
||||
*/
|
||||
public function get_join() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get condition part of query for single field.
|
||||
*
|
||||
* @param string $inner_operator Inner logics to use. It is ignored.
|
||||
*
|
||||
* @return string Conditional snippet for query.
|
||||
*/
|
||||
public function get_where( $inner_operator = null ) {
|
||||
if ( empty( $this->_values ) ) {
|
||||
return '';
|
||||
}
|
||||
return $this->get_field() . ' IN ( ' . join( ',', $this->_values ) . ' )';
|
||||
}
|
||||
|
||||
/**
|
||||
* Require ancestors to override this to build correct conditional snippet.
|
||||
*
|
||||
* @return string Column alias to use in condition.
|
||||
*/
|
||||
abstract public function get_field();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Filter provider interface.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
*
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Filter
|
||||
*/
|
||||
interface Ai1ec_Filter_Interface {
|
||||
|
||||
/**
|
||||
* Store user-input locally.
|
||||
*
|
||||
* @param Ai1ec_Registry_Object $registry Injected registry.
|
||||
* @param array $filter_values User provided input.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
Ai1ec_Registry_Object $registry,
|
||||
array $filter_values = array()
|
||||
);
|
||||
|
||||
/**
|
||||
* Return SQL snippet for `FROM` part.
|
||||
*
|
||||
* @return string Valid SQL snippet for `FROM` part.
|
||||
*/
|
||||
public function get_join();
|
||||
|
||||
/**
|
||||
* Return SQL snippet for `WHERE` part.
|
||||
*
|
||||
* Snippet should not be put in brackets - this will be performed
|
||||
* in upper level.
|
||||
*
|
||||
* @return string Valid SQL snippet.
|
||||
*/
|
||||
public function get_where();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Posts (events) filtering implementation.
|
||||
*
|
||||
* @instantiator new
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Filter
|
||||
*/
|
||||
class Ai1ec_Filter_Posts extends Ai1ec_Filter_Int {
|
||||
|
||||
public function get_field() {
|
||||
return 'e.post_id';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Tags filtering implementation.
|
||||
*
|
||||
* @instantiator new
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Filter
|
||||
*/
|
||||
class Ai1ec_Filter_Tags extends Ai1ec_Filter_Taxonomy {
|
||||
|
||||
public function get_taxonomy() {
|
||||
return 'events_tags';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Base class for taxonomies filtering.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
*
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.Filter
|
||||
*/
|
||||
abstract class Ai1ec_Filter_Taxonomy extends Ai1ec_Filter_Int {
|
||||
|
||||
/**
|
||||
* @var Ai1ec_Dbi Instance of database interface.
|
||||
*/
|
||||
protected $_dbi = null;
|
||||
|
||||
/**
|
||||
* Sanitize input values upon construction.
|
||||
*
|
||||
* @param Ai1ec_Registry_Object $registry Injected registry.
|
||||
* @param array $filter_values Values to sanitize.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
Ai1ec_Registry_Object $registry,
|
||||
array $filter_values = array()
|
||||
) {
|
||||
parent::__construct( $registry, $filter_values );
|
||||
$this->_dbi = $this->_registry->get( 'dbi.dbi' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build SQL snippet for `FROM` particle.
|
||||
*
|
||||
* @return string Valid SQL snippet.
|
||||
*/
|
||||
public function get_join() {
|
||||
if ( empty( $this->_values ) ) {
|
||||
return '';
|
||||
}
|
||||
$sql_query =
|
||||
'LEFT JOIN `{{RELATIONSHIPS_TABLE}}` AS `{{RELATIONSHIP_ALIAS}}` ' .
|
||||
'ON ( `e` . `post_id` = `{{RELATIONSHIP_ALIAS}}` . `object_id` ) ' .
|
||||
'LEFT JOIN `{{TAXONOMY_TABLE}}` AS `{{TAXONOMY_ALIAS}}` ' .
|
||||
'ON (' .
|
||||
'`{{RELATIONSHIP_ALIAS}}` . `term_taxonomy_id` = ' .
|
||||
'`{{TAXONOMY_ALIAS}}` . `term_taxonomy_id` ' .
|
||||
'AND `{{TAXONOMY_ALIAS}}` . taxonomy = {{TAXONOMY}} ' .
|
||||
')';
|
||||
return str_replace(
|
||||
array(
|
||||
'{{RELATIONSHIPS_TABLE}}',
|
||||
'{{RELATIONSHIP_ALIAS}}',
|
||||
'{{TAXONOMY_TABLE}}',
|
||||
'{{TAXONOMY_ALIAS}}',
|
||||
'{{TAXONOMY}}',
|
||||
),
|
||||
array(
|
||||
$this->_dbi->get_table_name( 'term_relationships' ),
|
||||
$this->_table_alias( 'term_relationships' ),
|
||||
$this->_dbi->get_table_name( 'term_taxonomy' ),
|
||||
$this->_table_alias( 'term_taxonomy' ),
|
||||
'\'' . addslashes( $this->get_taxonomy() ) . '\'',
|
||||
),
|
||||
$sql_query
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Required by parent class. Using internal abstractions.
|
||||
*
|
||||
* @return string Field name to use in `WHERE` particle.
|
||||
*/
|
||||
public function get_field() {
|
||||
return $this->_table_alias( 'term_taxonomy' ) . '.term_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the qualified name for the taxonomy.
|
||||
*
|
||||
* @return string Valid taxonomy name (see `term_taxonomy` table).
|
||||
*/
|
||||
abstract public function get_taxonomy();
|
||||
|
||||
/**
|
||||
* Generate table alias given taxonomy.
|
||||
*
|
||||
* @param string $table Table to generate alias for.
|
||||
*
|
||||
* @return string Table alias.
|
||||
*/
|
||||
protected function _table_alias( $table ) {
|
||||
return $table . '_' . $this->get_taxonomy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Post meta entries management.
|
||||
*
|
||||
* Meta entries management based on {@see Ai1ec_Meta} class.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.0
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
class Ai1ec_Meta_Post extends Ai1ec_Meta {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* User meta entries management.
|
||||
*
|
||||
* Meta entries management based on {@see Ai1ec_Meta} class.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.0
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
class Ai1ec_Meta_User extends Ai1ec_Meta {
|
||||
|
||||
/**
|
||||
* Get meta value for current user.
|
||||
*
|
||||
* @param string $meta_key Name of meta entry to get for current user.
|
||||
* @param mixed $default Value to return if no entry found.
|
||||
*
|
||||
* @return mixed Current user's option or $default if none found.
|
||||
*/
|
||||
public function get_current( $meta_key, $default = null ) {
|
||||
$user_id = 0;
|
||||
if ( is_callable( 'wp_get_current_user' ) ) {
|
||||
$user = wp_get_current_user();
|
||||
$user_id = (int)$user->ID;
|
||||
unset( $user );
|
||||
}
|
||||
if ( $user_id <= 0 ) {
|
||||
return $default;
|
||||
}
|
||||
return $this->get( $user_id, $meta_key, $default );
|
||||
}
|
||||
|
||||
/**
|
||||
* user_selected_tz method
|
||||
*
|
||||
* Get/set user selected (preferred) timezone.
|
||||
* If only {@see $user_id} is provided - method acts as getter.
|
||||
* Otherwise it acts as setter.
|
||||
*
|
||||
* @param int $user_id ID of user whose timezone is being checked/changed
|
||||
* @param string $new_value New timezone string value to set user preferrence
|
||||
* @param bool $force_update Set to true to force value update instead of add
|
||||
*
|
||||
* @return mixed Return value depends on activity:
|
||||
* - [getter] string User preferred timezone name (might be empty string)
|
||||
* - [setter] bool Success of preferrence change
|
||||
*/
|
||||
public function user_selected_tz(
|
||||
$user_id,
|
||||
$new_value = NULL,
|
||||
$force_update = false
|
||||
) {
|
||||
$meta_key = 'ai1ec_timezone';
|
||||
$user_id = (int)$user_id;
|
||||
$old_value = $this->get(
|
||||
$user_id,
|
||||
$meta_key,
|
||||
NULL,
|
||||
true
|
||||
);
|
||||
if ( NULL !== $new_value ) {
|
||||
if ( ! in_array( $new_value, timezone_identifiers_list() ) ) {
|
||||
return false;
|
||||
}
|
||||
$success = false;
|
||||
if ( true === $force_update || ! empty( $old_value ) ) {
|
||||
$success = update_user_meta(
|
||||
$user_id,
|
||||
$meta_key,
|
||||
$new_value,
|
||||
$old_value
|
||||
);
|
||||
} else {
|
||||
$success = add_user_meta(
|
||||
$user_id,
|
||||
$meta_key,
|
||||
$new_value,
|
||||
true
|
||||
);
|
||||
if ( false === $success ) {
|
||||
return $this->user_selected_tz(
|
||||
$user_id,
|
||||
$new_value,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
return $old_value;
|
||||
}
|
||||
|
||||
}
|
||||
194
wp-content/plugins/all-in-one-event-calendar/app/model/meta.php
Normal file
194
wp-content/plugins/all-in-one-event-calendar/app/model/meta.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Abstract class for meta entries management.
|
||||
*
|
||||
* Via use of cache allows object-based access to meta entries.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.0
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
abstract class Ai1ec_Meta extends Ai1ec_App {
|
||||
|
||||
/**
|
||||
* @var string Name of base object for storage.
|
||||
*/
|
||||
protected $_object = '';
|
||||
|
||||
/**
|
||||
* @var Ai1ec_Cache_Memory In-memory cache operator.
|
||||
*/
|
||||
protected $_cache = null;
|
||||
|
||||
/**
|
||||
* Initialize instance-specific in-memory cache storage.
|
||||
*
|
||||
* @return void Method does not return.
|
||||
*/
|
||||
protected function _initialize() {
|
||||
$class = get_class( $this );
|
||||
$this->_object = strtolower(
|
||||
substr( $class, strlen( __CLASS__ ) + 1 )
|
||||
);
|
||||
$this->_cache = $this->_registry->get( 'cache.memory' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new entry if it does not exist and cache provided value.
|
||||
*
|
||||
* @param string $object_id ID of object to store.
|
||||
* @param string $key Key particle for ID to store.
|
||||
* @param mixed $value Serializable value to store.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
final public function add( $object_id, $key, $value ) {
|
||||
if ( ! $this->_add( $object_id, $key, $value ) ) {
|
||||
return false;
|
||||
}
|
||||
$this->_cache->set( $this->_cache_key( $object_id, $key ), $value );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing entry and cache it's value.
|
||||
*
|
||||
* @param string $object_id ID of object to store.
|
||||
* @param string $key Key particle for ID to store.
|
||||
* @param mixed $value Serializable value to store.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
final public function update( $object_id, $key, $value ) {
|
||||
if ( ! $this->_update( $object_id, $key, $value ) ) {
|
||||
return false;
|
||||
}
|
||||
$this->_cache->set( $this->_cache_key( $object_id, $key ), $value );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object value - from cache or actual store.
|
||||
*
|
||||
* @param string $object_id ID of object to get.
|
||||
* @param string $key Key particle for ID to get.
|
||||
* @param mixed $default Value to return if nothing found.
|
||||
*
|
||||
* @return mixed Value stored or {$default}.
|
||||
*/
|
||||
final public function get( $object_id, $key, $default = null ) {
|
||||
$cache_key = $this->_cache_key( $object_id, $key );
|
||||
$value = $this->_cache->get( $cache_key, $default );
|
||||
if ( $default === $value ) {
|
||||
$value = $this->_get( $object_id, $key );
|
||||
$this->_cache->set( $cache_key, $value );
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update an entry cache new value.
|
||||
*
|
||||
* @param string $object_id ID of object to store.
|
||||
* @param string $key Key particle for ID to store.
|
||||
* @param mixed $value Serializable value to store.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
final public function set( $object_id, $key, $value ) {
|
||||
if ( ! $this->get( $object_id, $key ) ) {
|
||||
if ( ! $this->_add( $object_id, $key, $value ) ) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if ( ! $this->_update( $object_id, $key, $value ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$this->_cache->set( $this->_cache_key( $object_id, $key ), $value );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove object entry based on ID and key.
|
||||
*
|
||||
* @param string $object_id ID of object to remove.
|
||||
* @param string $key Key particle for ID to remove.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
final public function delete( $object_id, $key ) {
|
||||
$this->_cache->delete( $this->_cache_key( $object_id, $key ) );
|
||||
return $this->_delete( $object_id, $key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object value from actual store.
|
||||
*
|
||||
* @param string $object_id ID of object to get.
|
||||
* @param string $key Key particle for ID to get.
|
||||
*
|
||||
* @return mixed Value as found.
|
||||
*/
|
||||
protected function _get( $object_id, $key ) {
|
||||
$function = 'get_' . $this->_object . '_meta';
|
||||
return $function( $object_id, $key, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new entry if it does not exist.
|
||||
*
|
||||
* @param string $object_id ID of object to store.
|
||||
* @param string $key Key particle for ID to store.
|
||||
* @param mixed $value Serializable value to store.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
protected function _add( $object_id, $key, $value ) {
|
||||
$function = 'add_' . $this->_object . '_meta';
|
||||
return $function( $object_id, $key, $value, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing entry.
|
||||
*
|
||||
* @param string $object_id ID of object to store.
|
||||
* @param string $key Key particle for ID to store.
|
||||
* @param mixed $value Serializable value to store.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
protected function _update( $object_id, $key, $value ) {
|
||||
$function = 'update_' . $this->_object . '_meta';
|
||||
return $function( $object_id, $key, $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove object entry based on ID and key.
|
||||
*
|
||||
* @param string $object_id ID of object to remove.
|
||||
* @param string $key Key particle for ID to remove.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
protected function _delete( $object_id, $key ) {
|
||||
$function = 'delete_' . $this->_object . '_meta';
|
||||
return $function( $object_id, $key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate key for use with cache engine.
|
||||
*
|
||||
* @param string $object_id ID of object.
|
||||
* @param string $key Key particle for ID.
|
||||
*
|
||||
* @return string Single identifier for given keys.
|
||||
*/
|
||||
protected function _cache_key( $object_id, $key ) {
|
||||
static $separator = "\0";
|
||||
return $object_id . $separator . $key;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Options management class.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.0
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
class Ai1ec_Option extends Ai1ec_App {
|
||||
|
||||
/**
|
||||
* @var Ai1ec_Cache_Memory In-memory cache storage engine for fast access.
|
||||
*/
|
||||
protected $_cache = null;
|
||||
|
||||
/**
|
||||
* @var Ai1ec_Registry_Object instance of the registry object.
|
||||
*/
|
||||
protected $_registry;
|
||||
|
||||
/**
|
||||
* Add cache instance to object scope.
|
||||
*
|
||||
* @param Ai1ec_Registry_Object $registry Registry object.
|
||||
*
|
||||
* @return Ai1ec_Option
|
||||
*/
|
||||
public function __construct( Ai1ec_Registry_Object $registry ) {
|
||||
$this->_registry = $registry;
|
||||
$this->_cache = $registry->get( 'cache.memory' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an option if it does not exist.
|
||||
*
|
||||
* @param string $name Key to put value under.
|
||||
* @param mixed $value Value to put to storage.
|
||||
* @param bool $autoload Set to true to load on start.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function add( $name, $value, $autoload = false ) {
|
||||
$autoload = $this->_parse_autoload( $autoload );
|
||||
if ( ! add_option( $name, $value, '', $autoload ) ) {
|
||||
return false;
|
||||
}
|
||||
$this->_cache->set( $name, $value );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an option if it does not exist, or update existing.
|
||||
*
|
||||
* @param string $name Key to put value under.
|
||||
* @param mixed $value Value to put to storage.
|
||||
* @param bool $autoload Set to true to load on start.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function set( $name, $value, $autoload = false ) {
|
||||
$comparator = "\0t\0";
|
||||
if ( $this->get( $name, $comparator ) === $comparator ) {
|
||||
return $this->add( $name, $value, $autoload );
|
||||
}
|
||||
if ( ! update_option( $name, $value ) ) {
|
||||
return false;
|
||||
}
|
||||
$this->_cache->set( $name, $value );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from storage.
|
||||
*
|
||||
* @param string $name Key to retrieve.
|
||||
* @param mixed $default Value to return if key was not set previously.
|
||||
*
|
||||
* @return mixed Value from storage or {$default}.
|
||||
*/
|
||||
public function get( $name, $default = null ) {
|
||||
$value = $this->_cache->get( $name, $default );
|
||||
if ( $default === $value ) {
|
||||
$value = get_option( $name, $default );
|
||||
$this->_cache->set( $name, $value );
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete value from storage.
|
||||
*
|
||||
* @param string $name Key to delete.
|
||||
*
|
||||
* @wp_hook deleted_option Fire after deletion.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function delete( $name ) {
|
||||
$this->_cache->delete( $name );
|
||||
if ( 'deleted_option' === current_filter() ) {
|
||||
return true; // avoid loops
|
||||
}
|
||||
return delete_option( $name );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert autoload flag input to value recognized by WordPress.
|
||||
*
|
||||
* @param bool $input Autoload flag value.
|
||||
*
|
||||
* @return string Autoload identifier.
|
||||
*/
|
||||
protected function _parse_autoload( $input ) {
|
||||
return $input ? 'yes' : 'no';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class to manage the review made by the user.
|
||||
* The data are saved inside the wp_option table
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
*
|
||||
* @package AI1ECCFG
|
||||
*/
|
||||
class Ai1ec_Review extends Ai1ec_Base {
|
||||
|
||||
const EMAIL_FEEDBACK_DESTINATION = 'info@time.ly';
|
||||
const OPTION_KEY = '_ai1ec_review';
|
||||
const FEEDBACK_FIELD = 'feedback';
|
||||
const RELEASE_DATE_FIELD = 'release_date';
|
||||
const PUBLISHED_THRESHOLD = 15;
|
||||
const FUTURE_EVENTS_THRESHOLD = 3;
|
||||
const WEEK_OFFSET_SEC_FIELD = 604800; //1 week in seconds (7 * 86400)
|
||||
|
||||
/**
|
||||
* review colletion.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_review_items = array();
|
||||
|
||||
/**
|
||||
* Option class.
|
||||
*
|
||||
* @var Ai1ec_Option
|
||||
*/
|
||||
protected $_option;
|
||||
|
||||
public function __construct( Ai1ec_Registry_Object $registry ) {
|
||||
parent::__construct( $registry );
|
||||
$this->_option = $registry->get( 'model.option' );
|
||||
$this->_review_items = $this->_get_array( self::OPTION_KEY );
|
||||
}
|
||||
|
||||
private function _get_array( $option_key ) {
|
||||
$items = $this->_option->get( $option_key );
|
||||
if ( ! is_array( $items ) ) {
|
||||
$items = array();
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
private function _get_field( $field_name, $default_value ) {
|
||||
if ( isset( $this->_review_items[$field_name] ) ) {
|
||||
$value = $this->_review_items[$field_name];
|
||||
if ( ai1ec_is_blank( $value ) ) {
|
||||
return $default_value;
|
||||
} else {
|
||||
return $value;
|
||||
}
|
||||
} else {
|
||||
return $default_value;
|
||||
}
|
||||
}
|
||||
|
||||
protected function _save( array $values ) {
|
||||
foreach ($values as $key => $value) {
|
||||
$this->_review_items[$key] = $value;
|
||||
}
|
||||
$this->_option->set( self::OPTION_KEY, $this->_review_items );
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function _is_show_box_review() {
|
||||
//only show for admins
|
||||
if ( false === is_admin() ) {
|
||||
return false;
|
||||
}
|
||||
$user_id = get_current_user_id();
|
||||
if ( empty( $user_id ) ) {
|
||||
return false;
|
||||
}
|
||||
//if the user already gave his feedback does not ask him again
|
||||
if ( $this->_has_feedback( $user_id ) ) {
|
||||
return false;
|
||||
}
|
||||
$release_date_str = $this->_get_field( self::RELEASE_DATE_FIELD, '' );
|
||||
if ( ai1ec_is_blank( $release_date_str ) ) {
|
||||
//the first time this page is loaded is save the moment as the release date
|
||||
//to just ask the user a review after 2 weeks
|
||||
$this->_save( array(
|
||||
self::RELEASE_DATE_FIELD => $this->_registry->get( 'date.time' )->format()
|
||||
) );
|
||||
return false;
|
||||
} else {
|
||||
$current_time = $this->_registry->get( 'date.time' );
|
||||
$release_date = $this->_registry->get( 'date.time', $release_date_str );
|
||||
$diff_sec = $release_date->diff_sec( $current_time );
|
||||
//verify is passed 2 weeks after we release this feature
|
||||
if ( $diff_sec < self::WEEK_OFFSET_SEC_FIELD ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//count the published events
|
||||
$event_count = count_user_posts( $user_id, AI1EC_POST_TYPE, true );
|
||||
if ( $event_count < self::PUBLISHED_THRESHOLD ) {
|
||||
return false;
|
||||
}
|
||||
//count the future events
|
||||
$count_future_events = apply_filters( 'ai1ec_count_future_events', $user_id );
|
||||
if ( $count_future_events < self::FUTURE_EVENTS_THRESHOLD ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function get_content( $theme_loader ) {
|
||||
if ( $this->_is_show_box_review() ) {
|
||||
$current_user = wp_get_current_user();
|
||||
$review_args = array();
|
||||
if ( $current_user instanceof WP_User ) {
|
||||
$review_args['contact_name'] = $current_user->display_name;
|
||||
$review_args['contact_email'] = $current_user->user_email;
|
||||
} else {
|
||||
$review_args['contact_name'] = '';
|
||||
$review_args['contact_email'] = '';
|
||||
}
|
||||
$review_args['site_url'] = get_option( 'siteurl' );
|
||||
$theme_loader = $this->_registry->get( 'theme.loader' );
|
||||
return $theme_loader->get_file( 'box_ask_customer_review.php', $review_args, true )->get_content();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function save_feedback_review() {
|
||||
$user_id = get_current_user_id();
|
||||
if ( empty( $user_id ) ) {
|
||||
throw new Exception( 'User not identified' );
|
||||
}
|
||||
if ( ai1ec_is_blank( $_POST['feedback'] ) ||
|
||||
!in_array( $_POST['feedback'], array( 'y', 'n' ) ) ) {
|
||||
throw new Exception( 'The field is not filled or invalid' );
|
||||
}
|
||||
$values = $this->_get_field( self::FEEDBACK_FIELD, null );
|
||||
if ( null === $values ) {
|
||||
$values = array();
|
||||
}
|
||||
$values[ $user_id ] = $_POST['feedback'];
|
||||
$this->_save( array(
|
||||
self::FEEDBACK_FIELD => $values
|
||||
) );
|
||||
}
|
||||
|
||||
protected function _has_feedback( $user_id ) {
|
||||
$values = $this->_get_field( self::FEEDBACK_FIELD, null );
|
||||
if ( null === $values ) {
|
||||
return false;
|
||||
}
|
||||
$user = (string) $user_id;
|
||||
$value = isset( $values[$user] ) ? $values[$user] : '';
|
||||
return 0 === strcasecmp( 'y', $value ) || 0 === strcasecmp( 'n', $value );
|
||||
}
|
||||
|
||||
public function send_feedback_message() {
|
||||
if ( ai1ec_is_blank( $_POST['name'] ) ||
|
||||
ai1ec_is_blank( $_POST['email'] ) ||
|
||||
ai1ec_is_blank( $_POST['site'] ) ||
|
||||
ai1ec_is_blank( $_POST['message'] )
|
||||
) {
|
||||
throw new Exception( 'All fields are required' );
|
||||
}
|
||||
$subject = __( 'Feedback provided by user', AI1EC_PLUGIN_NAME );
|
||||
$content = sprintf( '<b>%s:</b><br/>%s<br/><br/><b>%s:</b><br/>%s<br/><br/><b>%s:</b><br/>%s<br/><br/><b>%s:</b><br/>%s',
|
||||
__( 'Name', AI1EC_PLUGIN_NAME ),
|
||||
$_POST['name'],
|
||||
__( 'E-mail', AI1EC_PLUGIN_NAME ),
|
||||
$_POST['email'],
|
||||
__( 'Site URL', AI1EC_PLUGIN_NAME ),
|
||||
$_POST['site'],
|
||||
__( 'Message', AI1EC_PLUGIN_NAME ),
|
||||
nl2br( $_POST['message'] )
|
||||
);
|
||||
$dispatcher = $this->_registry->get(
|
||||
'notification.email',
|
||||
$content,
|
||||
explode( ',', self::EMAIL_FEEDBACK_DESTINATION ),
|
||||
$subject
|
||||
);
|
||||
$headers = array(
|
||||
'Content-type: text/html',
|
||||
sprintf( 'From: %s <%s>', $_POST['name'], $_POST['email'])
|
||||
);
|
||||
if ( $dispatcher->send( $headers ) ) {
|
||||
$_POST['feedback'] = 'n';
|
||||
$this->save_feedback_review();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,879 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Search Event.
|
||||
*
|
||||
* @author Time.ly Network Inc.
|
||||
* @since 2.0
|
||||
*
|
||||
* @package AI1EC
|
||||
* @subpackage AI1EC.search
|
||||
*/
|
||||
class Ai1ec_Event_Search extends Ai1ec_Base {
|
||||
|
||||
/**
|
||||
* @var Ai1ec_Dbi instance
|
||||
*/
|
||||
private $_dbi = null;
|
||||
|
||||
/**
|
||||
* Caches the ids of the last 'between' query
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_ids_between_cache = array();
|
||||
|
||||
/**
|
||||
* Creates local DBI instance.
|
||||
*/
|
||||
public function __construct( Ai1ec_Registry_Object $registry ){
|
||||
parent::__construct( $registry );
|
||||
$this->_dbi = $this->_registry->get( 'dbi.dbi' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_cached_between_ids() {
|
||||
return $this->_ids_between_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the event object with the given post ID.
|
||||
*
|
||||
* Uses the WP cache to make this more efficient if possible.
|
||||
*
|
||||
* @param int $post_id The ID of the post associated.
|
||||
* @param bool|int $instance_id Instance ID, to fetch post details for.
|
||||
*
|
||||
* @return Ai1ec_Event The associated event object.
|
||||
*/
|
||||
public function get_event( $post_id, $instance_id = false ) {
|
||||
$post_id = (int)$post_id;
|
||||
$instance_id = (int)$instance_id;
|
||||
if ( $instance_id < 1 ) {
|
||||
$instance_id = false;
|
||||
}
|
||||
return $this->_registry->get( 'model.event', $post_id, $instance_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return events falling within some time range.
|
||||
*
|
||||
* Return all events starting after the given start time and before the
|
||||
* given end time that the currently logged in user has permission to view.
|
||||
* If $spanning is true, then also include events that span this
|
||||
* period. All-day events are returned first.
|
||||
*
|
||||
* @param Ai1ec_Date_Time $start Limit to events starting after this.
|
||||
* @param Ai1ec_Date_Time $end Limit to events starting before this.
|
||||
* @param array $filter Array of filters for the events returned:
|
||||
* ['cat_ids'] => list of category IDs;
|
||||
* ['tag_ids'] => list of tag IDs;
|
||||
* ['post_ids'] => list of post IDs;
|
||||
* ['auth_ids'] => list of author IDs;
|
||||
* ['instance_ids'] => list of events
|
||||
* instance ids;
|
||||
* @param bool $spanning Also include events that span this period.
|
||||
* @param bool $single_day This parameter is added for oneday view.
|
||||
* Query should find events lasting in
|
||||
* particular day instead of checking dates
|
||||
* range. If you need to call this method
|
||||
* with $single_day set to true consider
|
||||
* using method get_events_for_day. This
|
||||
* parameter matters only if $spanning is set
|
||||
* to false.
|
||||
*
|
||||
* @return array List of matching event objects.
|
||||
*/
|
||||
public function get_events_between(
|
||||
Ai1ec_Date_Time $start,
|
||||
Ai1ec_Date_Time $end,
|
||||
array $filter = array(),
|
||||
$spanning = false,
|
||||
$single_day = false
|
||||
) {
|
||||
// Query arguments
|
||||
$args = array(
|
||||
$start->format_to_gmt(),
|
||||
$end->format_to_gmt(),
|
||||
);
|
||||
|
||||
// Get post status Where snippet and associated SQL arguments
|
||||
$where_parameters = $this->_get_post_status_sql();
|
||||
$post_status_where = $where_parameters['post_status_where'];
|
||||
$args = array_merge( $args, $where_parameters['args'] );
|
||||
|
||||
// Get the Join (filter_join) and Where (filter_where) statements based
|
||||
// on $filter elements specified
|
||||
$filter = $this->_get_filter_sql( $filter );
|
||||
|
||||
$ai1ec_localization_helper = $this->_registry->get( 'p28n.wpml' );
|
||||
|
||||
$wpml_join_particle = $ai1ec_localization_helper
|
||||
->get_wpml_table_join( 'p.ID' );
|
||||
|
||||
$wpml_where_particle = $ai1ec_localization_helper
|
||||
->get_wpml_table_where();
|
||||
|
||||
if ( $spanning ) {
|
||||
$spanning_string = 'i.end > %d AND i.start < %d ';
|
||||
} elseif ( $single_day ) {
|
||||
$spanning_string = 'i.end >= %d AND i.start <= %d ';
|
||||
} else {
|
||||
$spanning_string = 'i.start BETWEEN %d AND %d ';
|
||||
}
|
||||
|
||||
$sql = '
|
||||
SELECT
|
||||
`p`.*,
|
||||
`e`.`post_id`,
|
||||
`i`.`id` AS `instance_id`,
|
||||
`i`.`start` AS `start`,
|
||||
`i`.`end` AS `end`,
|
||||
`e`.`timezone_name` AS `timezone_name`,
|
||||
`e`.`allday` AS `event_allday`,
|
||||
`e`.`recurrence_rules`,
|
||||
`e`.`exception_rules`,
|
||||
`e`.`recurrence_dates`,
|
||||
`e`.`exception_dates`,
|
||||
`e`.`venue`,
|
||||
`e`.`country`,
|
||||
`e`.`address`,
|
||||
`e`.`city`,
|
||||
`e`.`province`,
|
||||
`e`.`postal_code`,
|
||||
`e`.`instant_event`,
|
||||
`e`.`show_map`,
|
||||
`e`.`contact_name`,
|
||||
`e`.`contact_phone`,
|
||||
`e`.`contact_email`,
|
||||
`e`.`contact_url`,
|
||||
`e`.`cost`,
|
||||
`e`.`ticket_url`,
|
||||
`e`.`ical_feed_url`,
|
||||
`e`.`ical_source_url`,
|
||||
`e`.`ical_organizer`,
|
||||
`e`.`ical_contact`,
|
||||
`e`.`ical_uid`,
|
||||
`e`.`longitude`,
|
||||
`e`.`latitude`
|
||||
FROM
|
||||
' . $this->_dbi->get_table_name( 'ai1ec_events' ) . ' e
|
||||
INNER JOIN
|
||||
' . $this->_dbi->get_table_name( 'posts' ) . ' p
|
||||
ON ( `p`.`ID` = `e`.`post_id` )
|
||||
' . $wpml_join_particle . '
|
||||
INNER JOIN
|
||||
' . $this->_dbi->get_table_name( 'ai1ec_event_instances' ) . ' i
|
||||
ON ( `e`.`post_id` = `i`.`post_id` )
|
||||
' . $filter['filter_join'] . '
|
||||
WHERE
|
||||
post_type = \'' . AI1EC_POST_TYPE . '\'
|
||||
' . $wpml_where_particle . '
|
||||
AND
|
||||
' . $spanning_string . '
|
||||
' . $filter['filter_where'] . '
|
||||
' . $post_status_where . '
|
||||
GROUP BY
|
||||
`i`.`id`
|
||||
ORDER BY
|
||||
`e` . `allday` DESC,
|
||||
`i` . `start` ASC,
|
||||
`p` . `post_title` ASC';
|
||||
|
||||
$query = $this->_dbi->prepare( $sql, $args );
|
||||
$events = $this->_dbi->get_results( $query, ARRAY_A );
|
||||
|
||||
$id_list = array();
|
||||
$id_instance_list = array();
|
||||
foreach ( $events as $event ) {
|
||||
|
||||
if ( ! in_array( $event['post_id'], $id_list, true ) ) {
|
||||
$id_list[] = $event['post_id'];
|
||||
}
|
||||
|
||||
$id_instance_list[] = array(
|
||||
'id' => $event['post_id'],
|
||||
'instance_id' => $event['instance_id'],
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! empty( $id_list ) ) {
|
||||
update_meta_cache( 'post', $id_list );
|
||||
$this->_ids_between_cache = $id_instance_list;
|
||||
}
|
||||
|
||||
foreach ( $events as &$event ) {
|
||||
$event['allday'] = $this->_is_all_day( $event );
|
||||
$event = $this->_registry->get( 'model.event', $event );
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_events_relative_to function
|
||||
*
|
||||
* Return all events starting after the given reference time, limiting the
|
||||
* result set to a maximum of $limit items, offset by $page_offset. A
|
||||
* negative $page_offset can be provided, which will return events *before*
|
||||
* the reference time, as expected.
|
||||
*
|
||||
* @param int $time limit to events starting after this (local) UNIX time
|
||||
* @param int $limit return a maximum of this number of items
|
||||
* @param int $page_offset offset the result set by $limit times this number
|
||||
* @param array $filter Array of filters for the events returned.
|
||||
* ['cat_ids'] => non-associatative array of category IDs
|
||||
* ['tag_ids'] => non-associatative array of tag IDs
|
||||
* ['post_ids'] => non-associatative array of post IDs
|
||||
* ['auth_ids'] => non-associatative array of author IDs
|
||||
* ['instance_ids'] => non-associatative array of author IDs
|
||||
* @param int $last_day Last day (time), that was displayed.
|
||||
* NOTE FROM NICOLA: be careful, if you want a query with events
|
||||
* that have a start date which is greater than today, pass 0 as
|
||||
* this parameter. If you pass false ( or pass nothing ) you end up with a query
|
||||
* with events that finish before today. I don't know the rationale
|
||||
* behind this but that's how it works
|
||||
* @param bool $unique Whether display only unique events and don't
|
||||
* duplicate results with other instances or not.
|
||||
*
|
||||
* @return array five-element array:
|
||||
* ['events'] an array of matching event objects
|
||||
* ['prev'] true if more previous events
|
||||
* ['next'] true if more next events
|
||||
* ['date_first'] UNIX timestamp (date part) of first event
|
||||
* ['date_last'] UNIX timestamp (date part) of last event
|
||||
*/
|
||||
public function get_events_relative_to(
|
||||
$time,
|
||||
$limit = 0,
|
||||
$page_offset = 0,
|
||||
$filter = array(),
|
||||
$last_day = false,
|
||||
$unique = false
|
||||
) {
|
||||
$localization_helper = $this->_registry->get( 'p28n.wpml' );
|
||||
$settings = $this->_registry->get( 'model.settings' );
|
||||
|
||||
|
||||
// Even if there ARE more than 5 times the limit results - we shall not
|
||||
// try to fetch and display these, as it would crash system
|
||||
$limit = preg_replace('/\D/', '', $limit);
|
||||
$upper_boundary = $limit;
|
||||
if (
|
||||
$settings->get( 'agenda_include_entire_last_day' ) &&
|
||||
( false !== $last_day )
|
||||
) {
|
||||
$upper_boundary *= 5;
|
||||
}
|
||||
|
||||
// Convert timestamp to GMT time
|
||||
$time = $this->_registry->get(
|
||||
'date.system'
|
||||
)->get_current_rounded_time();
|
||||
// Get post status Where snippet and associated SQL arguments
|
||||
$where_parameters = $this->_get_post_status_sql();
|
||||
$post_status_where = $where_parameters['post_status_where'];
|
||||
|
||||
// Get the Join (filter_join) and Where (filter_where) statements based
|
||||
// on $filter elements specified
|
||||
$filter = $this->_get_filter_sql( $filter );
|
||||
|
||||
// Query arguments
|
||||
$args = array( $time );
|
||||
$args = array_merge( $args, $where_parameters['args'] );
|
||||
|
||||
if( $page_offset >= 0 ) {
|
||||
$first_record = $page_offset * $limit;
|
||||
} else {
|
||||
$first_record = ( -$page_offset - 1 ) * $limit;
|
||||
}
|
||||
|
||||
|
||||
$wpml_join_particle = $localization_helper
|
||||
->get_wpml_table_join( 'p.ID' );
|
||||
|
||||
$wpml_where_particle = $localization_helper
|
||||
->get_wpml_table_where();
|
||||
|
||||
$filter_date_clause = ( $page_offset >= 0 )
|
||||
? 'i.end >= %d '
|
||||
: 'i.start < %d ';
|
||||
$order_direction = ( $page_offset >= 0 ) ? 'ASC' : 'DESC';
|
||||
if ( false !== $last_day ) {
|
||||
if ( 0 == $last_day ) {
|
||||
$last_day = $time;
|
||||
}
|
||||
$filter_date_clause = ' i.end ';
|
||||
if ( $page_offset < 0 ) {
|
||||
$filter_date_clause .= '<';
|
||||
$order_direction = 'DESC';
|
||||
} else {
|
||||
$filter_date_clause .= '>';
|
||||
$order_direction = 'ASC';
|
||||
}
|
||||
$filter_date_clause .= ' %d ';
|
||||
$args[0] = $last_day;
|
||||
$first_record = 0;
|
||||
}
|
||||
$query = $this->_dbi->prepare(
|
||||
'SELECT DISTINCT p.*, e.post_id, i.id AS instance_id, ' .
|
||||
'i.start AS start, ' .
|
||||
'i.end AS end, ' .
|
||||
'e.allday AS event_allday, ' .
|
||||
'e.recurrence_rules, e.exception_rules, e.ticket_url, e.instant_event, e.recurrence_dates, e.exception_dates, ' .
|
||||
'e.venue, e.country, e.address, e.city, e.province, e.postal_code, ' .
|
||||
'e.show_map, e.contact_name, e.contact_phone, e.contact_email, e.cost, ' .
|
||||
'e.ical_feed_url, e.ical_source_url, e.ical_organizer, e.ical_contact, e.ical_uid, e.timezone_name, e.longitude, e.latitude ' .
|
||||
'FROM ' . $this->_dbi->get_table_name( 'ai1ec_events' ) . ' e ' .
|
||||
'INNER JOIN ' . $this->_dbi->get_table_name( 'posts' ) . ' p ON e.post_id = p.ID ' .
|
||||
$wpml_join_particle .
|
||||
' INNER JOIN ' . $this->_dbi->get_table_name( 'ai1ec_event_instances' ) . ' i ON e.post_id = i.post_id ' .
|
||||
$filter['filter_join'] .
|
||||
" WHERE post_type = '" . AI1EC_POST_TYPE . "' " .
|
||||
' AND ' . $filter_date_clause .
|
||||
$wpml_where_particle .
|
||||
$filter['filter_where'] .
|
||||
$post_status_where .
|
||||
( $unique ? ' GROUP BY e.post_id' : '' ) .
|
||||
// Reverse order when viewing negative pages, to get correct set of
|
||||
// records. Then reverse results later to order them properly.
|
||||
' ORDER BY i.start ' . $order_direction .
|
||||
', post_title ' . $order_direction .
|
||||
' LIMIT ' . $first_record . ', ' . $upper_boundary,
|
||||
$args
|
||||
);
|
||||
|
||||
$events = $this->_dbi->get_results( $query, ARRAY_A );
|
||||
|
||||
// Limit the number of records to convert to data-object
|
||||
$events = $this->_limit_result_set(
|
||||
$events,
|
||||
$limit,
|
||||
( false !== $last_day )
|
||||
);
|
||||
|
||||
// Reorder records if in negative page offset
|
||||
if( $page_offset < 0 ) {
|
||||
$events = array_reverse( $events );
|
||||
}
|
||||
|
||||
$date_first = $date_last = NULL;
|
||||
|
||||
foreach ( $events as &$event ) {
|
||||
$event['allday'] = $this->_is_all_day( $event );
|
||||
$event = $this->_registry->get( 'model.event', $event );
|
||||
if ( null === $date_first ) {
|
||||
$date_first = $event->get( 'start' );
|
||||
}
|
||||
$date_last = $event->get( 'start' );
|
||||
}
|
||||
$date_first = $this->_registry->get( 'date.time', $date_first );
|
||||
$date_last = $this->_registry->get( 'date.time', $date_last );
|
||||
// jus show next/prev links, in case no event found is shown.
|
||||
$next = true;
|
||||
$prev = true;
|
||||
|
||||
return array(
|
||||
'events' => $events,
|
||||
'prev' => $prev,
|
||||
'next' => $next,
|
||||
'date_first' => $date_first,
|
||||
'date_last' => $date_last,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* get_events_relative_to_reference function
|
||||
*
|
||||
* Return all events starting after the given date reference, limiting the
|
||||
* result set to a maximum of $limit items, offset by $page_offset. A
|
||||
* negative $page_offset can be provided, which will return events *before*
|
||||
* the reference time, as expected.
|
||||
*
|
||||
* @param int $date_reference if page_offset is greater than or equal to zero, events with start date greater than the date_reference will be returned
|
||||
* otherwise events with start date less than the date_reference will be returned.
|
||||
* @param int $limit return a maximum of this number of items
|
||||
* @param int $page_offset offset the result set by $limit times this number
|
||||
* @param array $filter Array of filters for the events returned.
|
||||
* ['cat_ids'] => non-associatative array of category IDs
|
||||
* ['tag_ids'] => non-associatative array of tag IDs
|
||||
* ['post_ids'] => non-associatative array of post IDs
|
||||
* ['auth_ids'] => non-associatative array of author IDs
|
||||
* ['instance_ids'] => non-associatative array of author IDs
|
||||
* @param bool $unique Whether display only unique events and don't
|
||||
* duplicate results with other instances or not.
|
||||
*
|
||||
* @return array five-element array:
|
||||
* ['events'] an array of matching event objects
|
||||
* ['prev'] true if more previous events
|
||||
* ['next'] true if more next events
|
||||
* ['date_first'] UNIX timestamp (date part) of first event
|
||||
* ['date_last'] UNIX timestamp (date part) of last event
|
||||
*/
|
||||
public function get_events_relative_to_reference( $date_reference, $limit = 0, $page_offset = 0, $filter = array(), $unique = false ) {
|
||||
$localization_helper = $this->_registry->get( 'p28n.wpml' );
|
||||
$settings = $this->_registry->get( 'model.settings' );
|
||||
|
||||
// Even if there ARE more than 5 times the limit results - we shall not
|
||||
// try to fetch and display these, as it would crash system
|
||||
$limit = preg_replace( '/\D/', '', $limit );
|
||||
|
||||
// Convert timestamp to GMT time
|
||||
if ( 0 == $date_reference ) {
|
||||
$timezone = $this->_registry->get( 'date.timezone' )->get( $settings->get( 'timezone_string' ) );
|
||||
$current_time = new DateTime( 'now' );
|
||||
$current_time->setTimezone( $timezone );
|
||||
$time = $current_time->format( 'U' );
|
||||
} else {
|
||||
$time = $date_reference;
|
||||
}
|
||||
|
||||
// Get post status Where snippet and associated SQL arguments
|
||||
$where_parameters = $this->_get_post_status_sql();
|
||||
$post_status_where = $where_parameters['post_status_where'];
|
||||
|
||||
// Get the Join (filter_join) and Where (filter_where) statements based
|
||||
// on $filter elements specified
|
||||
$filter = $this->_get_filter_sql( $filter );
|
||||
|
||||
// Query arguments
|
||||
$args = array( $time );
|
||||
$args = array_merge( $args, $where_parameters['args'] );
|
||||
|
||||
if ( 0 == $date_reference ) {
|
||||
if ( $page_offset >= 0 ) {
|
||||
$filter_date_clause = 'i.end >= %d ';
|
||||
$order_direction = 'ASC';
|
||||
} else {
|
||||
$filter_date_clause = 'i.start < %d ';
|
||||
$order_direction = 'DESC';
|
||||
}
|
||||
} else {
|
||||
if ( $page_offset < 0 ) {
|
||||
$filter_date_clause = 'i.end < %d ';
|
||||
$order_direction = 'DESC';
|
||||
} else {
|
||||
$filter_date_clause = 'i.end >= %d ';
|
||||
$order_direction = 'ASC';
|
||||
}
|
||||
}
|
||||
if ( $page_offset >= 0 ) {
|
||||
$first_record = $page_offset * $limit;
|
||||
} else {
|
||||
$first_record = ( - $page_offset - 1 ) * $limit;
|
||||
}
|
||||
$wpml_join_particle = $localization_helper->get_wpml_table_join( 'p.ID' );
|
||||
$wpml_where_particle = $localization_helper->get_wpml_table_where();
|
||||
|
||||
$query = $this->_dbi->prepare(
|
||||
'SELECT DISTINCT p.*, e.post_id, i.id AS instance_id, ' . 'i.start AS start, ' . 'i.end AS end, ' .
|
||||
'e.allday AS event_allday, ' .
|
||||
'e.recurrence_rules, e.exception_rules, e.ticket_url, e.instant_event, e.recurrence_dates, e.exception_dates, ' .
|
||||
'e.venue, e.country, e.address, e.city, e.province, e.postal_code, ' .
|
||||
'e.show_map, e.contact_name, e.contact_phone, e.contact_email, e.cost, ' .
|
||||
'e.ical_feed_url, e.ical_source_url, e.ical_organizer, e.ical_contact, e.ical_uid, e.timezone_name, e.longitude, e.latitude ' .
|
||||
'FROM ' . $this->_dbi->get_table_name( 'ai1ec_events' ) . ' e ' . 'INNER JOIN ' .
|
||||
$this->_dbi->get_table_name( 'posts' ) . ' p ON e.post_id = p.ID ' . $wpml_join_particle .
|
||||
' INNER JOIN ' . $this->_dbi->get_table_name( 'ai1ec_event_instances' ) . ' i ON e.post_id = i.post_id ' .
|
||||
$filter['filter_join'] . " WHERE post_type = '" . AI1EC_POST_TYPE . "' " . ' AND ' . $filter_date_clause .
|
||||
$wpml_where_particle . $filter['filter_where'] . $post_status_where .
|
||||
( $unique ? ' GROUP BY e.post_id' : '' ) .
|
||||
// Reverse order when viewing negative pages, to get correct set of
|
||||
// records. Then reverse results later to order them properly.
|
||||
' ORDER BY i.start ' . $order_direction . ', post_title ' . $order_direction . ' LIMIT ' . $first_record .
|
||||
', ' . ( $limit + 1 ),
|
||||
$args );
|
||||
|
||||
$events = $this->_dbi->get_results( $query, ARRAY_A );
|
||||
|
||||
if ( $page_offset >= 0 ) {
|
||||
$prev = true;
|
||||
$next = ( count( $events ) > $limit );
|
||||
if ( $next ) {
|
||||
array_pop( $events );
|
||||
}
|
||||
} else {
|
||||
$prev = ( count( $events ) > $limit );
|
||||
if ( $prev ) {
|
||||
array_pop( $events );
|
||||
}
|
||||
$next = true;
|
||||
}
|
||||
|
||||
// Reorder records if in negative page offset
|
||||
if ( $page_offset < 0 ) {
|
||||
$events = array_reverse( $events );
|
||||
}
|
||||
|
||||
$date_first = $date_last = NULL;
|
||||
|
||||
foreach ( $events as &$event ) {
|
||||
$event['allday'] = $this->_is_all_day( $event );
|
||||
$event = $this->_registry->get( 'model.event', $event );
|
||||
if ( null === $date_first ) {
|
||||
$date_first = $event->get( 'start' );
|
||||
}
|
||||
$date_last = $event->get( 'start' );
|
||||
}
|
||||
$date_first = $this->_registry->get( 'date.time', $date_first );
|
||||
$date_last = $this->_registry->get( 'date.time', $date_last );
|
||||
|
||||
return array(
|
||||
'events' => $events,
|
||||
'prev' => $prev,
|
||||
'next' => $next,
|
||||
'date_first' => $date_first,
|
||||
'date_last' => $date_last );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns events for given day. Event must start before end of day and must
|
||||
* ends after beginning of day.
|
||||
*
|
||||
* @param Ai1ec_Date_Time $day Date object.
|
||||
* @param array $filter Search filters;
|
||||
*
|
||||
* @return array List of events.
|
||||
*/
|
||||
public function get_events_for_day(
|
||||
Ai1ec_Date_Time $day,
|
||||
array $filter = array()
|
||||
) {
|
||||
$end_of_day = $this->_registry->get( 'date.time', $day )
|
||||
->set_time( 23, 59, 59 );
|
||||
$start_of_day = $this->_registry->get( 'date.time', $day )
|
||||
->set_time( 0, 0, 0 );
|
||||
return $this->get_events_between(
|
||||
$start_of_day,
|
||||
$end_of_day,
|
||||
$filter,
|
||||
false,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ID of event in database, matching imported one.
|
||||
*
|
||||
* Return event ID by iCalendar UID, feed url, start time and whether the
|
||||
* event has recurrence rules (to differentiate between an event with a UID
|
||||
* defining the recurrence pattern, and other events with with the same UID,
|
||||
* which are just RECURRENCE-IDs).
|
||||
*
|
||||
* @param int $uid iCalendar UID property
|
||||
* @param string $feed Feed URL
|
||||
* @param int $start Start timestamp (GMT)
|
||||
* @param bool $has_recurrence Whether the event has recurrence rules
|
||||
* @param int|null $exclude_post_id Do not match against this post ID
|
||||
*
|
||||
* @return object|null ID of matching event post, or NULL if no match
|
||||
*/
|
||||
public function get_matching_event_id(
|
||||
$uid,
|
||||
$feed,
|
||||
$start,
|
||||
$has_recurrence = false,
|
||||
$exclude_post_id = null
|
||||
) {
|
||||
$dbi = $this->_registry->get( 'dbi.dbi' );
|
||||
$table_name = $dbi->get_table_name( 'ai1ec_events' );
|
||||
$query = 'SELECT `post_id` FROM ' . $table_name . '
|
||||
WHERE
|
||||
ical_feed_url = %s
|
||||
AND ical_uid = %s
|
||||
AND start = %d ' .
|
||||
( $has_recurrence ? 'AND NOT ' : 'AND ' ) .
|
||||
' ( recurrence_rules IS NULL OR recurrence_rules = \'\' )';
|
||||
$args = array( $feed, $uid );
|
||||
if ( $start instanceof Ai1ec_Date_Time ) {
|
||||
$args[] = $start->format();
|
||||
} else {
|
||||
$args[] = (int)$start;
|
||||
}
|
||||
if ( null !== $exclude_post_id ) {
|
||||
$query .= ' AND post_id <> %d';
|
||||
$args[] = $exclude_post_id;
|
||||
}
|
||||
|
||||
return $dbi->get_var( $dbi->prepare( $query, $args ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event by UID. UID must be unique.
|
||||
*
|
||||
* NOTICE: deletes events with that UID if they have different URLs.
|
||||
*
|
||||
* @param string $uid UID from feed.
|
||||
* @param string $uid Feed URL.
|
||||
*
|
||||
* @return int|null Matching Event ID or NULL if none found.
|
||||
*/
|
||||
public function get_matching_event_by_uid_and_url( $uid, $url ) {
|
||||
if ( ! isset( $uid{1} ) ) {
|
||||
return null;
|
||||
}
|
||||
$dbi = $this->_registry->get( 'dbi.dbi' );
|
||||
$table_name = $dbi->get_table_name( 'ai1ec_events' );
|
||||
$argv = array( $url, $uid, $url );
|
||||
// fix issue where invalid feed URLs were assigned
|
||||
$update = 'UPDATE ' . $table_name . ' SET `ical_feed_url` = %s' .
|
||||
' WHERE `ical_uid` = %s AND `ical_feed_url` != %s';
|
||||
$query = $dbi->prepare( $update, $argv);
|
||||
$success = $dbi->query( $query );
|
||||
|
||||
// retrieve actual feed ID if any
|
||||
$select = 'SELECT `post_id` FROM `' . $table_name .
|
||||
'` WHERE `ical_uid` = %s';
|
||||
return $dbi->get_var( $dbi->prepare( $select, array( $uid ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event ids for the passed feed url
|
||||
*
|
||||
* @param string $feed_url
|
||||
*/
|
||||
public function get_event_ids_for_feed( $feed_url ) {
|
||||
$dbi = $this->_registry->get( 'dbi.dbi' );
|
||||
$table_name = $dbi->get_table_name( 'ai1ec_events' );
|
||||
$query = 'SELECT `post_id` FROM ' . $table_name .
|
||||
' WHERE ical_feed_url = %s';
|
||||
return $dbi->get_col( $dbi->prepare( $query, array( $feed_url ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns events instances closest to today.
|
||||
*
|
||||
* @param array $events_ids Events ids filter.
|
||||
*
|
||||
* @return array Events collection.
|
||||
* @throws Ai1ec_Bootstrap_Exception
|
||||
*/
|
||||
public function get_instances_closest_to_today( array $events_ids = array() ) {
|
||||
$where_events_ids = '';
|
||||
if ( ! empty( $events_ids ) ) {
|
||||
$where_events_ids = 'i.post_id IN ('
|
||||
. implode( ',', $events_ids ) . ') AND ';
|
||||
}
|
||||
$query = 'SELECT i.id, i.post_id FROM ' .
|
||||
$this->_dbi->get_table_name( 'ai1ec_event_instances' ) .
|
||||
' i WHERE ' .
|
||||
$where_events_ids .
|
||||
' i.start > %d ' .
|
||||
' GROUP BY i.post_id';
|
||||
/** @var $today Ai1ec_Date_Time */
|
||||
$today = $this->_registry->get( 'date.time', 'now', 'sys.default' );
|
||||
$today->set_time( 0, 0, 0 );
|
||||
$query = $this->_dbi->prepare( $query, $today->format( 'U' ) );
|
||||
$results = $this->_dbi->get_results( $query );
|
||||
$events = array();
|
||||
foreach ( $results as $result ) {
|
||||
$events[] = $this->get_event(
|
||||
$result->post_id,
|
||||
$result->id
|
||||
);
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given event must be treated as all-day event.
|
||||
*
|
||||
* Event instances that span 24 hours are treated as all-day.
|
||||
* NOTICE: event is passed in before being transformed into
|
||||
* Ai1ec_Event object, with Ai1ec_Date_Time fields.
|
||||
*
|
||||
* @param array $event Event data returned from database.
|
||||
*
|
||||
* @return bool True if event is all-day event.
|
||||
*/
|
||||
protected function _is_all_day( array $event ) {
|
||||
if ( isset( $event['event_allday'] ) && $event['event_allday'] ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! isset( $event['start'] ) || ! isset( $event['end'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ( 86400 === $event['end'] - $event['start'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* _limit_result_set function
|
||||
*
|
||||
* Slice given number of events from list, with exception when all
|
||||
* events from last day shall be included.
|
||||
*
|
||||
* @param array $events List of events to slice
|
||||
* @param int $limit Number of events to slice-off
|
||||
* @param bool $last_day Set to true to include all events from last day ignoring {$limit}
|
||||
*
|
||||
* @return array Sliced events list
|
||||
*/
|
||||
protected function _limit_result_set(
|
||||
array $events,
|
||||
$limit,
|
||||
$last_day
|
||||
) {
|
||||
$limited_events = array();
|
||||
$start_day_previous = 0;
|
||||
foreach ( $events as $event ) {
|
||||
$start_day = date(
|
||||
'Y-m-d',
|
||||
$event['start']
|
||||
);
|
||||
--$limit; // $limit = $limit - 1;
|
||||
if ( $limit < 0 ) {
|
||||
if ( true === $last_day ) {
|
||||
if ( $start_day != $start_day_previous ) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$limited_events[] = $event;
|
||||
$start_day_previous = $start_day;
|
||||
}
|
||||
return $limited_events;
|
||||
}
|
||||
|
||||
/**
|
||||
* _get_post_status_sql function
|
||||
*
|
||||
* Returns SQL snippet for properly matching event posts, as well as array
|
||||
* of arguments to pass to $this_dbi->prepare, in function argument
|
||||
* references.
|
||||
* Nothing is returned by the function.
|
||||
*
|
||||
* @return array An array containing post_status_where: the sql string,
|
||||
* args: the arguments for prepare()
|
||||
*/
|
||||
protected function _get_post_status_sql() {
|
||||
$args = array();
|
||||
|
||||
// Query the correct post status
|
||||
if (
|
||||
current_user_can( 'administrator' ) ||
|
||||
current_user_can( 'editor' ) ||
|
||||
current_user_can( 'read_private_ai1ec_events' )
|
||||
) {
|
||||
// User has privilege of seeing all published and private
|
||||
$post_status_where = 'AND post_status IN ( %s, %s ) ';
|
||||
$args[] = 'publish';
|
||||
$args[] = 'private';
|
||||
} elseif ( is_user_logged_in() ) {
|
||||
// User has privilege of seeing all published and only their own
|
||||
// private posts.
|
||||
|
||||
// Get user ID
|
||||
$user_id = 0;
|
||||
if ( is_callable( 'wp_get_current_user' ) ) {
|
||||
$user = wp_get_current_user();
|
||||
$user_id = (int)$user->ID;
|
||||
unset( $user );
|
||||
}
|
||||
|
||||
// include post_status = published
|
||||
// OR
|
||||
// post_status = private AND post_author = userID
|
||||
$post_status_where =
|
||||
'AND ( ' .
|
||||
'post_status = %s ' .
|
||||
'OR ( post_status = %s AND post_author = %d ) ' .
|
||||
') ';
|
||||
|
||||
$args[] = 'publish';
|
||||
$args[] = 'private';
|
||||
$args[] = $user_id;
|
||||
} else {
|
||||
// User can only see published posts.
|
||||
$post_status_where = 'AND post_status = %s ';
|
||||
$args[] = 'publish';
|
||||
}
|
||||
|
||||
return array(
|
||||
'post_status_where' => $post_status_where,
|
||||
'args' => $args
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take filter and return SQL options.
|
||||
*
|
||||
* Takes an array of filtering options and turns it into JOIN and WHERE
|
||||
* statements for running an SQL query limited to the specified options.
|
||||
*
|
||||
* @param array $filter Array of filters for the events returned:
|
||||
* ['cat_ids'] => list of category IDs
|
||||
* ['tag_ids'] => list of tag IDs
|
||||
* ['post_ids'] => list of event post IDs
|
||||
* ['auth_ids'] => list of event author IDs
|
||||
* ['instance_ids'] => list of event instance IDs
|
||||
*
|
||||
* @return array The modified filter array to having:
|
||||
* ['filter_join'] the Join statements for the SQL
|
||||
* ['filter_where'] the Where statements for the SQL
|
||||
*/
|
||||
protected function _get_filter_sql( $filter ) {
|
||||
$filter_join = $filter_where = array();
|
||||
foreach ( $filter as $filter_type => $filter_ids ) {
|
||||
$filter_object = null;
|
||||
try {
|
||||
if ( empty( $filter_ids ) ) {
|
||||
$filter_ids = array();
|
||||
}
|
||||
$filter_object = $this->_registry->get(
|
||||
'model.filter.' . $filter_type,
|
||||
$filter_ids
|
||||
);
|
||||
if ( ! ( $filter_object instanceof Ai1ec_Filter_Interface ) ) {
|
||||
throw new Ai1ec_Bootstrap_Exception(
|
||||
'Filter \'' . get_class( $filter_object ) .
|
||||
'\' is not instance of Ai1ec_Filter_Interface'
|
||||
);
|
||||
}
|
||||
} catch ( Ai1ec_Bootstrap_Exception $exception ) {
|
||||
continue;
|
||||
}
|
||||
$filter_join[] = $filter_object->get_join();
|
||||
$filter_where[] = $filter_object->get_where();
|
||||
}
|
||||
|
||||
$filter_join = array_filter( $filter_join );
|
||||
$filter_where = array_filter( $filter_where );
|
||||
$filter_join = join( ' ', $filter_join );
|
||||
if ( count( $filter_where ) > 0 ) {
|
||||
$operator = $this->get_distinct_types_operator();
|
||||
$filter_where = $operator . '( ' .
|
||||
implode( ' ) ' . $operator . ' ( ', $filter_where ) .
|
||||
' ) ';
|
||||
} else {
|
||||
$filter_where = '';
|
||||
}
|
||||
|
||||
return $filter + compact( 'filter_where', 'filter_join' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get operator for joining distinct filters in WHERE.
|
||||
*
|
||||
* @return string SQL operator.
|
||||
*/
|
||||
public function get_distinct_types_operator() {
|
||||
static $operators = array( 'AND' => 1, 'OR' => 2 );
|
||||
$default = 'AND';
|
||||
$where_operator = strtoupper( trim( (string)apply_filters(
|
||||
'ai1ec_filter_distinct_types_logic',
|
||||
$default
|
||||
) ) );
|
||||
if ( ! isset( $operators[$where_operator] ) ) {
|
||||
$where_operator = $default;
|
||||
}
|
||||
return $where_operator;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Settings extension for managing view-related settings.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.0
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Model
|
||||
*/
|
||||
class Ai1ec_Settings_View extends Ai1ec_App {
|
||||
|
||||
/**
|
||||
* @var string Name of settings option to use for views map.
|
||||
*/
|
||||
const SETTING_VIEWS_MAP = 'enabled_views';
|
||||
|
||||
/**
|
||||
* @var Ai1ec_Settings Instance
|
||||
*/
|
||||
protected $_settings = null;
|
||||
|
||||
/**
|
||||
* Acquire Settings model instance for future reference.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _initialize() {
|
||||
$this->_settings = $this->_registry->get( 'model.settings' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a view if not set.
|
||||
*
|
||||
* @param array $view
|
||||
*/
|
||||
public function add( array $view ) {
|
||||
$enabled_views = $this->_get();
|
||||
if ( isset( $enabled_views[$view['name']] ) ) {
|
||||
if ( $enabled_views[$view['name']]['longname'] === $view['longname'] ) {
|
||||
return;
|
||||
}
|
||||
$enabled_views[$view['name']]['longname'] = $view['longname'];
|
||||
} else {
|
||||
// Copy relevant settings to local view array; account for possible missing
|
||||
// mobile settings during upgrade (assign defaults).
|
||||
$enabled_views[$view['name']] = array(
|
||||
'enabled' => $view['enabled'],
|
||||
'default' => $view['default'],
|
||||
'enabled_mobile' => isset( $view['enabled_mobile'] ) ?
|
||||
$view['enabled_mobile'] : $view['enabled'],
|
||||
'default_mobile' => isset( $view['default_mobile'] ) ?
|
||||
$view['default_mobile'] : $view['default'],
|
||||
'longname' => $view['longname'],
|
||||
);
|
||||
}
|
||||
$this->_set( $enabled_views );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a view.
|
||||
*
|
||||
* @param string $view
|
||||
*/
|
||||
public function remove( $view ) {
|
||||
$enabled_views = $this->_get();
|
||||
if ( isset( $enabled_views[$view] ) ) {
|
||||
unset( $enabled_views[$view] );
|
||||
$this->_set( $enabled_views );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all configured views.
|
||||
*
|
||||
* @return array Map of configured view aliases and their details.
|
||||
*/
|
||||
public function get_all() {
|
||||
return $this->_get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name of view to be rendered for requested alias.
|
||||
*
|
||||
* @param string $view Name of view requested.
|
||||
*
|
||||
* @return string Name of view to be rendered.
|
||||
*
|
||||
* @throws Ai1ec_Settings_Exception If no views are configured.
|
||||
*/
|
||||
public function get_configured( $view ) {
|
||||
$enabled_views = $this->_get();
|
||||
if ( empty( $enabled_views ) ) {
|
||||
throw new Ai1ec_Settings_Exception( 'No view is enabled' );
|
||||
}
|
||||
if (
|
||||
isset( $enabled_views[$view] ) &&
|
||||
isset( $enabled_views[$view]['enabled' . ( wp_is_mobile() ? '_mobile' : '' ) ] ) &&
|
||||
$enabled_views[$view]['enabled' . ( wp_is_mobile() ? '_mobile' : '' ) ]
|
||||
) {
|
||||
return $view;
|
||||
}
|
||||
return $this->get_default();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default view to render.
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public function get_default() {
|
||||
$enabled_views = $this->_get();
|
||||
$default = null;
|
||||
// Check mobile settings first, if in mobile mode.
|
||||
if (
|
||||
! $this->_registry->get( 'compatibility.cli' )->is_cli() &&
|
||||
wp_is_mobile()
|
||||
) {
|
||||
foreach ( $enabled_views as $view => $details ) {
|
||||
if (
|
||||
isset( $details['default_mobile'] ) &&
|
||||
$details['default_mobile'] &&
|
||||
$details['enabled_mobile']
|
||||
) {
|
||||
$default = $view;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Either not in mobile mode or no mobile settings available; look up
|
||||
// desktop settings.
|
||||
if ( null === $default ) {
|
||||
foreach ( $enabled_views as $view => $details ) {
|
||||
if ( $details['default'] && $details['enabled'] ) {
|
||||
$default = $view;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// No enabled view found, but we need to pick one, so pick the first view.
|
||||
if ( null === $default ) {
|
||||
$default = (string)current( array_keys( $enabled_views ) );
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve views maps from storage.
|
||||
*
|
||||
* @return array Current views map.
|
||||
*/
|
||||
protected function _get() {
|
||||
return (array)$this->_settings->get( self::SETTING_VIEWS_MAP, array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update views map.
|
||||
*
|
||||
* @param array $enabled_views Map of enabled views.
|
||||
*
|
||||
* @return bool Success.
|
||||
*/
|
||||
protected function _set( array $enabled_views ) {
|
||||
return $this->_settings->set( self::SETTING_VIEWS_MAP, $enabled_views );
|
||||
}
|
||||
|
||||
}
|
||||
1094
wp-content/plugins/all-in-one-event-calendar/app/model/settings.php
Normal file
1094
wp-content/plugins/all-in-one-event-calendar/app/model/settings.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception occuring during settings modification.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.0
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Html
|
||||
*/
|
||||
class Ai1ec_Settings_Exception extends Ai1ec_Exception {
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Model used for storing/retrieving taxonomy.
|
||||
*
|
||||
* @author Time.ly Network, Inc.
|
||||
* @since 2.0
|
||||
* @package Ai1EC
|
||||
* @subpackage Ai1EC.Html
|
||||
*/
|
||||
class Ai1ec_Taxonomy extends Ai1ec_Base {
|
||||
|
||||
/**
|
||||
* @var array Map of taxonomy values.
|
||||
*/
|
||||
protected $_taxonomy_map = array(
|
||||
'events_categories' => array(),
|
||||
'events_tags' => array(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Callback to pre-populate taxonomies before exporting ics.
|
||||
* All taxonomies which are not tags are exported as event_categories
|
||||
*
|
||||
* @param array $post_ids List of Post IDs to inspect.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function prepare_meta_for_ics( array $post_ids ) {
|
||||
$taxonomies = get_object_taxonomies( AI1EC_POST_TYPE );
|
||||
$categories = array();
|
||||
$excluded_categories = array(
|
||||
'events_tags' => true,
|
||||
'events_feeds' => true
|
||||
);
|
||||
foreach ( $taxonomies as $taxonomy ) {
|
||||
if ( isset( $excluded_categories[$taxonomy] ) ) {
|
||||
continue;
|
||||
}
|
||||
$categories[] = $taxonomy;
|
||||
}
|
||||
foreach ( $post_ids as $post_id ) {
|
||||
$post_id = (int)$post_id;
|
||||
$this->_taxonomy_map['events_categories'][$post_id] = array();
|
||||
$this->_taxonomy_map['events_tags'][$post_id] = array();
|
||||
}
|
||||
$tags = wp_get_object_terms(
|
||||
$post_ids,
|
||||
array( 'events_tags' ),
|
||||
array( 'fields' => 'all_with_object_id' )
|
||||
);
|
||||
foreach ( $tags as $term ) {
|
||||
$this->_taxonomy_map[$term->taxonomy][$term->object_id][] = $term;
|
||||
}
|
||||
$category_terms = wp_get_object_terms(
|
||||
$post_ids,
|
||||
$categories,
|
||||
array( 'fields' => 'all_with_object_id' )
|
||||
);
|
||||
foreach ( $category_terms as $term ) {
|
||||
$this->_taxonomy_map['events_categories'][$term->object_id][] = $term;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to pre-populate taxonomies before processing.
|
||||
*
|
||||
* @param array $post_ids List of Post IDs to inspect.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function update_meta( array $post_ids ) {
|
||||
foreach ( $post_ids as $post_id ) {
|
||||
$post_id = (int)$post_id;
|
||||
$this->_taxonomy_map['events_categories'][$post_id] = array();
|
||||
$this->_taxonomy_map['events_tags'][$post_id] = array();
|
||||
}
|
||||
$terms = wp_get_object_terms(
|
||||
$post_ids,
|
||||
array( 'events_categories', 'events_tags' ),
|
||||
array( 'fields' => 'all_with_object_id' )
|
||||
);
|
||||
foreach ( $terms as $term ) {
|
||||
$this->_taxonomy_map[$term->taxonomy][$term->object_id][] = $term;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-fetch category entries map from database.
|
||||
*
|
||||
* @return array Map of category entries.
|
||||
*/
|
||||
public function fetch_category_map() {
|
||||
$category_map = array();
|
||||
$records = (array)$this->_registry->get( 'dbi.dbi' )->select(
|
||||
'ai1ec_event_category_meta',
|
||||
array( 'term_id', 'term_image', 'term_color' )
|
||||
);
|
||||
foreach ( $records as $row ) {
|
||||
$image = $color = null;
|
||||
if ( $row->term_image ) {
|
||||
$image = $row->term_image;
|
||||
}
|
||||
if ( $row->term_color ) {
|
||||
$color = $row->term_color;
|
||||
}
|
||||
$category_map[(int)$row->term_id] = compact( 'image', 'color' );
|
||||
}
|
||||
return $category_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get taxonomy values for specified post.
|
||||
*
|
||||
* @param int $post_id Actual Post ID to check.
|
||||
* @param string $taxonomy Name of taxonomy to retrieve values for.
|
||||
*
|
||||
* @return array List of terms (stdClass'es) associated with post.
|
||||
*/
|
||||
public function get_post_taxonomy( $post_id, $taxonomy ) {
|
||||
$post_id = (int)$post_id;
|
||||
if ( ! isset( $this->_taxonomy_map[$taxonomy][$post_id] ) ) {
|
||||
$definition = wp_get_post_terms( $post_id, $taxonomy );
|
||||
if ( empty( $definition ) || is_wp_error( $definition ) ) {
|
||||
$definition = array();
|
||||
}
|
||||
$this->_taxonomy_map[$taxonomy][$post_id] = $definition;
|
||||
}
|
||||
return $this->_taxonomy_map[$taxonomy][$post_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get post (event) categories taxonomy.
|
||||
*
|
||||
* @param int $post_id Checked post ID.
|
||||
*
|
||||
* @return array List of categories (stdClass'es) associated with event.
|
||||
*/
|
||||
public function get_post_categories( $post_id ) {
|
||||
return $this->get_post_taxonomy( $post_id, 'events_categories' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get post (event) tags taxonomy.
|
||||
*
|
||||
* @param int $post_id Checked post ID.
|
||||
*
|
||||
* @return array List of tags (stdClass'es) associated with event.
|
||||
*/
|
||||
public function get_post_tags( $post_id ) {
|
||||
return $this->get_post_taxonomy( $post_id, 'events_tags' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached category description field.
|
||||
*
|
||||
* @param int $term_id Category ID.
|
||||
* @param string $field Name of field, one of 'image', 'color'.
|
||||
*
|
||||
* @return string|null Field value or null if entry is not found.
|
||||
*/
|
||||
public function get_category_field( $term_id, $field ) {
|
||||
static $category_meta = null;
|
||||
if ( null === $category_meta ) {
|
||||
$category_meta = $this->fetch_category_map();
|
||||
}
|
||||
$term_id = (int)$term_id;
|
||||
if ( ! isset( $category_meta[$term_id] ) ) {
|
||||
return null;
|
||||
}
|
||||
return $category_meta[$term_id][$field];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color of the Event Category having the given term ID.
|
||||
*
|
||||
* @param int $term_id The ID of the Event Category.
|
||||
*
|
||||
* @return string|null Color to use
|
||||
*/
|
||||
public function get_category_color( $term_id ) {
|
||||
return $this->get_category_field( $term_id, 'color' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image of the Event Category having the given term ID.
|
||||
*
|
||||
* @param int $term_id The ID of the Event Category.
|
||||
*
|
||||
* @return string|null Image url to use.
|
||||
*/
|
||||
public function get_category_image( $term_id ) {
|
||||
return $this->get_category_field( $term_id, 'image' );
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user