@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user