Autogenerated_Series
Class Autogenerated_Series
Source
File: src/Events_Pro/Custom_Tables/V1/Series/Autogenerated_Series.php
class Autogenerated_Series {
/**
* The name of the meta key that will be used to flag a Series as auto-generated
* following the creation of a Recurring Event not assigned a pre-existing Series.
*
* @since 6.0.0
*/
const FLAG_META_KEY = '_tec_autogenerated';
/**
* The nane of the meta key that will be used to store the Series post checksum.
*
* @since 6.0.0
*/
const CHECKSUM_META_KEY = '_tec_autogenerated_checksum';
/**
* A reference to the current provisional post ID handler.
*
* @since 6.0.0
*
* @var Provisional_Post
*/
protected $provisional_post;
/**
* A map from post IDs to the last checksums collected.
*
* @since 6.0.0
*
* @var array<int,string>
*/
protected $checksums = [];
/**
* Autogenerated_Series constructor.
*
* @since 6.0.0
*
* @param \TEC\Events_Pro\Custom_Tables\V1\Models\Provisional_Post $provisional_post A reference to the current provisional post ID handler.
*/
public function __construct( Provisional_Post $provisional_post ) {
$this->provisional_post = $provisional_post;
}
/**
* Handles the trashing of any auto-generated Series related to an Event
*
* @since 6.0.0
*
* @param int|WP_Post $post_id The ID of the post being trashed or a reference to
* a post object.
*
* @return int The number of trashed Series.
*/
public function trash_following( $post_id ) {
$post = $this->check_event_post( $post_id );
if ( false === $post ) {
return false;
}
$relationships = $this->get_event_relationships( $post );
$trashed = 0;
/** @var \TEC\Events_Pro\Custom_Tables\V1\Models\Relationship $relationship */
foreach ( $relationships as $relationship ) {
$series_id = $relationship->series_post_id;
if ( ! (
$this->check_autogenerated( $series_id )
&& $this->should_follow( $series_id, $post_id ) )
) {
continue;
}
$trashed_post = wp_trash_post( $series_id );
if ( ! $trashed_post instanceof WP_Post ) {
continue;
}
$trashed ++;
}
return $trashed;
}
/**
* Get and check the input post ID to make sure trashing or deletion of the
* related Series is coherent.
*
* @since 6.0.0
*
* @param int $post_id The post ID.
*
* @return WP_Post|false Either a reference to the Event post object or `false`
* to indicate the fetching or the checks failed.
*/
private function check_event_post( $post_id ) {
if ( $this->provisional_post->is_provisional_post_id( $post_id ) ) {
$occurrence_id = $this->provisional_post->normalize_provisional_post_id( $post_id );
$occurrence = Occurrence::find( $occurrence_id, 'occurrence_id' );
if ( ! $occurrence instanceof Occurrence ) {
return false;
}
$event_post_id = $occurrence->post_id;
} else {
$event_post_id = $post_id;
}
$post = get_post( $event_post_id );
if ( ! ( $post instanceof WP_Post && TEC::POSTTYPE === $post->post_type ) ) {
return false;
}
$event = Event::find( $post->ID, 'post_id' );
if ( ! $event instanceof Event || empty( $event->rset ) ) {
return false;
}
return $post;
}
/**
* Whether a Series post has the pre-conditions to be trashed or not.
*
* A Series should be trashed following an Event if that Event is the last
* one related to the Series and is a Recurring Event.
*
* @since 6.0.0
*
* @param int $series_id The Series post ID.
* @param int|WP_Post $event_id The Event post ID or a reference to the Event post object.
*
* @return bool Whether a Series post has the pre-conditions to be trashed or not.
*/
private function should_follow( $series_id, $event_id ) {
$relationships = Relationship::builder_instance()->find_all( $series_id, 'series_post_id' );
$event_id = $event_id instanceof WP_Post ? $event_id->ID : $event_id;
if ( ! $relationships instanceof Generator ) {
return false;
}
$related = array_map( static function ( Relationship $relationship ) {
return $relationship->event_post_id;
}, iterator_to_array( $relationships, false ) );
return $related === [ $event_id ];
}
/**
* Handles the deletion of any auto-generated Series related to a Recurring Event
* that has been deleted.
*
* @since 6.0.0
*
* @parma WP_Post $post A reference to the post object being deleted.
*
* @return int The number of deleted Series.
*/
public function delete_following( WP_Post $post ) {
return $this->trash_following( $post );
}
/**
* Checks whether a post, or post ID, refers to an auto-generated Series
* or not.
*
* @since 6.0.0
*
* @param int|WP_Post|null $post_id A reference to the post object, or post ID, to check.
*
* @return false|WP_Post Either a reference to the auto-generated Series post, `false` otherwise.
*/
private function check_autogenerated( $post_id ) {
$post = get_post( $post_id );
if ( ! ( $post instanceof WP_Post && Series::POSTTYPE === $post->post_type ) ) {
return false;
}
$flag = get_post_meta( $post->ID, self::FLAG_META_KEY, true );
if ( empty( $flag ) ) {
// The flag is not there to begin with, bail.
return false;
}
return $post;
}
/**
* Removes the auto-generated flag from a Series post if meaningful
* edits happened to it.
*
* @since 6.0.0
*
* @param WP_Post $series A reference to the Series post object.
*
* @return bool Either `true` if the autogenerated flag was removed, `false`
* otherwise.
*/
public function remove_autogenerated_flag( WP_Post $series ) {
$post = $this->check_autogenerated( $series );
if ( ! $post instanceof WP_Post ) {
return false;
}
// If the checksum is the same, do not remove.
$remove = ! $this->checksum_matches( $series );
/**
* Filters whether a Series post autogenerated flag should be removed from its
* meta or not.
*
* @since 6.0.0
*
* @param bool $remove Whether the autogenerated flag should be removed or not.
* @param WP_Post $series A reference to the Series post object the filter is
* being applied for.
*/
$remove = apply_filters( 'tec_events_custom_tables_v1_remove_series_autogenerated_flag', $remove, $series );
if ( ! $remove ) {
return false;
}
return delete_post_meta( $post->ID, self::FLAG_META_KEY )
&& delete_post_meta( $post->ID, self::CHECKSUM_META_KEY );
}
/**
* Returns the checksum of the post fields and custom fields for a Series post.
*
* @since 6.0.0
*
* @param WP_Post $post A reference to the Series post object.
*
* @return string A string representing the post current checksum.
*/
private function calculate_post_checksum( WP_Post $post ) {
$post_vars = array_diff_key( get_object_vars( $post ), [
'post_modified' => true,
'post_modified_gmt' => true,
] );
$post_meta = array_diff_key( get_post_meta( $post->ID ), [
self::FLAG_META_KEY => true,
self::CHECKSUM_META_KEY => true,
'_edit_lock' => true,
'_edit_last' => true,
] );
$relationships_data = [];
$relationships = Relationship::find_all( $post->ID, 'series_post_id' );
/** @var \TEC\Events_Pro\Custom_Tables\V1\Models\Relationship $relationship */
foreach ( $relationships as $relationship ) {
$relationships_data[] = $relationship->to_array();
}
return md5(
wp_json_encode( $post_vars )
. wp_json_encode( $post_meta )
. wp_json_encode( $relationships_data )
);
}
/**
* Returns whether the current Series post checksum matches the stored version or not.
*
* If no checksum exists for the Series, then it will be calculated and stored.
*
* @since 6.0.0
*
* @param WP_Post $post A reference to the Series post object.
*
* @return bool Whether the current Series post checksum value matches the stored one
* or not.
*/
public function checksum_matches( WP_Post $post ) {
$expected = get_post_meta( $post->ID, self::CHECKSUM_META_KEY, true );
$current = $this->calculate_post_checksum( $post );
if ( empty( $expected ) ) {
// First time we check it.
update_post_meta( $post->ID, self::CHECKSUM_META_KEY, $current );
return true;
}
return $expected === $current;
}
/**
* Untrashes a Series post if the Event being untrashed is the one that triggered
* the Series auto-generation.
*
* @since 6.0.0
*
* @param int $post_id The Event post ID.
*
* @return int The number of untrashed Series.
*/
public function untrash_following( $post_id ) {
$post = $this->check_event_post( $post_id );
if ( ! $post instanceof WP_Post ) {
return false;
}
$relationships = $this->get_event_relationships( $post );
$untrashed = 0;
/** @var \TEC\Events_Pro\Custom_Tables\V1\Models\Relationship $relationship */
foreach ( $relationships as $relationship ) {
$series_id = $relationship->series_post_id;
if ( ! $this->should_follow( $series_id, $post_id ) ) {
continue;
}
$untrashed_post = wp_untrash_post( $series_id );
if ( ! $untrashed_post instanceof WP_Post ) {
continue;
}
$untrashed ++;
}
return $untrashed;
}
/**
* Returns a generator that will produce all the Series <> Event relationships
* for an Event.
*
* @since 6.0.0
*
* @param WP_Post $event A reference to the Event post object.
*
* @return Generator<\TEC\Events_Pro\Custom_Tables\V1\Models\Relationship>|array Either a Generator that will produce all Relationships
* for the Event, or an empty array.
*/
private function get_event_relationships( WP_Post $event ) {
$relationships = Relationship::builder_instance()->find_all( (array) $event->ID, 'event_post_id' );
return $relationships instanceof Generator ? $relationships : [];
}
/**
* Updates a Series post status if the Event post status is being updated.
*
* @since 6.0.11
*
* @param WP_Post $post The Event post object.
* @param string $old_status The old Event post status.
* @param string $new_status The new Event post status.
*
* @return int|WP_Error The updated Series post ID, `0` if no Series was updated, or a WP_Error object.
*/
public function update_series_post_status( WP_Post $post, string $old_status, string $new_status ) {
$event_post_id = Occurrence::normalize_id( $post->ID );
if ( get_post_type( $event_post_id ) !== TEC::POSTTYPE ) {
return 0;
}
$series = tec_series()
->where( 'event_post_id', $event_post_id )
->where( 'post_status', $old_status )
->first();
if ( ! $series instanceof WP_Post ) {
return 0;
}
if ( ! tribe_is_truthy( get_post_meta( $series->ID, self::FLAG_META_KEY, true ) ) ) {
return 0;
}
$series_post_status = get_post_status( $series->ID );
if ( $series_post_status === $new_status ) {
return 0;
}
// Only if all Series' Events will match this new status.
global $wpdb;
$events_table = Events::table_name();
$series_relationship_table = Series_Relationships::table_name();
$query = "SELECT COUNT(*)
FROM
{$wpdb->posts} AS event_post
INNER JOIN
{$events_table} ON event_post.ID = {$events_table}.post_id
INNER JOIN
{$series_relationship_table} ON {$series_relationship_table}.event_id = {$events_table}.event_id
WHERE
{$series_relationship_table}.series_post_id = %d
AND event_post.post_status = %s";
$query = $wpdb->prepare( $query, $series->ID, $new_status );
// How many are in that status?
$total_in_same_status = (int) $wpdb->get_var( $query );
// How many total?
$total_events = Relationship::where( 'series_post_id', $series->ID )->count();
// If they aren't all in the same status, do not transition Autogenerated Series.
if ( $total_events > 1 && $total_in_same_status !== $total_events ) {
return 0;
}
// This update is happening because the Series is auto-generated: do not remove the flag.
add_filter( 'tec_events_custom_tables_v1_remove_series_autogenerated_flag', '__return_false' );
$updated = wp_update_post( [
'ID' => $series->ID,
'post_status' => $new_status,
] );
// Update the checksum since the post status changed.
update_post_meta( $series->ID, self::CHECKSUM_META_KEY, $this->calculate_post_checksum( $series ) );
remove_filter( 'tec_events_custom_tables_v1_remove_series_autogenerated_flag', '__return_false' );
if ( $updated instanceof WP_Error ) {
do_action( 'tribe_log', 'error', __METHOD__, [
'message' => 'Error updating series post status following Event post status update.',
'old_status' => $old_status,
'new_status' => $new_status,
'event_post_id' => $event_post_id,
'series_post_id' => $series->ID,
] );
}
return $updated;
}
}
Changelog
| Version | Description |
|---|---|
| 6.0.0 | Introduced. |
Methods
- __construct — Autogenerated_Series constructor.
- checksum_matches — Returns whether the current Series post checksum matches the stored version or not.
- delete_following — Handles the deletion of any auto-generated Series related to a Recurring Event that has been deleted.
- remove_autogenerated_flag — Removes the auto-generated flag from a Series post if meaningful edits happened to it.
- trash_following — Handles the trashing of any auto-generated Series related to an Event
- untrash_following — Untrashes a Series post if the Event being untrashed is the one that triggered the Series auto-generation.
- update_series_post_status — Updates a Series post status if the Event post status is being updated.