Skip to content

Commit

Permalink
cleanup relationships when deleting questions and lessons, per #263
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasplevy committed Feb 27, 2018
1 parent 2e8dc91 commit 3945837
Show file tree
Hide file tree
Showing 3 changed files with 301 additions and 1 deletion.
188 changes: 188 additions & 0 deletions includes/class.llms.post.relationships.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }

/**
* Hooks and actions related to post relationships
* @since [version]
* @version [version]
*/
class LLMS_Post_Relationships {

/**
* Configure relationships
* @var array
*/
private $relationships = array(

'lesson' => array(
array(
'action' => 'unset',
'meta_key' => '_llms_prerequisite',
'meta_keys_additional' => array( '_llms_has_prerequisite' ),
'post_type' => 'lesson',
),
array(
'action' => 'unset',
'meta_key' => '_llms_lesson_id',
'post_type' => 'llms_quiz',
),
),

'llms_quiz' => array(
array(
'action' => 'delete', // delete = force delete; trash = move to trash
'meta_key' => '_llms_parent_id',
'post_type' => 'llms_question',
),
array(
'action' => 'unset',
'meta_key' => '_llms_quiz',
'meta_keys_additional' => array( '_llms_quiz_enabled' ),
'post_type' => 'lesson',
),
),

);

public function __construct() {

add_action( 'delete_post', array( $this, 'maybe_update_relationships' ) );

}

/**
* Delete / Trash posts related to the deleted post
* @param obj $post WP Post that's been deleted
* @param array $data relationship data array
* @return void
* @since [version]
* @version [version]
*/
private function delete_relationships( $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
* @return array
* @since [version]
* @version [version]
*/
private function get_post_types() {
return array_keys( $this->get_relationships() );
}

/**
* Retrieve filtered LifterLMS post relatinoships array
* @return array
* @since [version]
* @version [version]
*/
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
* @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
* @since [version]
* @version [version]
*/
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
) );


}

/**
* Check relationships and delete / update related posts when a post is deleted
* Called on `delete_post` hook (before a post is deleted)
* @param int $post_id WP Post ID of the deleted post
* @return void
* @since [version]
* @version [version]
*/
public function maybe_update_relationships( $post_id ) {

$post = get_post( $post_id );
if ( ! in_array( $post->post_type, $this->get_post_types() ) ) {
return;
}

foreach ( $this->get_relationships() as $post_type => $relationships ) {

foreach ( $relationships as $data ) {

if ( in_array( $data['action'], array( 'delete', 'trash' ) ) ) {

$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
* @param obj $post WP Post that's been deleted
* @param array $data relationship data array
* @return void
* @since [version]
* @version [version]
*/
private function unset_relationships( $post, $data ) {

$relationships = $this->get_related_posts( $post->ID, $data['post_type'], $data['meta_key'] );

global $wpdb;

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 );
}
}

}

}

}

return new LLMS_Post_Relationships();
3 changes: 2 additions & 1 deletion lifterlms.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ private function define_constants() {
/**
* Include required core classes
* @since 1.0.0
* @version 3.16.7
* @version [version]
*/
private function includes() {

Expand Down Expand Up @@ -282,6 +282,7 @@ private function includes() {
include_once( 'includes/class.llms.lesson.handler.php' );
include_once( 'includes/class.llms.course.factory.php' );
include_once( 'includes/class.llms.question.types.php' );
include_once( 'includes/class.llms.post.relationships.php' );
include_once( 'includes/class.llms.review.php' );
include_once( 'includes/class.llms.student.dashboard.php' );
include_once( 'includes/class.llms.user.permissions.php' );
Expand Down
111 changes: 111 additions & 0 deletions tests/unit-tests/class.llms.test.post.relationships.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php
/**
* Tests for LLMS_Post_Instructors model & functions
* @group post_relationships
* @since [version]
* @version [version]
*/
class LLMS_Test_Post_Relationships extends LLMS_UnitTestCase {

/**
* When deleting lessons
* A) Any lesson which has this lesson as a prereq should have that prereq removed
* And the has_prereq metavalue should be unset returning "no"
* B) Any quiz attached to this lesson should be detached (making it an orphan)
* @return [type]
* @since [version]
* @version [version]
*/
private function delete_lesson() {

$courses = $this->generate_mock_courses( 1, 1, 4, 3, 1 );
$lessons = llms_get_post( $courses[0] )->get_lessons();

// add prereqs to all the lessons except the first
foreach ( $lessons as $i => $lesson ) {

if ( 0 === $i ) {
continue;
}

$prev = $lessons[ $i - 1 ];

$lesson->set( 'has_prerequisite', 'yes' );
$lesson->set( 'prerequisite', $prev->get( 'id' ) );

}

// delete posts and run tests
foreach ( $lessons as $i => $lesson ) {

$quiz = $lesson->get_quiz();

wp_delete_post( $lesson->get( 'id' ) );

// quizzes attached to the lesson should now be orphaned
if ( $quiz ) {
$this->assertTrue( $quiz->is_orphan() );
}

if ( $i === count( $lessons ) - 1 ) {
continue;
}
$next = $lessons[ $i + 1 ];

// prereqs should be removed
$this->assertEquals( 'no', $next->get( 'has_prerequisite' ) );
$this->assertEquals( 0, $next->get( 'prerequisite' ) );
$this->assertFalse( $next->has_prerequisite() );

}

}

/**
* When a quiz is deleted, all the child questions should be deleted too
* Lesson should switch quiz_enabled to "no"
* @return void
* @since [version]
* @version [version]
*/
private function delete_quiz() {

$courses = $this->generate_mock_courses( 1, 1, 1, 1, 20 );
$lesson = llms_get_post( llms_get_post( $courses[0] )->get_lessons( 'ids' )[0] );
$quiz = $lesson->get_quiz();

$questions = $quiz->get_questions( 'ids' );

wp_delete_post( $quiz->get( 'id' ), true );

foreach ( $questions as $question_id ) {

$this->assertNull( get_post( $question_id ) );

}

$this->assertFalse( $lesson->is_quiz_enabled() );

}

/**
* Test all relationships based on post types
* @return void
* @since [version]
* @version [version]
*/
public function test_maybe_update_relationships() {

$funcs = array(
'delete_quiz',
'delete_lesson',
);
foreach ( $funcs as $func ) {

call_user_func( array( $this, $func ) );

}

}

}

0 comments on commit 3945837

Please sign in to comment.