From 7da7bf8ec2efde0192ed780d0cceed0bc60eb882 Mon Sep 17 00:00:00 2001 From: JJ Date: Tue, 6 Aug 2024 11:34:56 -0400 Subject: [PATCH 1/7] wip: adding data to summary message in log --- classes/class-db.php | 2 +- connectors/class-connector-posts.php | 129 ++++++++++++++++++++++----- 2 files changed, 109 insertions(+), 22 deletions(-) diff --git a/classes/class-db.php b/classes/class-db.php index 9209676b..a35b89ab 100755 --- a/classes/class-db.php +++ b/classes/class-db.php @@ -119,7 +119,7 @@ protected function sanitize_record( $record ) { return array_map( function ( $value ) { if ( ! is_array( $value ) ) { - return wp_strip_all_tags( $value ); + return wp_kses_post( $value ); } return $value; diff --git a/connectors/class-connector-posts.php b/connectors/class-connector-posts.php index 057b70f9..7c9566ea 100644 --- a/connectors/class-connector-posts.php +++ b/connectors/class-connector-posts.php @@ -26,6 +26,7 @@ class Connector_Posts extends Connector { public $actions = array( 'transition_post_status', 'deleted_post', + 'post_updated' ); /** @@ -81,7 +82,8 @@ public function get_context_labels() { public function action_links( $links, $record ) { $post = get_post( $record->object_id ); - if ( $post && $post->post_status === $record->get_meta( 'new_status', true ) ) { + // Let's get action links for all posts. + if ( $post ) { $post_type_name = $this->get_post_type_name( get_post_type( $post->ID ) ); if ( 'trash' === $post->post_status ) { @@ -260,31 +262,15 @@ public function callback_transition_post_status( $new_status, $old_status, $post } if ( empty( $action ) ) { - $action = 'updated'; + return; // We will use a separate callback for updated posts. } - $revision_id = null; - - if ( wp_revisions_enabled( $post ) ) { - $revision = get_children( - array( - 'post_type' => 'revision', - 'post_status' => 'inherit', - 'post_parent' => $post->ID, - 'posts_per_page' => 1, // VIP safe. - 'orderby' => 'post_date', - 'order' => 'DESC', - ) - ); - - if ( $revision ) { - $revision = array_values( $revision ); - $revision_id = $revision[0]->ID; - } - } + $revision_id = $this->get_revision_id( $post ); $post_type_name = strtolower( $this->get_post_type_name( $post->post_type ) ); + add_filter( 'wp_stream_has_tracked_post_updated', '__return_true' ); + $this->log( $summary, array( @@ -302,6 +288,76 @@ public function callback_transition_post_status( $new_status, $old_status, $post ); } + public function callback_post_updated( $post_id, $post_after, $post_before ) { + + // If we have already tracked this change, bail. + if ( apply_filters( 'wp_stream_has_tracked_post_updated', false ) ) { + return; + } + + // We don't want the meta box update request either, just the post update. + if ( ! empty( wp_stream_filter_input( INPUT_GET, 'meta-box-loader' ) ) ) { + return; + } + + $action = 'updated'; + $post_type_name = $this->get_post_type_name( $post_after->post_type ); + + $message = ''; + $fields_updated = ''; + + // Find out what was updated. + foreach ( $post_after as $field => $value ) { + if ( $value === $post_before->$field ) { + continue; + } + + switch ( $field ) { + case 'post_author': + $fields_updated .= sprintf( + __( "post_author updated from %s to %s", 'stream' ), + 'hey', + 'there' + ); + break; + default: + $fields_updated .= sprintf( + __( "%s updated from %s to %s", 'stream' ), + esc_html( $field ), + 'hey', + 'there' + ); + break; + } + + } + + // If it's not a post author, fall back to the default. + + /* translators: %1$s: a post title, %2$s: a post type singular name (e.g. "HelloWorld", "Post") */ + $summary = _x( + '"%1$s" %2$s updated:
%3$s', + '1: Post title, 2: Post type singular name', + 'stream' + ); + + $this->log( + $summary, + array( + 'post_title' => $post_after->post_title, + 'singular_name' => $post_type_name, + 'fields_updated' => $fields_updated, + 'post_date' => $post_after->post_date, + 'post_date_gmt' => $post_after->post_date_gmt, + 'status' => $post_after->post_status, + 'revision_id' => $this->get_revision_id( $post_after ), + ), + $post_after->ID, + $post_after->post_type, + $action + ); + } + /** * Log post deletion * @@ -418,4 +474,35 @@ public function get_adjacent_post_revision( $revision_id, $previous = true ) { return $revision_id; } + + + /** + * Retrieves the ID of the latest revision for a given post. + * + * @param WP_Post $post The post object. + * @return int|null The ID of the latest revision, or null if revisions are not enabled for the post. + */ + public function get_revision_id( \WP_Post $post ) { + $revision_id = null; + + if ( wp_revisions_enabled( $post ) ) { + $revision = get_children( + array( + 'post_type' => 'revision', + 'post_status' => 'inherit', + 'post_parent' => $post->ID, + 'posts_per_page' => 1, // VIP safe. + 'orderby' => 'post_date', + 'order' => 'DESC', + ) + ); + + if ( $revision ) { + $revision = array_values( $revision ); + $revision_id = $revision[0]->ID; + } + } + + return $revision_id; + } } From e3d529a785e46bce88a52efc9f0d6bcb019bb8be Mon Sep 17 00:00:00 2001 From: JJ Date: Thu, 8 Aug 2024 12:01:40 -0400 Subject: [PATCH 2/7] Use post_updated hook for post updates, add separate term log Trying to catch everything that generated a log before but teasing them apart to get more data. --- connectors/class-connector-posts.php | 274 +++++++++++++++++++--- connectors/class-connector-taxonomies.php | 7 - 2 files changed, 245 insertions(+), 36 deletions(-) diff --git a/connectors/class-connector-posts.php b/connectors/class-connector-posts.php index 7c9566ea..3fb3c6f5 100644 --- a/connectors/class-connector-posts.php +++ b/connectors/class-connector-posts.php @@ -7,6 +7,8 @@ namespace WP_Stream; +use WP_Post; + /** * Class - Connector_Posts */ @@ -24,9 +26,9 @@ class Connector_Posts extends Connector { * @var array */ public $actions = array( - 'transition_post_status', 'deleted_post', - 'post_updated' + 'post_updated', + 'set_object_terms', ); /** @@ -151,6 +153,126 @@ public function registered_post_type( $post_type, $args ) { wp_stream_get_instance()->connectors->term_labels['stream_context'][ $post_type ] = $label; } + /** + * Log when post object terms are set. + * + * @param int $object_id Object ID. + * @param array $terms An array of object term IDs or slugs. + * @param array $tt_ids An array of term taxonomy IDs. + * @param string $tax_slug Taxonomy slug. + * @param bool $append Whether to append new terms to the old terms. + * @param array $old_tt_ids Old array of term taxonomy IDs. + * @return void + */ + public function callback_set_object_terms( $object_id, $terms, $tt_ids, $tax_slug, $append, $old_tt_ids ) { + + $object = get_post( $object_id ); + + if ( + ! is_a( $object, 'WP_Post' ) + || + in_array( $object->post_type, $this->get_excluded_post_types(), true ) + ) { + return; + } + + // Only post_tags and categories are included by default. + if ( ! in_array( $tax_slug, $this->get_included_taxonomies( $object_id ), true ) ) { + return; + } + + $taxonomy = get_taxonomy( $tax_slug ); + $tax_name = is_a( $taxonomy, 'WP_Taxonomy' ) ? $taxonomy->labels->singular_name : $tax_slug; + + $removed = array_diff( $old_tt_ids, $tt_ids ); + $added = array_diff( $tt_ids, $old_tt_ids ); + + if ( empty( $removed ) && empty( $added ) ) { + return; + } + + $terms_lists = ''; + + if ( ! empty( $added ) ) { + $added_terms = sprintf( + /* Translators: %s is a linked list of the added terms. */ + __( 'added: %s', 'stream' ), + $this->make_term_list( $added, $tax_slug ) + ); + + $terms_lists .= sprintf( '
  • %s
  • ', $added_terms ); + } + + if ( ! empty( $removed ) ) { + $removed_terms = sprintf( + /* Translators: %s is a linked list of the removed terms. */ + __( 'removed: %s', 'stream' ), + $this->make_term_list( $removed, $tax_slug ) + ); + + $terms_lists .= sprintf( '
  • %s
  • ', $removed_terms ); + } + + /* translators: %1$s: a post title, %2$s: a post type singular name (e.g."HelloWorld", "Post") */ + $summary = _x( + '"%1$s" %2$s %3$s terms updated', + '1: Post title, 2: Post type singular name, 3: Taxonomy name', + 'stream' + ); + + $summary = sprintf( '%s', $summary, $terms_lists ); + + $this->log( + $summary, + array( + 'post_title' => get_the_title( $object_id ), + 'singular_name' => $this->get_post_type_name( $object->post_type ), + 'taxonomy_name' => $tax_name, + 'taxonomy' => $tax_slug, + 'terms_updated' => array( + 'added' => $added, + 'removed' => $removed, + ), + 'post_date' => $object->post_date, + 'post_date_gmt' => $object->post_date_gmt, + 'revision_id' => $this->get_revision_id( $object ), + ), + $object->ID, + $object->post_type, + __( 'Updated Terms' ) + ); + } + + /** + * Generates a list of terms based on the provided term IDs and taxonomy. + * + * @param array $updated The array of term IDs to generate the list from. + * @param string $taxonomy The taxonomy to which the terms belong. + * + * @return string The generated list of terms as HTML links. + */ + private function make_term_list( $updated, $taxonomy ) { + $list = array_reduce( + $updated, + function ( $acc, $id ) use ( $taxonomy ) { + $term = get_term( $id, $taxonomy ); + + if ( empty( $term ) ) { + return $acc; + } + + return $acc .= sprintf( + '%s, ', + get_term_link( $term, $taxonomy ), + $term->name + ); + }, + '' + ); + + return rtrim( $list, ', ' ); + } + /** * Log all post status changes ( creating / updating / trashing ) * @@ -158,9 +280,9 @@ public function registered_post_type( $post_type, $args ) { * * @param mixed $new_status New status. * @param mixed $old_status Old status. - * @param \WP_Post $post Post object. + * @param WP_Post $post Post object. */ - public function callback_transition_post_status( $new_status, $old_status, $post ) { + public function log_transition_post_status( $new_status, $old_status, $post ) { if ( in_array( $post->post_type, $this->get_excluded_post_types(), true ) ) { return; @@ -269,8 +391,6 @@ public function callback_transition_post_status( $new_status, $old_status, $post $post_type_name = strtolower( $this->get_post_type_name( $post->post_type ) ); - add_filter( 'wp_stream_has_tracked_post_updated', '__return_true' ); - $this->log( $summary, array( @@ -288,8 +408,21 @@ public function callback_transition_post_status( $new_status, $old_status, $post ); } + /** + * This currently only looks at the posts table. + * + * @param int|string $post_id The post id. + * @param WP_Post $post_after The post object of the final post. + * @param WP_Post $post_before The post object before it was updated. + * @return void + */ public function callback_post_updated( $post_id, $post_after, $post_before ) { + // Don't log the non-included post types. + if ( in_array( $post_after->post_type, $this->get_excluded_post_types(), true ) ) { + return; + } + // If we have already tracked this change, bail. if ( apply_filters( 'wp_stream_has_tracked_post_updated', false ) ) { return; @@ -303,8 +436,7 @@ public function callback_post_updated( $post_id, $post_after, $post_before ) { $action = 'updated'; $post_type_name = $this->get_post_type_name( $post_after->post_type ); - $message = ''; - $fields_updated = ''; + $fields_updated = array(); // Find out what was updated. foreach ( $post_after as $field => $value ) { @@ -314,43 +446,72 @@ public function callback_post_updated( $post_id, $post_after, $post_before ) { switch ( $field ) { case 'post_author': - $fields_updated .= sprintf( - __( "post_author updated from %s to %s", 'stream' ), - 'hey', - 'there' + $fields_updated['post_author'] = sprintf( + /* Translators: %1$s is the previous post author, %2$s is the current post author */ + __( '%1$s to %2$s', 'stream' ), + $this->get_author_maybe_link( $post_before->post_author ), + $this->get_author_maybe_link( $post_after->post_author ) ); break; + case 'post_modified': + case 'post_modified_gmt': + // Not including these for now. + break; + case 'post_content': + $fields_updated['post_content'] = __( 'updated', 'stream' ); + break; + case 'post_status': + // This is mainly for back compat, post status transitions will be logged in a separate entry. + // However they are all triggered here to account for the block editor flow. + $this->log_transition_post_status( $post_after->post_status, $post_before->post_status, $post_after ); + break; default: - $fields_updated .= sprintf( - __( "%s updated from %s to %s", 'stream' ), - esc_html( $field ), - 'hey', - 'there' + $fields_updated[ $field ] = sprintf( + /* Translators: %1$s is the previous value, %2$s is the current value */ + __( '"%1$s" to "%2$s"', 'stream' ), + esc_html( $post_before->$field ), + esc_html( $value ) ); break; } + } + // If none of the post fields were updated, there should be a log somewhere else. + if ( empty( $fields_updated ) ) { + return; } - // If it's not a post author, fall back to the default. + // Creating a string for the summary. The array will be stored in the meta. + $fields_updated_list_items = array_reduce( + array_keys( $fields_updated ), + function ( $acc, $key ) use ( $fields_updated ) { + return $acc .= sprintf( '
  • %s: %s
  • ', $key, $fields_updated[ $key ] ); + }, + '' + ); /* translators: %1$s: a post title, %2$s: a post type singular name (e.g. "HelloWorld", "Post") */ $summary = _x( - '"%1$s" %2$s updated:
    %3$s', - '1: Post title, 2: Post type singular name', + '"%1$s" %2$s updated', + '1: Post title, 2: Post type singular name, 3: Fields updated list', 'stream' ); - $this->log( + $summary_with_fields = sprintf( + '%s
    ', $summary, + $fields_updated_list_items + ); + + $this->log( + $summary_with_fields, array( - 'post_title' => $post_after->post_title, - 'singular_name' => $post_type_name, + 'post_title' => $post_after->post_title, + 'singular_name' => $post_type_name, 'fields_updated' => $fields_updated, - 'post_date' => $post_after->post_date, - 'post_date_gmt' => $post_after->post_date_gmt, - 'status' => $post_after->post_status, - 'revision_id' => $this->get_revision_id( $post_after ), + 'post_date' => $post_after->post_date, + 'post_date_gmt' => $post_after->post_date_gmt, + 'revision_id' => $this->get_revision_id( $post_after ), ), $post_after->ID, $post_after->post_type, @@ -358,6 +519,35 @@ public function callback_post_updated( $post_id, $post_after, $post_before ) { ); } + /** + * Retrieves the author name with an optional link to the author's profile. + * + * @param int $author_id The ID of the author. + * @return string The author name with an optional link to the author's profile. + */ + private function get_author_maybe_link( $author_id ) { + $author = get_userdata( $author_id ); + + if ( empty( $author ) || is_wp_error( $author ) ) { + /* Translators: %d is the user id. */ + return sprintf( __( 'Unknown user %d', 'stream' ), $author_id ); + } + + $author_name = $author->display_name; + + // This is the same cap check as in `get_edit_user_link()` so we'll use it + // here to return just the name if the link won't work for the current user. + if ( ! current_user_can( 'edit_user', $author_id ) ) { + return $author_name; + } + + return sprintf( + '%s', + esc_url( get_edit_user_link( $author_id ) ), + esc_html( $author_name ) + ); + } + /** * Log post deletion * @@ -369,7 +559,7 @@ public function callback_deleted_post( $post_id ) { $post = get_post( $post_id ); // We check if post is an instance of WP_Post as it doesn't always resolve in unit testing. - if ( ! ( $post instanceof \WP_Post ) || in_array( $post->post_type, $this->get_excluded_post_types(), true ) ) { + if ( ! ( $post instanceof WP_Post ) || in_array( $post->post_type, $this->get_excluded_post_types(), true ) ) { return; } @@ -413,6 +603,32 @@ public function get_excluded_post_types() { ); } + /** + * Retrieves the list of taxonomies to include when logging term changes. + * + * By default, it includes the 'post_tag' and 'category' taxonomies. + * + * @param int|string $post_id The post id. + * + * @return array The list of taxonomies to log. + */ + public function get_included_taxonomies( $post_id ) { + /** + * Filter the taxonomies for which term changes should be logged. + * + * @param array An array of the taxonomies. + * @param int|string The post id. + */ + return apply_filters( + 'wp_stream_posts_include_taxonomies', + array( + 'post_tag', + 'category', + ), + $post_id + ); + } + /** * Gets the singular post type label * @@ -482,7 +698,7 @@ public function get_adjacent_post_revision( $revision_id, $previous = true ) { * @param WP_Post $post The post object. * @return int|null The ID of the latest revision, or null if revisions are not enabled for the post. */ - public function get_revision_id( \WP_Post $post ) { + public function get_revision_id( WP_Post $post ) { $revision_id = null; if ( wp_revisions_enabled( $post ) ) { diff --git a/connectors/class-connector-taxonomies.php b/connectors/class-connector-taxonomies.php index 13e4bdb8..8fea9239 100644 --- a/connectors/class-connector-taxonomies.php +++ b/connectors/class-connector-taxonomies.php @@ -44,13 +44,6 @@ class Connector_Taxonomies extends Connector { */ public $context_labels; - /** - * Register connector in the WP Frontend - * - * @var bool - */ - public $register_frontend = false; - /** * Return translated connector label * From fa57faee5e95d1f5c6559ef1fa125decd870b269 Mon Sep 17 00:00:00 2001 From: JJ Date: Thu, 8 Aug 2024 18:17:03 -0400 Subject: [PATCH 3/7] use json objects when saving metadata --- connectors/class-connector-posts.php | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/connectors/class-connector-posts.php b/connectors/class-connector-posts.php index 3fb3c6f5..82a77081 100644 --- a/connectors/class-connector-posts.php +++ b/connectors/class-connector-posts.php @@ -184,8 +184,11 @@ public function callback_set_object_terms( $object_id, $terms, $tt_ids, $tax_slu $taxonomy = get_taxonomy( $tax_slug ); $tax_name = is_a( $taxonomy, 'WP_Taxonomy' ) ? $taxonomy->labels->singular_name : $tax_slug; - $removed = array_diff( $old_tt_ids, $tt_ids ); - $added = array_diff( $tt_ids, $old_tt_ids ); + $old_tt_int_ids = array_map( 'intval', $old_tt_ids ); + $tt_int_ids = array_map( 'intval', $tt_ids ); + + $removed = array_diff( $old_tt_int_ids, $tt_int_ids ); + $added = array_diff( $tt_int_ids, $old_tt_int_ids ); if ( empty( $removed ) && empty( $added ) ) { return; @@ -229,9 +232,11 @@ public function callback_set_object_terms( $object_id, $terms, $tt_ids, $tax_slu 'singular_name' => $this->get_post_type_name( $object->post_type ), 'taxonomy_name' => $tax_name, 'taxonomy' => $tax_slug, - 'terms_updated' => array( - 'added' => $added, - 'removed' => $removed, + 'terms_updated' => wp_json_encode( + array( + 'added' => $added, + 'removed' => $removed, + ) ), 'post_date' => $object->post_date, 'post_date_gmt' => $object->post_date_gmt, @@ -278,8 +283,8 @@ function ( $acc, $id ) use ( $taxonomy ) { * * @action transition_post_status * - * @param mixed $new_status New status. - * @param mixed $old_status Old status. + * @param mixed $new_status New status. + * @param mixed $old_status Old status. * @param WP_Post $post Post object. */ public function log_transition_post_status( $new_status, $old_status, $post ) { @@ -412,8 +417,8 @@ public function log_transition_post_status( $new_status, $old_status, $post ) { * This currently only looks at the posts table. * * @param int|string $post_id The post id. - * @param WP_Post $post_after The post object of the final post. - * @param WP_Post $post_before The post object before it was updated. + * @param WP_Post $post_after The post object of the final post. + * @param WP_Post $post_before The post object before it was updated. * @return void */ public function callback_post_updated( $post_id, $post_after, $post_before ) { @@ -508,7 +513,7 @@ function ( $acc, $key ) use ( $fields_updated ) { array( 'post_title' => $post_after->post_title, 'singular_name' => $post_type_name, - 'fields_updated' => $fields_updated, + 'fields_updated' => wp_json_encode( $fields_updated ), 'post_date' => $post_after->post_date, 'post_date_gmt' => $post_after->post_date_gmt, 'revision_id' => $this->get_revision_id( $post_after ), From 7769e0e473859d4b056476040d872b0e2ab76237 Mon Sep 17 00:00:00 2001 From: JJ Date: Thu, 8 Aug 2024 19:28:08 -0400 Subject: [PATCH 4/7] wip: next step is to not log post update when post status transition is not update and only log post status update when it is not update --- connectors/class-connector-posts.php | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/connectors/class-connector-posts.php b/connectors/class-connector-posts.php index 82a77081..ca1aa4f9 100644 --- a/connectors/class-connector-posts.php +++ b/connectors/class-connector-posts.php @@ -289,15 +289,6 @@ function ( $acc, $id ) use ( $taxonomy ) { */ public function log_transition_post_status( $new_status, $old_status, $post ) { - if ( in_array( $post->post_type, $this->get_excluded_post_types(), true ) ) { - return; - } - - // We don't want the meta box update request, just the post update. - if ( ! empty( wp_stream_filter_input( INPUT_GET, 'meta-box-loader' ) ) ) { - return; - } - $start_statuses = array( 'auto-draft', 'inherit', 'new' ); if ( in_array( $new_status, $start_statuses, true ) ) { return; @@ -389,7 +380,7 @@ public function log_transition_post_status( $new_status, $old_status, $post ) { } if ( empty( $action ) ) { - return; // We will use a separate callback for updated posts. + $action = 'updated'; // We will use a separate callback for updated posts. } $revision_id = $this->get_revision_id( $post ); @@ -428,11 +419,6 @@ public function callback_post_updated( $post_id, $post_after, $post_before ) { return; } - // If we have already tracked this change, bail. - if ( apply_filters( 'wp_stream_has_tracked_post_updated', false ) ) { - return; - } - // We don't want the meta box update request either, just the post update. if ( ! empty( wp_stream_filter_input( INPUT_GET, 'meta-box-loader' ) ) ) { return; From 1a3a85918610cd6a99532380a6a8fb573ce6a73f Mon Sep 17 00:00:00 2001 From: JJ Date: Fri, 9 Aug 2024 09:38:01 -0400 Subject: [PATCH 5/7] wip: using action to pass through data about updated terms, updating summary --- connectors/class-connector-posts.php | 116 +++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 16 deletions(-) diff --git a/connectors/class-connector-posts.php b/connectors/class-connector-posts.php index ca1aa4f9..d15e1577 100644 --- a/connectors/class-connector-posts.php +++ b/connectors/class-connector-posts.php @@ -31,6 +31,35 @@ class Connector_Posts extends Connector { 'set_object_terms', ); + /** + * Adds an action to retrieve previous post data before updating a post. + */ + public function register() { + parent::register(); + add_action( 'pre_post_update', array( $this, 'get_previous_post_data' ), 10, 2 ); + } + + /** + * Get the previous post versions terms. + * + * @param int|string] $post_id + * @return void + */ + public function get_previous_post_data( $post_id ) { + + $terms = []; + foreach( $this->get_included_taxonomies( $post_id ) as $tax ) { + $tax_terms = wp_list_pluck( get_the_terms( $post_id, $tax ), 'term_taxonomy_id' ); + if ( ! empty( $tax_terms ) && ! is_wp_error( $tax_terms ) ) { + $terms[ $tax ] = $tax_terms; + } + } + + add_filter( "wp_stream_previous_{$post_id}_terms", static function () use ( $terms ) { + return $terms; + } ); + } + /** * Return translated connector label * @@ -467,19 +496,78 @@ public function callback_post_updated( $post_id, $post_after, $post_before ) { } } - // If none of the post fields were updated, there should be a log somewhere else. - if ( empty( $fields_updated ) ) { + $updated_terms = []; + $included_taxes = $this->get_included_taxonomies( $post_after->ID ); + $post_before_terms = apply_filters( "wp_stream_previous_{$post_id}_terms", false ); + + // Only do this if the filter is working. + if ( false !== $post_before_terms ) { + + foreach( $included_taxes as $tax ) { + + $tax_terms = wp_list_pluck( get_the_terms( $post_after->ID, $tax ), 'term_taxonomy_id' ); + + $added = array_diff( $tax_terms, $post_before_terms[ $tax ] ?? [] ); + $removed = array_diff( $post_before_terms[ $tax ] ?? [], $tax_terms ); + + if ( ! empty( $added ) ) { + $updated_terms[ $tax ][ 'added' ] = $added; + } + + if ( ! empty( $removed ) ) { + $updated_terms[ $tax ][ 'removed' ] = $removed; + } + } + } + + // If none of the post fields or terms were updated, there should be a log somewhere else. + if ( empty( $fields_updated ) && empty( $updated_terms ) ) { return; } - // Creating a string for the summary. The array will be stored in the meta. - $fields_updated_list_items = array_reduce( - array_keys( $fields_updated ), - function ( $acc, $key ) use ( $fields_updated ) { - return $acc .= sprintf( '
  • %s: %s
  • ', $key, $fields_updated[ $key ] ); - }, - '' - ); + $details = ''; + + if ( ! empty( $updated_terms ) ) { + foreach( $updated_terms as $tax => $term_updates ) { + $taxonomy = get_taxonomy( $tax ); + $tax_name = is_a( $taxonomy, 'WP_Taxonomy' ) ? $taxonomy->labels->singular_name : $tax; + if ( ! empty( $term_updates['added'] ) ) { + $details .= sprintf( + /* Translators: %1$s is the taxonomy slug and %2$s is a linked list of the added terms. */ + __( ' %1$s terms added: %2$s ', 'stream' ), + $tax_name, + $this->make_term_list( $term_updates['added'], $tax ) + ); + } + + if ( ! empty( $term_updates['removed'] ) ) { + $removed_terms = sprintf( + /* Translators: %1$s is the taxonomy slug and %2$s is a linked list of the removed terms. */ + __( ' %1$s terms added: %2$s ', 'stream' ), + $tax_name, + $this->make_term_list( $term_updates['removed'], $tax ) + ); + + $details .= sprintf( '%s', $removed_terms ); + } + } + } + + if ( ! empty( $fields_updated ) ) { + // Creating a string for the summary. The array will be stored in the meta. + $details .= array_reduce( + array_keys( $fields_updated ), + function ( $acc, $key ) use ( $fields_updated ) { + return $acc .= sprintf( ' %s: %s, ', $key, $fields_updated[ $key ] ); + }, + '' + ); + + $details = rtrim( $details, ', ' ); + } + + + /* translators: %1$s: a post title, %2$s: a post type singular name (e.g. "HelloWorld", "Post") */ $summary = _x( @@ -488,14 +576,10 @@ function ( $acc, $key ) use ( $fields_updated ) { 'stream' ); - $summary_with_fields = sprintf( - '%s
      %s
    ', - $summary, - $fields_updated_list_items - ); + $log_summary = apply_filters( 'wp_stream_post_updated_summary', "{$summary}
    {$details}", $post_after, $post_before, $post_before_terms ); $this->log( - $summary_with_fields, + $log_summary, array( 'post_title' => $post_after->post_title, 'singular_name' => $post_type_name, From 8c2ca4f0b0547cb8a1d78d90b9c4c5423c7c4b31 Mon Sep 17 00:00:00 2001 From: JJ Date: Fri, 9 Aug 2024 12:24:33 -0400 Subject: [PATCH 6/7] wip: nearly working but need rest api support --- connectors/class-connector-posts.php | 199 ++++++++------------------- 1 file changed, 61 insertions(+), 138 deletions(-) diff --git a/connectors/class-connector-posts.php b/connectors/class-connector-posts.php index d15e1577..23864ae1 100644 --- a/connectors/class-connector-posts.php +++ b/connectors/class-connector-posts.php @@ -8,6 +8,7 @@ namespace WP_Stream; use WP_Post; +use WP_Taxonomy; /** * Class - Connector_Posts @@ -28,6 +29,7 @@ class Connector_Posts extends Connector { public $actions = array( 'deleted_post', 'post_updated', + 'transition_post_status', 'set_object_terms', ); @@ -36,27 +38,20 @@ class Connector_Posts extends Connector { */ public function register() { parent::register(); - add_action( 'pre_post_update', array( $this, 'get_previous_post_data' ), 10, 2 ); + add_action( 'set_object_terms', array( $this, 'get_previous_post_terms' ), 10, 6 ); } /** - * Get the previous post versions terms. + * Get the previous post version's terms. * * @param int|string] $post_id * @return void */ - public function get_previous_post_data( $post_id ) { + public function get_previous_post_terms( $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids ) { - $terms = []; - foreach( $this->get_included_taxonomies( $post_id ) as $tax ) { - $tax_terms = wp_list_pluck( get_the_terms( $post_id, $tax ), 'term_taxonomy_id' ); - if ( ! empty( $tax_terms ) && ! is_wp_error( $tax_terms ) ) { - $terms[ $tax ] = $tax_terms; - } - } - - add_filter( "wp_stream_previous_{$post_id}_terms", static function () use ( $terms ) { - return $terms; + $hey = $taxonomy; + add_filter( "wp_stream_previous_{$object_id}_{$taxonomy}_terms", static function () use ( $old_tt_ids ) { + return array( $old_tt_ids ); } ); } @@ -84,6 +79,11 @@ public function get_action_labels() { ); } + public function callback_set_object_terms( $object_id, $terms, $tt_ids, $tax_slug, $append, $old_tt_ids ) { + $debug = wp_debug_backtrace_summary(); + $hey = 'there'; + } + /** * Return translated context labels * @@ -182,101 +182,6 @@ public function registered_post_type( $post_type, $args ) { wp_stream_get_instance()->connectors->term_labels['stream_context'][ $post_type ] = $label; } - /** - * Log when post object terms are set. - * - * @param int $object_id Object ID. - * @param array $terms An array of object term IDs or slugs. - * @param array $tt_ids An array of term taxonomy IDs. - * @param string $tax_slug Taxonomy slug. - * @param bool $append Whether to append new terms to the old terms. - * @param array $old_tt_ids Old array of term taxonomy IDs. - * @return void - */ - public function callback_set_object_terms( $object_id, $terms, $tt_ids, $tax_slug, $append, $old_tt_ids ) { - - $object = get_post( $object_id ); - - if ( - ! is_a( $object, 'WP_Post' ) - || - in_array( $object->post_type, $this->get_excluded_post_types(), true ) - ) { - return; - } - - // Only post_tags and categories are included by default. - if ( ! in_array( $tax_slug, $this->get_included_taxonomies( $object_id ), true ) ) { - return; - } - - $taxonomy = get_taxonomy( $tax_slug ); - $tax_name = is_a( $taxonomy, 'WP_Taxonomy' ) ? $taxonomy->labels->singular_name : $tax_slug; - - $old_tt_int_ids = array_map( 'intval', $old_tt_ids ); - $tt_int_ids = array_map( 'intval', $tt_ids ); - - $removed = array_diff( $old_tt_int_ids, $tt_int_ids ); - $added = array_diff( $tt_int_ids, $old_tt_int_ids ); - - if ( empty( $removed ) && empty( $added ) ) { - return; - } - - $terms_lists = ''; - - if ( ! empty( $added ) ) { - $added_terms = sprintf( - /* Translators: %s is a linked list of the added terms. */ - __( 'added: %s', 'stream' ), - $this->make_term_list( $added, $tax_slug ) - ); - - $terms_lists .= sprintf( '
  • %s
  • ', $added_terms ); - } - - if ( ! empty( $removed ) ) { - $removed_terms = sprintf( - /* Translators: %s is a linked list of the removed terms. */ - __( 'removed: %s', 'stream' ), - $this->make_term_list( $removed, $tax_slug ) - ); - - $terms_lists .= sprintf( '
  • %s
  • ', $removed_terms ); - } - - /* translators: %1$s: a post title, %2$s: a post type singular name (e.g."HelloWorld", "Post") */ - $summary = _x( - '"%1$s" %2$s %3$s terms updated', - '1: Post title, 2: Post type singular name, 3: Taxonomy name', - 'stream' - ); - - $summary = sprintf( '%s
      %s
    ', $summary, $terms_lists ); - - $this->log( - $summary, - array( - 'post_title' => get_the_title( $object_id ), - 'singular_name' => $this->get_post_type_name( $object->post_type ), - 'taxonomy_name' => $tax_name, - 'taxonomy' => $tax_slug, - 'terms_updated' => wp_json_encode( - array( - 'added' => $added, - 'removed' => $removed, - ) - ), - 'post_date' => $object->post_date, - 'post_date_gmt' => $object->post_date_gmt, - 'revision_id' => $this->get_revision_id( $object ), - ), - $object->ID, - $object->post_type, - __( 'Updated Terms' ) - ); - } - /** * Generates a list of terms based on the provided term IDs and taxonomy. * @@ -316,7 +221,18 @@ function ( $acc, $id ) use ( $taxonomy ) { * @param mixed $old_status Old status. * @param WP_Post $post Post object. */ - public function log_transition_post_status( $new_status, $old_status, $post ) { + public function callback_transition_post_status( $new_status, $old_status, $post ) { + + + // Don't log the non-included post types. + if ( ! ( $post instanceof WP_Post ) || in_array( $post->post_type, $this->get_excluded_post_types(), true ) ) { + return; + } + + // We don't want the meta box update request either, just the postupdate. + if ( ! empty( wp_stream_filter_input( INPUT_GET, 'meta-box-loader' ) ) ){ + return; + } $start_statuses = array( 'auto-draft', 'inherit', 'new' ); if ( in_array( $new_status, $start_statuses, true ) ) { @@ -397,11 +313,11 @@ public function log_transition_post_status( $new_status, $old_status, $post ) { $action = 'trashed'; } else { /* translators: %1$s: a post title, %2$s: a post type singular name (e.g. "Hello World", "Post") */ - $summary = _x( - '"%1$s" %2$s updated', - '1: Post title, 2: Post type singular name', - 'stream' - ); + $summary = false; + } + + if ( ! $summary ) { + return; } if ( in_array( $old_status, $start_statuses, true ) && ! in_array( $new_status, $start_statuses, true ) ) { @@ -409,7 +325,7 @@ public function log_transition_post_status( $new_status, $old_status, $post ) { } if ( empty( $action ) ) { - $action = 'updated'; // We will use a separate callback for updated posts. + $action = 'updated'; } $revision_id = $this->get_revision_id( $post ); @@ -456,7 +372,7 @@ public function callback_post_updated( $post_id, $post_after, $post_before ) { $action = 'updated'; $post_type_name = $this->get_post_type_name( $post_after->post_type ); - $fields_updated = array(); + $updated_fields = array(); // Find out what was updated. foreach ( $post_after as $field => $value ) { @@ -466,27 +382,24 @@ public function callback_post_updated( $post_id, $post_after, $post_before ) { switch ( $field ) { case 'post_author': - $fields_updated['post_author'] = sprintf( + $updated_fields['post_author'] = sprintf( /* Translators: %1$s is the previous post author, %2$s is the current post author */ __( '%1$s to %2$s', 'stream' ), $this->get_author_maybe_link( $post_before->post_author ), $this->get_author_maybe_link( $post_after->post_author ) ); break; + // Not including these for now. case 'post_modified': case 'post_modified_gmt': - // Not including these for now. + // Handled in transition_post_status hook. + case 'post_status': break; case 'post_content': - $fields_updated['post_content'] = __( 'updated', 'stream' ); - break; - case 'post_status': - // This is mainly for back compat, post status transitions will be logged in a separate entry. - // However they are all triggered here to account for the block editor flow. - $this->log_transition_post_status( $post_after->post_status, $post_before->post_status, $post_after ); + $updated_fields['post_content'] = __( 'updated', 'stream' ); break; default: - $fields_updated[ $field ] = sprintf( + $updated_fields[ $field ] = sprintf( /* Translators: %1$s is the previous value, %2$s is the current value */ __( '"%1$s" to "%2$s"', 'stream' ), esc_html( $post_before->$field ), @@ -498,17 +411,24 @@ public function callback_post_updated( $post_id, $post_after, $post_before ) { $updated_terms = []; $included_taxes = $this->get_included_taxonomies( $post_after->ID ); - $post_before_terms = apply_filters( "wp_stream_previous_{$post_id}_terms", false ); + $post_before_terms = true; // Only do this if the filter is working. if ( false !== $post_before_terms ) { foreach( $included_taxes as $tax ) { + $previous_terms = apply_filters( "wp_stream_previous_{$post_after->ID}_{$tax}_terms", false ); + + // Bail if the filter failed. + if ( false === $previous_terms ) { + continue; + } + $tax_terms = wp_list_pluck( get_the_terms( $post_after->ID, $tax ), 'term_taxonomy_id' ); - $added = array_diff( $tax_terms, $post_before_terms[ $tax ] ?? [] ); - $removed = array_diff( $post_before_terms[ $tax ] ?? [], $tax_terms ); + $added = array_diff( $tax_terms, $previous_terms ); + $removed = array_diff( $previous_terms, $tax_terms ); if ( ! empty( $added ) ) { $updated_terms[ $tax ][ 'added' ] = $added; @@ -521,7 +441,7 @@ public function callback_post_updated( $post_id, $post_after, $post_before ) { } // If none of the post fields or terms were updated, there should be a log somewhere else. - if ( empty( $fields_updated ) && empty( $updated_terms ) ) { + if ( empty( $updated_fields ) && empty( $updated_terms ) ) { return; } @@ -530,35 +450,37 @@ public function callback_post_updated( $post_id, $post_after, $post_before ) { if ( ! empty( $updated_terms ) ) { foreach( $updated_terms as $tax => $term_updates ) { $taxonomy = get_taxonomy( $tax ); - $tax_name = is_a( $taxonomy, 'WP_Taxonomy' ) ? $taxonomy->labels->singular_name : $tax; + $tax_name = ( $taxonomy instanceof WP_Taxonomy ) ? $taxonomy->labels->singular_name : $tax; if ( ! empty( $term_updates['added'] ) ) { - $details .= sprintf( + $added_terms = sprintf( /* Translators: %1$s is the taxonomy slug and %2$s is a linked list of the added terms. */ __( ' %1$s terms added: %2$s ', 'stream' ), $tax_name, $this->make_term_list( $term_updates['added'], $tax ) ); + $details .= sprintf( '%s
    ', $added_terms ); } if ( ! empty( $term_updates['removed'] ) ) { $removed_terms = sprintf( /* Translators: %1$s is the taxonomy slug and %2$s is a linked list of the removed terms. */ - __( ' %1$s terms added: %2$s ', 'stream' ), + __( ' %1$s terms removed: %2$s', 'stream' ), $tax_name, $this->make_term_list( $term_updates['removed'], $tax ) ); - $details .= sprintf( '%s', $removed_terms ); + $details .= sprintf( '%s
    ', $removed_terms ); } } } - if ( ! empty( $fields_updated ) ) { + if ( ! empty( $updated_fields ) ) { + $details .= __( 'Post updates: ', 'stream' ); // Creating a string for the summary. The array will be stored in the meta. $details .= array_reduce( - array_keys( $fields_updated ), - function ( $acc, $key ) use ( $fields_updated ) { - return $acc .= sprintf( ' %s: %s, ', $key, $fields_updated[ $key ] ); + array_keys( $updated_fields ), + function ( $acc, $key ) use ( $updated_fields ) { + return $acc .= sprintf( ' %s: %s, ', $key, $updated_fields[ $key ] ); }, '' ); @@ -583,7 +505,8 @@ function ( $acc, $key ) use ( $fields_updated ) { array( 'post_title' => $post_after->post_title, 'singular_name' => $post_type_name, - 'fields_updated' => wp_json_encode( $fields_updated ), + 'fields_updated' => wp_json_encode( $updated_fields ), + 'terms_updated' => wp_json_encode( $updated_terms ), 'post_date' => $post_after->post_date, 'post_date_gmt' => $post_after->post_date_gmt, 'revision_id' => $this->get_revision_id( $post_after ), From 4ffcb688cbb96288b24eb0e57c0b239fe424eb18 Mon Sep 17 00:00:00 2001 From: JJ Date: Mon, 12 Aug 2024 16:24:42 -0400 Subject: [PATCH 7/7] use wp_after_insert_post for post updates --- connectors/class-connector-posts.php | 75 +++++++++++++++++----------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/connectors/class-connector-posts.php b/connectors/class-connector-posts.php index 23864ae1..575c0c07 100644 --- a/connectors/class-connector-posts.php +++ b/connectors/class-connector-posts.php @@ -28,7 +28,7 @@ class Connector_Posts extends Connector { */ public $actions = array( 'deleted_post', - 'post_updated', + 'wp_after_insert_post', 'transition_post_status', 'set_object_terms', ); @@ -42,17 +42,24 @@ public function register() { } /** - * Get the previous post version's terms. + * Add an array with the previous terms to a filter for future use. * - * @param int|string] $post_id + * @param int|string] $object_id The post id. + * @param array $terms The current terms. + * @param array $tt_ids The current term taxonomy ids. + * @param string $taxonomy The taxonomy slug. + * @param bool $append Whether or not the terms were appended. + * @param array $old_tt_ids The previous term taxonomy ids. * @return void */ - public function get_previous_post_terms( $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids ) { + public function get_previous_post_terms( $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids ) { - $hey = $taxonomy; - add_filter( "wp_stream_previous_{$object_id}_{$taxonomy}_terms", static function () use ( $old_tt_ids ) { - return array( $old_tt_ids ); - } ); + add_filter( + "wp_stream_previous_{$object_id}_{$taxonomy}_terms", + static function () use ( $old_tt_ids ) { + return (array) $old_tt_ids; + } + ); } /** @@ -79,11 +86,6 @@ public function get_action_labels() { ); } - public function callback_set_object_terms( $object_id, $terms, $tt_ids, $tax_slug, $append, $old_tt_ids ) { - $debug = wp_debug_backtrace_summary(); - $hey = 'there'; - } - /** * Return translated context labels * @@ -196,7 +198,7 @@ private function make_term_list( $updated, $taxonomy ) { function ( $acc, $id ) use ( $taxonomy ) { $term = get_term( $id, $taxonomy ); - if ( empty( $term ) ) { + if ( empty( $term ) || is_wp_error( $term ) ) { return $acc; } @@ -223,17 +225,21 @@ function ( $acc, $id ) use ( $taxonomy ) { */ public function callback_transition_post_status( $new_status, $old_status, $post ) { - // Don't log the non-included post types. if ( ! ( $post instanceof WP_Post ) || in_array( $post->post_type, $this->get_excluded_post_types(), true ) ) { return; } // We don't want the meta box update request either, just the postupdate. - if ( ! empty( wp_stream_filter_input( INPUT_GET, 'meta-box-loader' ) ) ){ + if ( ! empty( wp_stream_filter_input( INPUT_GET, 'meta-box-loader' ) ) ) { return; } + /** + * Whether or not there should also be a "post updated" log. + */ + $should_log_update = false; + $start_statuses = array( 'auto-draft', 'inherit', 'new' ); if ( in_array( $new_status, $start_statuses, true ) ) { return; @@ -332,6 +338,7 @@ public function callback_transition_post_status( $new_status, $old_status, $post $post_type_name = strtolower( $this->get_post_type_name( $post->post_type ) ); + add_filter( 'wp_stream_has_post_transition_log', '__return_true' ); $this->log( $summary, array( @@ -354,13 +361,14 @@ public function callback_transition_post_status( $new_status, $old_status, $post * * @param int|string $post_id The post id. * @param WP_Post $post_after The post object of the final post. + * @param bool $update Whether or not this is an updated post. * @param WP_Post $post_before The post object before it was updated. * @return void */ - public function callback_post_updated( $post_id, $post_after, $post_before ) { + public function callback_wp_after_insert_post( $post_id, $post_after, $update, $post_before ) { - // Don't log the non-included post types. - if ( in_array( $post_after->post_type, $this->get_excluded_post_types(), true ) ) { + // Don't log newly created posts or the non-included post types. + if ( ! $update || in_array( $post_after->post_type, $this->get_excluded_post_types(), true ) ) { return; } @@ -369,6 +377,12 @@ public function callback_post_updated( $post_id, $post_after, $post_before ) { return; } + $start_statuses = array( 'auto-draft', 'inherit', 'new' ); + if ( + in_array( $post_after->post_status, $start_statuses, true ) || in_array( $post_before->post_status, $start_statuses, true ) || ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) ) { + return; + } + $action = 'updated'; $post_type_name = $this->get_post_type_name( $post_after->post_type ); @@ -392,12 +406,18 @@ public function callback_post_updated( $post_id, $post_after, $post_before ) { // Not including these for now. case 'post_modified': case 'post_modified_gmt': - // Handled in transition_post_status hook. + // Handled in transition_post_status hook. case 'post_status': break; case 'post_content': $updated_fields['post_content'] = __( 'updated', 'stream' ); break; + case 'post_date': + case 'post_date_gmt': + if ( apply_filters( 'wp_stream_has_post_transition_log', false ) ) { + break; + } + // Break if there's a log, otherwise pass through. default: $updated_fields[ $field ] = sprintf( /* Translators: %1$s is the previous value, %2$s is the current value */ @@ -409,14 +429,14 @@ public function callback_post_updated( $post_id, $post_after, $post_before ) { } } - $updated_terms = []; + $updated_terms = array(); $included_taxes = $this->get_included_taxonomies( $post_after->ID ); $post_before_terms = true; // Only do this if the filter is working. if ( false !== $post_before_terms ) { - foreach( $included_taxes as $tax ) { + foreach ( $included_taxes as $tax ) { $previous_terms = apply_filters( "wp_stream_previous_{$post_after->ID}_{$tax}_terms", false ); @@ -428,14 +448,14 @@ public function callback_post_updated( $post_id, $post_after, $post_before ) { $tax_terms = wp_list_pluck( get_the_terms( $post_after->ID, $tax ), 'term_taxonomy_id' ); $added = array_diff( $tax_terms, $previous_terms ); - $removed = array_diff( $previous_terms, $tax_terms ); + $removed = array_diff( $previous_terms, $tax_terms ); if ( ! empty( $added ) ) { - $updated_terms[ $tax ][ 'added' ] = $added; + $updated_terms[ $tax ]['added'] = $added; } if ( ! empty( $removed ) ) { - $updated_terms[ $tax ][ 'removed' ] = $removed; + $updated_terms[ $tax ]['removed'] = $removed; } } } @@ -448,7 +468,7 @@ public function callback_post_updated( $post_id, $post_after, $post_before ) { $details = ''; if ( ! empty( $updated_terms ) ) { - foreach( $updated_terms as $tax => $term_updates ) { + foreach ( $updated_terms as $tax => $term_updates ) { $taxonomy = get_taxonomy( $tax ); $tax_name = ( $taxonomy instanceof WP_Taxonomy ) ? $taxonomy->labels->singular_name : $tax; if ( ! empty( $term_updates['added'] ) ) { @@ -488,9 +508,6 @@ function ( $acc, $key ) use ( $updated_fields ) { $details = rtrim( $details, ', ' ); } - - - /* translators: %1$s: a post title, %2$s: a post type singular name (e.g. "HelloWorld", "Post") */ $summary = _x( '"%1$s" %2$s updated',