Add upstream plugins

Signed-off-by: Adrian Nöthlich <git@promasu.tech>
This commit is contained in:
2019-10-25 22:42:20 +02:00
parent 5d3c2ec184
commit 290736650a
1186 changed files with 302577 additions and 0 deletions

View File

@@ -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'] );
}
}
}

View File

@@ -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;
}

View File

@@ -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 {
}

View File

@@ -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 );
}
}
}

View File

@@ -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 {
}

View File

@@ -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
);
}
}

View File

@@ -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 {
}

View File

@@ -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 &#8220;%s&#8221;', 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;
}
}

View File

@@ -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 )
);
}
}

View File

@@ -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;
}
}