LLMS_Post_Relationships
Hooks and actions related to post relationships.
Contents
Source Source
File: includes/class.llms.post.relationships.php
class LLMS_Post_Relationships { /** * Configure relationships * * @var array */ private $relationships = array( 'course' => array( array( 'action' => 'delete', 'meta_key' => '_llms_product_id', 'post_type' => 'llms_access_plan', ), ), 'llms_membership' => array( array( 'action' => 'delete', 'meta_key' => '_llms_product_id', 'post_type' => 'llms_access_plan', ), ), 'lesson' => array( array( 'action' => 'unset', 'meta_key' => '_llms_prerequisite', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_keys_additional' => array( '_llms_has_prerequisite' ), 'post_type' => 'lesson', ), array( 'action' => 'unset', 'meta_key' => '_llms_lesson_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'post_type' => 'llms_quiz', ), ), 'llms_order' => array( array( 'action' => 'delete', 'meta_key' => '_llms_order_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'post_type' => 'llms_transaction', ), ), 'llms_quiz' => array( array( 'action' => 'delete', // Delete = force delete; trash = move to trash. 'meta_key' => '_llms_parent_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'post_type' => 'llms_question', ), array( 'action' => 'unset', 'meta_key' => '_llms_quiz', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_keys_additional' => array( '_llms_quiz_enabled' ), 'post_type' => 'lesson', ), array( 'action' => 'delete', 'table_name' => 'lifterlms_quiz_attempts', 'table_key' => 'quiz_id', ), ), ); /** * Constructor. * * @since 3.16.12 * @since 5.4.0 Prevent course/membership with active subscriptions deletion. * @since 6.0.0 Added hook to cleanup user post meta data when awarded certs and achievements are deleted. * * @return void */ public function __construct() { add_action( 'delete_post', array( $this, 'maybe_update_relationships' ) ); add_action( 'pre_delete_post', array( __CLASS__, 'maybe_prevent_product_deletion' ), 10, 2 ); add_action( 'before_delete_post', array( __CLASS__, 'maybe_clean_earned_engagments_related_user_post_meta' ) ); } /** * Maybe delete LifterLMS user post meta related to earned engagements. * * @since 6.0.0 * * @param int $post_id Post ID. * @return void */ public static function maybe_clean_earned_engagments_related_user_post_meta( $post_id ) { $post_types = array( 'llms_my_certificate', 'llms_my_achievement', ); $post_type = get_post_type( $post_id ); if ( ! in_array( $post_type, $post_types, true ) ) { return; } $earned_engagement = 'llms_my_certificate' === $post_type ? new LLMS_User_Certificate( $post_id ) : new LLMS_User_Achievement( $post_id ); do_action_deprecated( 'llms_before_delete_' . str_replace( 'llms_my_', '', $post_type ), array( $earned_engagement, ), '6.0.0', '', __( 'Use WordPress core `before_delete_post` action hook', 'lifterlms' ) ); global $wpdb; $wpdb->delete( "{$wpdb->prefix}lifterlms_user_postmeta", array( 'user_id' => $earned_engagement->get_user_id(), 'meta_key' => '_' . str_replace( 'llms_my_', '', $post_type ) . '_earned', 'meta_value' => $post_id, ), array( '%d', '%s', '%d' ) ); // no-cache ok. add_action( 'after_delete_post', function( $post_id ) use ( $earned_engagement, $post_type ) { if ( $earned_engagement->get( 'id' ) === $post_id ) { do_action_deprecated( 'llms_delete_' . str_replace( 'llms_my_', '', $post_type ), array( $earned_engagement, ), '6.0.0', '', __( 'Use WordPress core `deleted_post` action hook.', 'lifterlms' ) ); } } ); } /** * Determine whether a product deletion should take place. * * @since 5.4.0 * * @param bool|null $delete Whether to go forward with deletion. * @param WP_Post $post Post object. * @return bool|null */ public static function maybe_prevent_product_deletion( $delete, $post ) { if ( ! in_array( get_post_type( $post ), array( 'course', 'llms_membership' ), true ) ) { return $delete; } $product = llms_get_product( $post ); if ( empty( $product ) || ! $product->has_active_subscriptions() ) { return $delete; } // If performing the deletion via REST API change the error message to reflect the reason for the prevention. if ( llms_is_rest() ) { // Filter the error message. add_filter( 'rest_request_after_callbacks', array( __CLASS__, 'rest_filter_products_with_active_subscriptions_error_message' ), 10, 3 ); } else { // Deleting via wp-admin. wp_die( self::delete_product_with_active_subscriptions_error_message( $product->get( 'id' ) ) ); } return false; } /** * Filter the error message returned when trying to delete a product with active subscription via REST API. * * The original message is a standard permission denied message. * * @since 5.4.0 * * @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client. * Usually a WP_REST_Response or WP_Error. * @param array $handler Route handler used for the request. * @param WP_REST_Request $request Request used to generate the response. * @return WP_REST_Response|WP_HTTP_Response|WP_Error|mixed */ public static function rest_filter_products_with_active_subscriptions_error_message( $response, $handler, $request ) { if ( is_wp_error( $response ) ) { foreach ( $response->errors as $code => &$data ) { // Error code can be produced by our rest-api or by wp core. if ( in_array( $code, array( 'llms_rest_cannot_delete', 'rest_cannot_delete' ), true ) ) { $data[0] = self::delete_product_with_active_subscriptions_error_message( $request['id'] ); break; } } } return $response; } /** * Returns the error message to display when deleting a product with active subscriptions. * * @since 5.4.0 * * @param int $post_id The WP_Post ID of the product. * @return string */ public static function delete_product_with_active_subscriptions_error_message( $post_id ) { $post_type = get_post_type( $post_id ); if ( ! in_array( $post_type, array( 'course', 'llms_membership' ), true ) ) { return ''; } $post_type_object = get_post_type_object( $post_type ); $post_type_name = $post_type_object->labels->name; return sprintf( // Translators: %s = The post type plural name. __( 'Sorry, you are not allowed to delete %s with active subscriptions.', 'lifterlms' ), $post_type_name ); } /** * Delete / Trash posts related to the deleted post. * * @since 3.16.12 * @since 3.37.8 Allow for deletion of related items outside the WP core posts table. * * @param WP_Post $post WP Post that's been deleted. * @param array $data Relationship data array. * @return void */ private function delete_relationships( $post, $data ) { if ( isset( $data['post_type'] ) && isset( $data['meta_key'] ) ) { $this->delete_wp_posts( $post, $data ); } elseif ( isset( $data['table_name'] ) && isset( $data['table_key'] ) ) { $this->delete_table_records( $post, $data ); } } /** * Delete records from a table that are related to the deleted post. * * @since 3.37.8 * * @param WP_Post $post WP Post that's been deleted. * @param array $data Relationship data array. * @return void */ private function delete_table_records( $post, $data ) { global $wpdb; $wpdb->delete( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prefix . $data['table_name'], array( $data['table_key'] => $post->ID, ), '%d' ); } /** * Delete or trash WP Posts related to the deleted post. * * @since 3.37.8 * * @param WP_Post $post WP Post that's been deleted. * @param array $data Relationship data array. * @return void */ private function delete_wp_posts( $post, $data ) { $relationships = $this->get_related_posts( $post->ID, $data['post_type'], $data['meta_key'] ); $force = ( 'delete' === $data['action'] ); foreach ( $relationships as $id ) { wp_delete_post( $id, $force ); } } /** * Get a list of post types with relationships that should be checked. * * @since 3.16.12 * * @return array */ private function get_post_types() { return array_keys( $this->get_relationships() ); } /** * Retrieve filtered LifterLMS post relationships array. * * @since 3.16.12 * * @return array */ private function get_relationships() { return apply_filters( 'llms_get_post_relationships', $this->relationships ); } /** * Retrieve an array of post ids related to the deleted post by post type and meta key. * * @since 3.16.12 * * @param int $post_id WP Post ID of the deleted post. * @param string $post_type WP Post type of the related post(s). * @param string $meta_key meta_key to check for relations by. * @return array */ private function get_related_posts( $post_id, $post_type, $meta_key ) { global $wpdb; return $wpdb->get_col( $wpdb->prepare( "SELECT p.ID FROM {$wpdb->posts} AS p LEFT JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id AND pm.meta_key = %s WHERE p.post_type = %s AND pm.meta_value = %d", $meta_key, $post_type, $post_id ) ); // db-call ok; no-cache ok. } /** * Check relationships and delete / update related posts when a post is deleted. * * Called on `delete_post` hook (before a post is deleted). * * @since 3.16.12 * @since 3.24.0 Unknown. * * @param int $post_id WP Post ID of the deleted post. * @return void */ public function maybe_update_relationships( $post_id ) { $post = get_post( $post_id ); if ( ! in_array( $post->post_type, $this->get_post_types(), true ) ) { return; } foreach ( $this->get_relationships() as $post_type => $relationships ) { if ( $post->post_type !== $post_type ) { continue; } foreach ( $relationships as $data ) { if ( in_array( $data['action'], array( 'delete', 'trash' ), true ) ) { $this->delete_relationships( $post, $data ); } elseif ( 'unset' === $data['action'] ) { $this->unset_relationships( $post, $data ); } } } } /** * Unsets relationship data from post_meta when a post is deleted. * * @since 3.16.12 * @since 3.24.0 Unknown. * * @param WP_Post $post WP Post that's been deleted. * @param array $data Relationship data array. * @return void */ private function unset_relationships( $post, $data ) { $relationships = $this->get_related_posts( $post->ID, $data['post_type'], $data['meta_key'] ); foreach ( $relationships as $id ) { delete_post_meta( $id, $data['meta_key'], $post->ID ); if ( isset( $data['meta_keys_additional'] ) ) { foreach ( $data['meta_keys_additional'] as $key ) { delete_post_meta( $id, $key ); } } } } }
Expand full source code Collapse full source code View on GitHub
Methods Methods
- __construct — Constructor.
- delete_product_with_active_subscriptions_error_message — Returns the error message to display when deleting a product with active subscriptions.
- delete_relationships — Delete / Trash posts related to the deleted post.
- delete_table_records — Delete records from a table that are related to the deleted post.
- delete_wp_posts — Delete or trash WP Posts related to the deleted post.
- get_post_types — Get a list of post types with relationships that should be checked.
- get_related_posts — Retrieve an array of post ids related to the deleted post by post type and meta key.
- get_relationships — Retrieve filtered LifterLMS post relationships array.
- maybe_clean_earned_engagments_related_user_post_meta — Maybe delete LifterLMS user post meta related to earned engagements.
- maybe_prevent_product_deletion — Determine whether a product deletion should take place.
- maybe_update_relationships — Check relationships and delete / update related posts when a post is deleted.
- rest_filter_products_with_active_subscriptions_error_message — Filter the error message returned when trying to delete a product with active subscription via REST API.
- unset_relationships — Unsets relationship data from post_meta when a post is deleted.
Changelog Changelog
Version | Description |
---|---|
4.15.0 | Delete access plans related to courses/memberships on their deletion. |
3.37.8 | Delete student quiz attempts when a quiz is deleted. |
3.24.0 | Unknown. |
3.16.12 | Introduced. |