all-in-one-event-calendar/app/model/event/parent.php
2017-11-09 17:36:04 +01:00

366 lines
13 KiB
PHP

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