config = $config; $this->workflow = $workflow; $this->recipients = $recipients; $this->mailer = $mailer; $this->queue = $queue; } /** * Register hooks. * * @return void */ public function register_hooks() { add_action( 'ostew/after_transition', array( $this, 'on_transition' ), 10, 5 ); } /** * Handle a transition: match rules, dispatch. * * @param int $post_id Post ID. * @param string $old_status Old status. * @param string $new_status New status. * @param int $actor_id Acting user ID. * @param string $note Editorial note. * @return void */ public function on_transition( $post_id, $old_status, $new_status, $actor_id, $note ) { $post = get_post( $post_id ); if ( ! $post ) { return; } $workflow = $this->workflow->for_post( $post ); if ( ! $workflow || empty( $workflow['notifications'] ) ) { return; } // Bust cached review counts (statuses changed). $this->flush_queue_cache(); foreach ( $workflow['notifications'] as $rule ) { if ( ! isset( $rule['on_status'] ) || $rule['on_status'] !== $new_status ) { continue; } if ( isset( $rule['when_from'] ) && ! in_array( $old_status, (array) $rule['when_from'], true ) ) { continue; } $channels = isset( $rule['channels'] ) ? (array) $rule['channels'] : array( 'email' ); $recipients = $this->recipients->resolve( $rule['notify'], $post, $workflow, $new_status, $actor_id ); /** * Filter the resolved recipients for a notification rule. * * @param \WP_User[] $recipients Recipients. * @param array $rule Notification rule. * @param \WP_Post $post Post. */ $recipients = apply_filters( 'ostew/notify_recipients', $recipients, $rule, $post ); if ( empty( $recipients ) ) { continue; } $vars = $this->merge_vars( $post, $old_status, $new_status, $actor_id, $note, $workflow ); $template = isset( $rule['template'] ) ? $rule['template'] : 'review_request'; foreach ( $recipients as $user ) { if ( in_array( 'email', $channels, true ) ) { $this->mailer->send( $template, $user, $vars ); } } /** * Fires after a notification rule is dispatched (a Slack/Teams add-on hooks here). * * @param array $rule Notification rule. * @param \WP_User[] $recipients Recipients. * @param \WP_Post $post Post. * @param array $vars Merge vars. */ do_action( 'ostew/send_notification', $rule, $recipients, $post, $vars ); } } /** * Build the merge variables for templates. * * @param \WP_Post $post Post. * @param string $old_status Old status. * @param string $new_status New status. * @param int $actor_id Acting user ID. * @param string $note Editorial note. * @param array $workflow Workflow. * @return array */ private function merge_vars( $post, $old_status, $new_status, $actor_id, $note, array $workflow ) { $actor = get_user_by( 'id', $actor_id ); $pt_obj = get_post_type_object( $post->post_type ); return array( 'post_title' => get_the_title( $post ), 'post_type_label' => $pt_obj ? $pt_obj->labels->singular_name : $post->post_type, 'editor_url' => (string) get_edit_post_link( $post->ID, 'raw' ), 'view_url' => (string) get_permalink( $post->ID ), 'actor_name' => $actor ? $actor->display_name : __( 'Someone', 'ost-editorial-workflow' ), 'from_status' => $this->workflow->status_label( $workflow, $old_status ), 'to_status' => $this->workflow->status_label( $workflow, $new_status ), 'note' => $note, 'site_name' => get_bloginfo( 'name' ), 'recipient_name' => '', ); } /** * Delete cached per-user review counts. * * @return void */ private function flush_queue_cache() { global $wpdb; // Small, best-effort: clear all ostew_qcount_* transients. $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_ostew_qcount_%' OR option_name LIKE '_transient_timeout_ostew_qcount_%'" ); } }