366 lines
13 KiB
PHP
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 “%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;
|
|
}
|
|
}
|