Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a progress table eraser tool #7230

Merged
merged 7 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions includes/class-sensei.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use Sensei\Internal\Student_Progress\Services\Lesson_Deleted_Handler;
use Sensei\Internal\Student_Progress\Services\Quiz_Deleted_Handler;
use Sensei\Internal\Student_Progress\Services\User_Deleted_Handler;
use Sensei\Internal\Tools\Progress_Tables_Eraser;

if ( ! defined( 'ABSPATH' ) ) {
exit;
Expand Down Expand Up @@ -369,8 +370,8 @@
* @param {string} $template_url The template url.
* @return {string} Filtered template url.
*/
$this->template_url = apply_filters( 'sensei_template_url', 'sensei/' );
$this->version = isset( $args['version'] ) ? $args['version'] : null;

Check warning on line 374 in includes/class-sensei.php

View check run for this annotation

Codecov / codecov/patch

includes/class-sensei.php#L373-L374

Added lines #L373 - L374 were not covered by tests

// Only set the install version if it is included in alloptions. This prevents a query on every page load.
$alloptions = wp_load_alloptions();
Expand Down Expand Up @@ -659,6 +660,11 @@
( new Migration_Tool( \Sensei_Tools::instance(), $this->migration_scheduler ) )->init();
}

// Progress tables eraser.
if ( ! $tables_enabled ) {
( new Progress_Tables_Eraser() )->init();

Check warning on line 665 in includes/class-sensei.php

View check run for this annotation

Codecov / codecov/patch

includes/class-sensei.php#L664-L665

Added lines #L664 - L665 were not covered by tests
}

// Quiz submission repositories.
$this->quiz_submission_repository = ( new Submission_Repository_Factory( $tables_enabled ) )->create();
$this->quiz_answer_repository = ( new Answer_Repository_Factory( $tables_enabled ) )->create();
Expand Down
189 changes: 189 additions & 0 deletions includes/internal/tools/class-progress-tables-eraser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<?php
/**
* File containing the class Progress_Tables_Eraser.
*
* @package sensie
*/

namespace Sensei\Internal\Tools;

use Sensei\Internal\Installer\Schema;
use Sensei_Tool_Interactive_Interface;
use Sensei_Tool_Interface;
use Sensei_Tools;

if ( ! defined( 'ABSPATH' ) ) {
exit;

Check warning on line 16 in includes/internal/tools/class-progress-tables-eraser.php

View check run for this annotation

Codecov / codecov/patch

includes/internal/tools/class-progress-tables-eraser.php#L15-L16

Added lines #L15 - L16 were not covered by tests
}

/**
* Class Progress_Tables_Eraser.
*
* @internal
*
* @since $$next-version$$
*/
class Progress_Tables_Eraser implements Sensei_Tool_Interface, Sensei_Tool_Interactive_Interface {
/**
* Nonce action.
*
* @var string
*/
const NONCE_ACTION = 'sensei-tools-progress-tables-eraser';

/**
* Initialize the tool.
*/
public function init(): void {
add_filter( 'sensei_tools', [ $this, 'register_tool' ] );

Check warning on line 38 in includes/internal/tools/class-progress-tables-eraser.php

View check run for this annotation

Codecov / codecov/patch

includes/internal/tools/class-progress-tables-eraser.php#L37-L38

Added lines #L37 - L38 were not covered by tests
}

/**
* Register the tool.
*
* @param array $tools List of tools.
*
* @return array
*/
public function register_tool( $tools ) {
$tools[] = $this;
return $tools;
}

/**
* Get the ID of the tool.
*
* @return string
*/
public function get_id() {
return 'progress-tables-eraser';
}

/**
* Get the name of the tool.
*
* @return string
*/
public function get_name() {
return __( 'Delete student progress tables', 'sensei-lms' );
}

/**
* Get the description of the tool.
*
* @return string
*/
public function get_description() {
return __( 'Delete student progress and quiz submission tables. This will delete those tables, but won\'t affect comment-based data.', 'sensei-lms' );
}

/**
* Run the tool.
*/
public function process() {
if ( empty( $_POST['delete-tables'] ) ) {
return;

Check warning on line 85 in includes/internal/tools/class-progress-tables-eraser.php

View check run for this annotation

Codecov / codecov/patch

includes/internal/tools/class-progress-tables-eraser.php#L85

Added line #L85 was not covered by tests
}

$wpnonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';
if ( empty( $wpnonce ) || ! wp_verify_nonce( wp_unslash( $wpnonce ), self::NONCE_ACTION ) ) {
Sensei_Tools::instance()->trigger_invalid_request( $this );
return;

Check warning on line 91 in includes/internal/tools/class-progress-tables-eraser.php

View check run for this annotation

Codecov / codecov/patch

includes/internal/tools/class-progress-tables-eraser.php#L90-L91

Added lines #L90 - L91 were not covered by tests
}

if ( empty( $_POST['confirm'] ) ) {
Sensei_Tools::instance()->add_user_message( __( 'You must confirm the action before it can be performed.', 'sensei-lms' ), true );
wp_safe_redirect( $this->get_tool_url() );
exit;

Check warning on line 97 in includes/internal/tools/class-progress-tables-eraser.php

View check run for this annotation

Codecov / codecov/patch

includes/internal/tools/class-progress-tables-eraser.php#L97

Added line #L97 was not covered by tests
}

global $wpdb;

$results = array();
foreach ( $this->get_tables() as $table ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) === $table ) {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange
$wpdb->query( "DROP TABLE $table" );
$results[] = $table;
}
}

if ( count( $results ) > 0 ) {
$message = sprintf(
/* translators: %s: list of tables. */
__( 'The following tables have been deleted: %s', 'sensei-lms' ),
implode( ', ', $results )
);

/**
* Fires after progress tables are deleted.
*
* @since $$next-version$$
*
* @param array $tables List of deleted tables.
*/
do_action( 'sensei_tools_progress_tables_deleted', $results );
} else {
$message = __( 'No tables were deleted.', 'sensei-lms' );

Check warning on line 128 in includes/internal/tools/class-progress-tables-eraser.php

View check run for this annotation

Codecov / codecov/patch

includes/internal/tools/class-progress-tables-eraser.php#L128

Added line #L128 was not covered by tests
}

Sensei_Tools::instance()->add_user_message( $message );

// Redirect to the tools page to avoid confusion: the tool is no longer available.
wp_safe_redirect( Sensei_Tools::instance()->get_tools_url() );
exit;

Check warning on line 135 in includes/internal/tools/class-progress-tables-eraser.php

View check run for this annotation

Codecov / codecov/patch

includes/internal/tools/class-progress-tables-eraser.php#L135

Added line #L135 was not covered by tests
}

/**
* Is the tool currently available?
*
* @return bool True if tool is available.
*/
public function is_available() {
global $wpdb;

foreach ( $this->get_tables() as $table ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) === $table ) {
return true;
}
}

return false;

Check warning on line 153 in includes/internal/tools/class-progress-tables-eraser.php

View check run for this annotation

Codecov / codecov/patch

includes/internal/tools/class-progress-tables-eraser.php#L153

Added line #L153 was not covered by tests
}

/**
* Output tool view for interactive action methods.
*/
public function output() {

Check warning on line 159 in includes/internal/tools/class-progress-tables-eraser.php

View check run for this annotation

Codecov / codecov/patch

includes/internal/tools/class-progress-tables-eraser.php#L159

Added line #L159 was not covered by tests
// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Variable used in view.
$tool_id = $this->get_id();
include __DIR__ . '/views/html-progress-tables-eraser-form.php';

Check warning on line 162 in includes/internal/tools/class-progress-tables-eraser.php

View check run for this annotation

Codecov / codecov/patch

includes/internal/tools/class-progress-tables-eraser.php#L161-L162

Added lines #L161 - L162 were not covered by tests
}

/**
* Get the URL for this tool.
*
* @return string
*/
private function get_tool_url(): string {
return admin_url( 'admin.php?page=sensei-tools&tool=' . $this->get_id() );
}

/**
* Get the tables to delete.
*
* @return array
*/
private function get_tables(): array {
global $wpdb;

return array(
"{$wpdb->prefix}sensei_lms_progress",
"{$wpdb->prefix}sensei_lms_quiz_submissions",
"{$wpdb->prefix}sensei_lms_quiz_answers",
"{$wpdb->prefix}sensei_lms_quiz_grades",
);
}
}
44 changes: 44 additions & 0 deletions includes/internal/tools/views/html-progress-tables-eraser-form.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
/**
* File containing the form for Progress_Tables_Eraser.
*
* @package sensei
*
* @var string $tool_id Tool ID for this tool.
*/

// phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable

if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}

?>
<form method="post" action="">
<?php wp_nonce_field( \Sensei\Internal\Tools\Progress_Tables_Eraser::NONCE_ACTION, '_wpnonce', false ); ?>
<input type="hidden" name="delete-tables" value="yes">
<input type="hidden" name="page" value="sensei-tools">
<input type="hidden" name="tool" value="<?php echo esc_attr( $tool_id ); ?>">
<div>
<?php esc_html_e( 'This tool will delete all progress tables from the database. This action cannot be undone.', 'sensei-lms' ); ?>
</div>
<p class="confirm">
<input
type="checkbox"
name="confirm"
value="yes"
id="sensei-tools-progress-tables-eraser-confirm"
/>
<label for="sensei-tools-progress-tables-eraser-confirm">
<?php esc_html_e( 'I understand this action cannot be undone.', 'sensei-lms' ); ?>
</label>
<p class="submit">
<input
type="submit"
class="button button-primary"
name="submit"
value="<?php esc_attr_e( 'Delete Progress Tables', 'sensei-lms' ); ?>"
confirm="<?php esc_attr_e( 'Are you sure you want to delete all progress tables?', 'sensei-lms' ); ?>"
/>
</p>
</form>
119 changes: 119 additions & 0 deletions tests/unit-tests/internal/tools/test-class-progress-tables-eraser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php
namespace SenseiTest\Internal\Tools;

use Sensei\Internal\Installer\Schema;
use Sensei\Internal\Tools\Progress_Tables_Eraser;

/**
* Class Progress_Tables_Eraser_Test
*
* @covers \Sensei\Internal\Tools\Progress_Tables_Eraser
*/
class Progress_Tables_Eraser_Test extends \WP_UnitTestCase {

/**
* Eraser instance.
*
* @var Progress_Tables_Eraser
*/
private $eraser;

public function filterWpRedirect( $location, $status ) {
throw new \Exception( $location, $status );
}

protected function setUp(): void {
parent::setUp();
$this->eraser = new Progress_Tables_Eraser();
add_filter( 'wp_redirect', [ $this, 'filterWpRedirect' ], 10, 2 );
}

protected function tearDown(): void {
parent::tearDown();
remove_filter( 'wp_redirect', [ $this, 'filterWpRedirect' ], 10 );
}

public function testRegisterTool_Always_AddsItselfToTools(): void {
/* Act. */
$tools = $this->eraser->register_tool( array() );

/* Assert. */
self::assertSame( $this->eraser, $tools[0] );
}

public function testGetId_Always_ReturnsMatchingString(): void {
/* Act. */
$id = $this->eraser->get_id();

/* Assert. */
self::assertSame( 'progress-tables-eraser', $id );
}

public function testGetName_Always_ReturnsMatchingString(): void {
/* Act. */
$name = $this->eraser->get_name();

/* Assert. */
self::assertSame( 'Delete student progress tables', $name );
}

public function testGetDescription_Always_ReturnsMatchingString(): void {

/* Act. */
$description = $this->eraser->get_description();

/* Assert. */
self::assertSame( 'Delete student progress and quiz submission tables. This will delete those tables, but won\'t affect comment-based data.', $description );
}

public function testProcess_ConfirmationProvided_DeletesTables(): void {
/* Arrange. */
$this->create_tables();

$_POST['_wpnonce'] = wp_create_nonce( Progress_Tables_Eraser::NONCE_ACTION );
$_POST['confirm'] = 'yes';
$_POST['delete-tables'] = 'yes';

/* Act. */
$this->expectException( \Exception::class );
$this->eraser->process();

/* Assert. */
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$table_exists = $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->prefix}sensei_lms_progress'" );
self::assertFalse( $table_exists );
}


public function testProcess_NoConfirmationProvided_DeletesTables(): void {
/* Arrange. */
$this->create_tables();

$_POST['_wpnonce'] = wp_create_nonce( Progress_Tables_Eraser::NONCE_ACTION );
$_POST['delete-tables'] = 'yes';

/* Act. */
$this->expectException( \Exception::class );
$this->eraser->process();

/* Assert. */
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$table_exists = $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->prefix}sensei_lms_progress'" );
self::assertTrue( $table_exists );
}

public function testIsAvailable_Always_ReturnsTrue(): void {
/* Act. */
$result = $this->eraser->is_available();

/* Assert. */
self::assertTrue( $result );
}

private function create_tables(): void {
$schema = new Schema();
$schema->create_tables();
}
}
Loading