From d0088f94c0d444b908b0148298dbbd1b9d3aa241 Mon Sep 17 00:00:00 2001 From: erseco Date: Sat, 26 Sep 2026 08:12:59 +0100 Subject: [PATCH 1/2] Remove unreachable scaffolding and clean options on uninstall Delete code that no request can reach or that does nothing: - ExeLearning_Admin_Upload: an admin_post handler for a form that is never rendered, so its nonce is never issued. - ExeLearning_Filters: wrote the literal 'elp file metadata' into every .elpx attachment's metadata (and REST media_details); nothing reads it, and on a REST-created attachment with no metadata it triggered PHP 8.1+'s false to array deprecation. - ExeLearning_Hooks, ExeLearning_Post_Types and ExeLearning::run() / load_dependencies(): empty boilerplate, two of them still hooked on init. - ExeLearning_Elp_List_Table: a list table with hard-coded sample rows that is never instantiated. - REST_API::cleanup_old_extraction() and its private recursive delete, only reached through reflection in tests. uninstall.php now deletes the plugin's five options, on every site of a network. Uploaded packages, their extractions and uploaded styles are user content and stay. --- admin/class-admin-upload.php | 91 ----------- exelearning.php | 8 +- includes/class-elp-list-table.php | 132 --------------- includes/class-exelearning-rest-api.php | 41 ----- includes/class-exelearning.php | 24 --- includes/class-filters.php | 67 -------- includes/class-hooks.php | 36 ----- includes/class-post-types.php | 40 ----- languages/exelearning-ca.po | 25 +-- languages/exelearning-ca_valencia.po | 25 +-- languages/exelearning-de_DE.po | 25 +-- languages/exelearning-eo.po | 25 +-- languages/exelearning-es_ES.po | 25 +-- languages/exelearning-eu.po | 25 +-- languages/exelearning-gl_ES.po | 25 +-- languages/exelearning-it_IT.po | 25 +-- languages/exelearning-pt_PT.po | 25 +-- languages/exelearning-ro_RO.po | 25 +-- languages/exelearning.pot | 25 +-- phpunit.xml.dist | 1 - tests/unit/AdminUploadTest.php | 56 ------- tests/unit/ElpListTableTest.php | 139 ---------------- tests/unit/ExeLearningTest.php | 22 --- tests/unit/FiltersTest.php | 204 ------------------------ tests/unit/HooksTest.php | 74 --------- tests/unit/PostTypesTest.php | 55 ------- tests/unit/RestApiTest.php | 143 ----------------- tests/unit/UninstallTest.php | 41 +++++ uninstall.php | 43 +++-- 29 files changed, 103 insertions(+), 1389 deletions(-) delete mode 100644 admin/class-admin-upload.php delete mode 100644 includes/class-elp-list-table.php delete mode 100644 includes/class-filters.php delete mode 100644 includes/class-hooks.php delete mode 100644 includes/class-post-types.php delete mode 100644 tests/unit/AdminUploadTest.php delete mode 100644 tests/unit/ElpListTableTest.php delete mode 100644 tests/unit/FiltersTest.php delete mode 100644 tests/unit/HooksTest.php delete mode 100644 tests/unit/PostTypesTest.php create mode 100644 tests/unit/UninstallTest.php diff --git a/admin/class-admin-upload.php b/admin/class-admin-upload.php deleted file mode 100644 index d08ce4b..0000000 --- a/admin/class-admin-upload.php +++ /dev/null @@ -1,91 +0,0 @@ - false ); - $uploaded_file = wp_handle_upload( $file, $upload_overrides ); - - if ( isset( $uploaded_file['error'] ) ) { - wp_safe_redirect( add_query_arg( 'message', 'upload_error', wp_get_referer() ) ); - exit; - } - - // Create an attachment for the uploaded file. - $attachment = array( - 'post_mime_type' => $uploaded_file['type'], - 'post_title' => sanitize_file_name( $file['name'] ), - 'post_content' => '', - 'post_status' => 'inherit', - ); - - $attachment_id = wp_insert_attachment( $attachment, $uploaded_file['file'] ); - - // Generate and update attachment metadata. - $attach_data = wp_generate_attachment_metadata( $attachment_id, $uploaded_file['file'] ); - wp_update_attachment_metadata( $attachment_id, $attach_data ); - - wp_safe_redirect( add_query_arg( 'message', 'upload_success', wp_get_referer() ) ); - exit; - } -} diff --git a/exelearning.php b/exelearning.php index df721cf..27ff2a1 100644 --- a/exelearning.php +++ b/exelearning.php @@ -39,12 +39,8 @@ require_once EXELEARNING_PLUGIN_DIR . 'includes/class-activator.php'; require_once EXELEARNING_PLUGIN_DIR . 'includes/class-deactivator.php'; require_once EXELEARNING_PLUGIN_DIR . 'includes/class-exelearning.php'; -require_once EXELEARNING_PLUGIN_DIR . 'includes/class-hooks.php'; require_once EXELEARNING_PLUGIN_DIR . 'includes/class-i18n.php'; -require_once EXELEARNING_PLUGIN_DIR . 'includes/class-filters.php'; -require_once EXELEARNING_PLUGIN_DIR . 'includes/class-post-types.php'; require_once EXELEARNING_PLUGIN_DIR . 'includes/class-mime-types.php'; -require_once EXELEARNING_PLUGIN_DIR . 'includes/class-elp-list-table.php'; // Load the eXeLearning file upload handler. require_once EXELEARNING_PLUGIN_DIR . 'includes/class-elp-upload-handler.php'; @@ -63,7 +59,6 @@ // Admin classes. require_once EXELEARNING_PLUGIN_DIR . 'admin/class-admin-settings.php'; require_once EXELEARNING_PLUGIN_DIR . 'admin/class-admin-styles.php'; -require_once EXELEARNING_PLUGIN_DIR . 'admin/class-admin-upload.php'; // Public classes. require_once EXELEARNING_PLUGIN_DIR . 'public/class-shortcodes.php'; @@ -103,8 +98,7 @@ function exelearning_run() { $viewer_enhancements = new ExeLearning_Viewer_Enhancements(); $viewer_enhancements->register_hooks(); - $plugin = new ExeLearning(); - $plugin->run(); + new ExeLearning(); } exelearning_run(); diff --git a/includes/class-elp-list-table.php b/includes/class-elp-list-table.php deleted file mode 100644 index ccf05f9..0000000 --- a/includes/class-elp-list-table.php +++ /dev/null @@ -1,132 +0,0 @@ - 'exelearning_file', - 'plural' => 'exelearning_files', - 'ajax' => false, - ) - ); - } - - /** - * Returns the list table columns. - * - * @return array List of columns. - */ - public function get_columns() { - $columns = array( - 'cb' => '', - 'title' => __( 'Title', 'exelearning' ), - 'date' => __( 'Date', 'exelearning' ), - 'status' => __( 'Status', 'exelearning' ), - ); - return $columns; - } - - /** - * Prepares the list of items. - */ - public function prepare_items() { - // Static data for demonstration. Replace with data from the database. - $data = array( - array( - 'ID' => 1, - 'title' => 'Example File 1', - 'date' => '2025-01-01', - 'status' => 'Active', - ), - array( - 'ID' => 2, - 'title' => 'Example File 2', - 'date' => '2025-01-02', - 'status' => 'Blocked', - ), - ); - - $columns = $this->get_columns(); - $hidden = array(); - $sortable = array(); - - $this->_column_headers = array( $columns, $hidden, $sortable ); - - $this->items = $data; - } - - /** - * Renders the checkbox column. - * - * @param array $item Current item. - * @return string HTML for the checkbox. - */ - protected function column_cb( $item ) { - return sprintf( - '', - esc_attr( $item['ID'] ) - ); - } - - /** - * Renders the title column. - * - * @param array $item Current item. - * @return string HTML for the title with row actions. - */ - protected function column_title( $item ) { - $edit_url = '#'; // Define edit URL as required. - - $actions = array( - 'edit' => sprintf( '%s', esc_url( $edit_url ), __( 'Edit', 'exelearning' ) ), - 'delete' => sprintf( '%s', esc_url( '#' ), __( 'Delete', 'exelearning' ) ), - ); - - return sprintf( - '%1$s %2$s', - esc_html( $item['title'] ), - $this->row_actions( $actions ) - ); - } - - /** - * Default column rendering. - * - * @param array $item Current item. - * @param string $column_name Column name. - * @return string Column content. - */ - protected function column_default( $item, $column_name ) { - if ( isset( $item[ $column_name ] ) ) { - return esc_html( $item[ $column_name ] ); - } - return ''; - } -} diff --git a/includes/class-exelearning-rest-api.php b/includes/class-exelearning-rest-api.php index 8c7eaba..567253e 100644 --- a/includes/class-exelearning-rest-api.php +++ b/includes/class-exelearning-rest-api.php @@ -626,25 +626,6 @@ private function validate_elp_file_path( $attachment_id ) { return $file_path; } - /** - * Clean up old extracted folder for an attachment. - * - * @param int $attachment_id Attachment ID. - */ - private function cleanup_old_extraction( $attachment_id ) { - $old_extracted = get_post_meta( $attachment_id, '_exelearning_extracted', true ); - if ( ! $old_extracted ) { - return; - } - - $upload_dir = wp_upload_dir(); - $old_folder = trailingslashit( $upload_dir['basedir'] ) . 'exelearning/' . $old_extracted . '/'; - - if ( is_dir( $old_folder ) ) { - $this->recursive_delete( $old_folder ); - } - } - /** * Clean up extraction directory by hash. * @@ -785,26 +766,4 @@ private function extract_elp_to_new_dir( $file_path ) { private function apply_elp_metadata( $attachment_id, $elp_service, $hash, $has_preview ) { $this->reprocessor->apply_metadata( $attachment_id, $elp_service, $hash, $has_preview ); } - - /** - * Recursively delete a directory. - * - * @param string $dir Directory path. - */ - private function recursive_delete( $dir ) { - if ( ! file_exists( $dir ) ) { - return; - } - - if ( is_file( $dir ) || is_link( $dir ) ) { - wp_delete_file( $dir ); - } else { - $files = array_diff( scandir( $dir ), array( '.', '..' ) ); - foreach ( $files as $file ) { - $this->recursive_delete( $dir . DIRECTORY_SEPARATOR . $file ); - } - // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- Direct filesystem access needed for cleanup. - rmdir( $dir ); - } - } } diff --git a/includes/class-exelearning.php b/includes/class-exelearning.php index 9dae0a4..905a3c6 100644 --- a/includes/class-exelearning.php +++ b/includes/class-exelearning.php @@ -41,35 +41,21 @@ class ExeLearning { */ public function __construct() { $this->version = EXELEARNING_VERSION; - $this->load_dependencies(); $this->init_components(); $this->setup_hooks(); $this->load_i18n(); } - /** - * Loads required dependencies. - * - * Additional dependencies can be loaded here if needed. - */ - private function load_dependencies() { - // All required files are loaded via require_once in the main plugin file. - } - /** * Initializes all plugin components. */ private function init_components() { $this->components = array( - 'hooks' => new ExeLearning_Hooks(), - 'filters' => new ExeLearning_Filters(), - 'post_types' => new ExeLearning_Post_Types(), 'mime_types' => new ExeLearning_Mime_Types(), 'shortcodes' => new ExeLearning_Shortcodes(), 'media_library' => new ExeLearning_Media_Library(), 'i18n' => new ExeLearning_I18n(), 'admin_settings' => new ExeLearning_Admin_Settings(), - 'admin_upload' => new ExeLearning_Admin_Upload(), 'elp_upload_handler' => new ExeLearning_Elp_Upload_Handler(), 'elp_upload_block' => new ExeLearning_Elp_Upload_Block(), 'editor' => new ExeLearning_Editor(), @@ -89,10 +75,7 @@ private function setup_hooks() { // Early so migrated options are in place before settings registration // (priority 10) and any admin page render reads them. add_action( 'admin_init', array( 'ExeLearning_Upgrader', 'maybe_upgrade' ), 5 ); - add_action( 'init', array( $this->components['hooks'], 'register_hooks' ) ); - add_action( 'init', array( $this->components['post_types'], 'register_post_types' ) ); add_action( 'init', array( $this->components['shortcodes'], 'register_shortcodes' ) ); - $this->components['filters']->register_filters(); $this->components['mime_types']->register_mime_types(); $this->components['elp_upload_handler']->register(); } @@ -106,11 +89,4 @@ private function setup_hooks() { private function load_i18n() { add_action( 'init', array( $this->components['i18n'], 'load_textdomain' ) ); } - - /** - * Runs the plugin. - */ - public function run() { - // Additional execution code can be added here. - } } diff --git a/includes/class-filters.php b/includes/class-filters.php deleted file mode 100644 index 3cbb293..0000000 --- a/includes/class-filters.php +++ /dev/null @@ -1,67 +0,0 @@ -is_elp_file( $file ) ) { - // Add custom metadata for .elp files. - $metadata['exelearning'] = 'elp file metadata'; - } - - return $metadata; - } - - /** - * Checks if a file is an .elp file. - * - * @param string $file File path. - * - * @return bool True if the file is an .elp file, false otherwise. - */ - private function is_elp_file( $file ) { - if ( ! $file || ! is_string( $file ) ) { - return false; - } - $ext = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) ); - return 'elpx' === $ext; - } -} diff --git a/includes/class-hooks.php b/includes/class-hooks.php deleted file mode 100644 index d2d10ad..0000000 --- a/includes/class-hooks.php +++ /dev/null @@ -1,36 +0,0 @@ - admin/vendor - admin/class-admin-upload.php includes/class-cli-command.php diff --git a/tests/unit/AdminUploadTest.php b/tests/unit/AdminUploadTest.php deleted file mode 100644 index ad0860f..0000000 --- a/tests/unit/AdminUploadTest.php +++ /dev/null @@ -1,56 +0,0 @@ -upload = new ExeLearning_Admin_Upload(); - } - - /** - * Test constructor registers action. - */ - public function test_constructor_registers_action() { - $upload = new ExeLearning_Admin_Upload(); - - $this->assertGreaterThan( - 0, - has_action( 'admin_post_exelearning_upload', array( $upload, 'handle_upload' ) ) - ); - } - - /** - * Test handle_upload method exists. - */ - public function test_handle_upload_exists() { - $this->assertTrue( method_exists( $this->upload, 'handle_upload' ) ); - } - - /** - * Test handle_upload is public. - */ - public function test_handle_upload_is_public() { - $method = new ReflectionMethod( ExeLearning_Admin_Upload::class, 'handle_upload' ); - $this->assertTrue( $method->isPublic() ); - } -} diff --git a/tests/unit/ElpListTableTest.php b/tests/unit/ElpListTableTest.php deleted file mode 100644 index ce6371f..0000000 --- a/tests/unit/ElpListTableTest.php +++ /dev/null @@ -1,139 +0,0 @@ -list_table = new ExeLearning_Elp_List_Table(); - } - - /** - * Test get_columns returns expected columns. - */ - public function test_get_columns() { - $columns = $this->list_table->get_columns(); - - $this->assertIsArray( $columns ); - $this->assertArrayHasKey( 'cb', $columns ); - $this->assertArrayHasKey( 'title', $columns ); - $this->assertArrayHasKey( 'date', $columns ); - $this->assertArrayHasKey( 'status', $columns ); - } - - /** - * Test prepare_items sets items. - */ - public function test_prepare_items() { - $this->list_table->prepare_items(); - - $this->assertNotEmpty( $this->list_table->items ); - $this->assertCount( 2, $this->list_table->items ); - } - - /** - * Test prepare_items sets column headers. - */ - public function test_prepare_items_sets_headers() { - $this->list_table->prepare_items(); - - $headers = $this->list_table->get_column_info(); - - $this->assertIsArray( $headers ); - $this->assertCount( 4, $headers ); - } - - /** - * Test column_cb renders checkbox. - */ - public function test_column_cb() { - $method = new ReflectionMethod( ExeLearning_Elp_List_Table::class, 'column_cb' ); - $method->setAccessible( true ); - - $item = array( 'ID' => 123 ); - $result = $method->invoke( $this->list_table, $item ); - - $this->assertStringContainsString( 'assertStringContainsString( 'type="checkbox"', $result ); - $this->assertStringContainsString( 'value="123"', $result ); - } - - /** - * Test column_title renders title with actions. - */ - public function test_column_title() { - $method = new ReflectionMethod( ExeLearning_Elp_List_Table::class, 'column_title' ); - $method->setAccessible( true ); - - $item = array( - 'ID' => 1, - 'title' => 'Test File', - ); - $result = $method->invoke( $this->list_table, $item ); - - $this->assertStringContainsString( 'Test File', $result ); - $this->assertStringContainsString( '', $result ); - $this->assertStringContainsString( 'row-actions', $result ); - $this->assertStringContainsString( 'edit', $result ); - $this->assertStringContainsString( 'delete', $result ); - } - - /** - * Test column_default returns column value. - */ - public function test_column_default() { - $method = new ReflectionMethod( ExeLearning_Elp_List_Table::class, 'column_default' ); - $method->setAccessible( true ); - - $item = array( - 'date' => '2025-01-01', - 'status' => 'Active', - ); - $result = $method->invoke( $this->list_table, $item, 'date' ); - - $this->assertEquals( '2025-01-01', $result ); - } - - /** - * Test column_default returns empty for unknown column. - */ - public function test_column_default_unknown() { - $method = new ReflectionMethod( ExeLearning_Elp_List_Table::class, 'column_default' ); - $method->setAccessible( true ); - - $item = array( 'date' => '2025-01-01' ); - $result = $method->invoke( $this->list_table, $item, 'unknown' ); - - $this->assertEquals( '', $result ); - } - - /** - * Test class extends WP_List_Table. - */ - public function test_extends_wp_list_table() { - $this->assertInstanceOf( WP_List_Table::class, $this->list_table ); - } -} diff --git a/tests/unit/ExeLearningTest.php b/tests/unit/ExeLearningTest.php index 656bc69..ebd1921 100644 --- a/tests/unit/ExeLearningTest.php +++ b/tests/unit/ExeLearningTest.php @@ -34,13 +34,6 @@ public function test_plugin_instantiation() { $this->assertInstanceOf( ExeLearning::class, $this->plugin ); } - /** - * Test run method exists. - */ - public function test_run_method_exists() { - $this->assertTrue( method_exists( $this->plugin, 'run' ) ); - } - /** * Read an instantiated component from the plugin's component registry. * @@ -54,20 +47,6 @@ private function get_component( $key ) { return isset( $components[ $key ] ) ? $components[ $key ] : null; } - /** - * Test hooks component is initialized. - */ - public function test_hooks_initialized() { - $this->assertInstanceOf( ExeLearning_Hooks::class, $this->get_component( 'hooks' ) ); - } - - /** - * Test filters component is initialized. - */ - public function test_filters_initialized() { - $this->assertInstanceOf( ExeLearning_Filters::class, $this->get_component( 'filters' ) ); - } - /** * Test mime_types component is initialized. */ @@ -175,5 +154,4 @@ public function test_admin_styles_component_is_admin_only() { $this->assertInstanceOf( ExeLearning_Admin_Styles::class, $components['admin_styles'] ); } - } diff --git a/tests/unit/FiltersTest.php b/tests/unit/FiltersTest.php deleted file mode 100644 index 699e09b..0000000 --- a/tests/unit/FiltersTest.php +++ /dev/null @@ -1,204 +0,0 @@ -filters = new ExeLearning_Filters(); - } - - /** - * Test register_filters adds filter. - */ - public function test_register_filters_adds_filter() { - $this->filters->register_filters(); - - $this->assertGreaterThan( - 0, - has_filter( 'wp_get_attachment_metadata', array( $this->filters, 'filter_attachment_metadata' ) ) - ); - } - - /** - * Test filter_attachment_metadata returns metadata unchanged for non-elp. - */ - public function test_filter_attachment_metadata_unchanged_for_non_elp() { - $attachment_id = $this->factory->attachment->create(); - $metadata = array( 'width' => 100, 'height' => 100 ); - - $result = $this->filters->filter_attachment_metadata( $metadata, $attachment_id ); - - $this->assertEquals( $metadata, $result ); - } - - /** - * Test filter_attachment_metadata handles empty attachment_id. - */ - public function test_filter_attachment_metadata_handles_empty_id() { - $metadata = array( 'test' => 'value' ); - - $result = $this->filters->filter_attachment_metadata( $metadata, 0 ); - - $this->assertEquals( $metadata, $result ); - } - - /** - * Test filter_attachment_metadata handles null attachment_id. - */ - public function test_filter_attachment_metadata_handles_null_id() { - $metadata = array( 'test' => 'value' ); - - $result = $this->filters->filter_attachment_metadata( $metadata, null ); - - $this->assertEquals( $metadata, $result ); - } - - /** - * Test filter_attachment_metadata handles non-numeric attachment_id. - */ - public function test_filter_attachment_metadata_handles_non_numeric_id() { - $metadata = array( 'test' => 'value' ); - - $result = $this->filters->filter_attachment_metadata( $metadata, 'invalid' ); - - $this->assertEquals( $metadata, $result ); - } - - /** - * Test is_elp_file method is private. - */ - public function test_is_elp_file_is_private() { - $method = new ReflectionMethod( ExeLearning_Filters::class, 'is_elp_file' ); - $this->assertTrue( $method->isPrivate() ); - } - - /** - * Test is_elp_file returns true for elpx files. - */ - public function test_is_elp_file_returns_true_for_elpx() { - $method = new ReflectionMethod( ExeLearning_Filters::class, 'is_elp_file' ); - $method->setAccessible( true ); - - $this->assertTrue( $method->invoke( $this->filters, '/path/to/file.elpx' ) ); - } - - /** - * Test is_elp_file returns false for non-elp files. - */ - public function test_is_elp_file_returns_false_for_other() { - $method = new ReflectionMethod( ExeLearning_Filters::class, 'is_elp_file' ); - $method->setAccessible( true ); - - $this->assertFalse( $method->invoke( $this->filters, '/path/to/file.pdf' ) ); - $this->assertFalse( $method->invoke( $this->filters, '/path/to/file.zip' ) ); - } - - /** - * Test is_elp_file handles null input. - */ - public function test_is_elp_file_handles_null() { - $method = new ReflectionMethod( ExeLearning_Filters::class, 'is_elp_file' ); - $method->setAccessible( true ); - - $this->assertFalse( $method->invoke( $this->filters, null ) ); - } - - /** - * Test is_elp_file handles empty string. - */ - public function test_is_elp_file_handles_empty() { - $method = new ReflectionMethod( ExeLearning_Filters::class, 'is_elp_file' ); - $method->setAccessible( true ); - - $this->assertFalse( $method->invoke( $this->filters, '' ) ); - } - - /** - * Test filter_attachment_metadata adds exelearning key for elpx files. - */ - public function test_filter_attachment_metadata_adds_key_for_elpx() { - $attachment_id = $this->factory->attachment->create(); - - // Create a temp elpx file. - $upload_dir = wp_upload_dir(); - $file_path = $upload_dir['basedir'] . '/test-filter-' . $attachment_id . '.elpx'; - file_put_contents( $file_path, 'test content' ); - - update_attached_file( $attachment_id, $file_path ); - - $metadata = array( 'width' => 100 ); - $result = $this->filters->filter_attachment_metadata( $metadata, $attachment_id ); - - $this->assertArrayHasKey( 'exelearning', $result ); - $this->assertEquals( 'elp file metadata', $result['exelearning'] ); - - unlink( $file_path ); - } - - /** - * Test filter_attachment_metadata preserves existing metadata. - */ - public function test_filter_attachment_metadata_preserves_existing() { - $attachment_id = $this->factory->attachment->create(); - - $upload_dir = wp_upload_dir(); - $file_path = $upload_dir['basedir'] . '/test-preserve-' . $attachment_id . '.elpx'; - file_put_contents( $file_path, 'test content' ); - - update_attached_file( $attachment_id, $file_path ); - - $metadata = array( - 'width' => 100, - 'height' => 200, - ); - $result = $this->filters->filter_attachment_metadata( $metadata, $attachment_id ); - - $this->assertEquals( 100, $result['width'] ); - $this->assertEquals( 200, $result['height'] ); - $this->assertArrayHasKey( 'exelearning', $result ); - - unlink( $file_path ); - } - - /** - * Test is_elp_file returns true for elp extension. - */ - public function test_is_elp_file_returns_false_for_elp() { - $method = new ReflectionMethod( ExeLearning_Filters::class, 'is_elp_file' ); - $method->setAccessible( true ); - - // The function only returns true for elpx, not elp. - $this->assertFalse( $method->invoke( $this->filters, '/path/to/file.elp' ) ); - } - - /** - * Test is_elp_file case insensitive. - */ - public function test_is_elp_file_case_insensitive() { - $method = new ReflectionMethod( ExeLearning_Filters::class, 'is_elp_file' ); - $method->setAccessible( true ); - - $this->assertTrue( $method->invoke( $this->filters, '/path/to/FILE.ELPX' ) ); - $this->assertTrue( $method->invoke( $this->filters, '/path/to/file.Elpx' ) ); - } -} diff --git a/tests/unit/HooksTest.php b/tests/unit/HooksTest.php deleted file mode 100644 index 9fb333d..0000000 --- a/tests/unit/HooksTest.php +++ /dev/null @@ -1,74 +0,0 @@ -hooks = new ExeLearning_Hooks(); - } - - /** - * Test register_hooks method exists and is callable. - */ - public function test_register_hooks_exists() { - $this->assertTrue( method_exists( $this->hooks, 'register_hooks' ) ); - } - - /** - * Test register_hooks does not register the legacy public CPT. - * - * The boilerplate `exelearning` custom post type was removed: the plugin - * works with attachments only and must not expose a public post type. - */ - public function test_register_hooks_does_not_register_cpt() { - if ( post_type_exists( 'exelearning' ) ) { - unregister_post_type( 'exelearning' ); - } - - // register_hooks() must not register any init callback that creates the CPT. - // (We avoid firing do_action('init') here because that would re-run the - // whole init chain, including block registration, and trip WP's - // already-registered incorrect-usage notice.) - $this->hooks->register_hooks(); - - $this->assertFalse( post_type_exists( 'exelearning' ) ); - $this->assertFalse( has_action( 'init', array( $this->hooks, 'register_custom_post_type' ) ) ); - } - - /** - * Test register_hooks does not add a the_content filter. - */ - public function test_register_hooks_adds_no_content_filter() { - $this->hooks->register_hooks(); - - $this->assertFalse( has_filter( 'the_content', array( $this->hooks, 'modify_content' ) ) ); - } - - /** - * Test the removed boilerplate methods no longer exist. - */ - public function test_legacy_methods_removed() { - $this->assertFalse( method_exists( $this->hooks, 'register_custom_post_type' ) ); - $this->assertFalse( method_exists( $this->hooks, 'modify_content' ) ); - } -} diff --git a/tests/unit/PostTypesTest.php b/tests/unit/PostTypesTest.php deleted file mode 100644 index 7b2681f..0000000 --- a/tests/unit/PostTypesTest.php +++ /dev/null @@ -1,55 +0,0 @@ -post_types = new ExeLearning_Post_Types(); - } - - /** - * Test register_post_types method exists. - */ - public function test_register_post_types_exists() { - $this->assertTrue( method_exists( $this->post_types, 'register_post_types' ) ); - } - - /** - * The plugin deliberately registers no custom post type: ELP packages are - * WordPress attachments and their metadata lives in attachment post meta. - * This guards that decision — registering one here would silently change - * where content is stored. - */ - public function test_register_post_types_registers_no_custom_post_type() { - $before = get_post_types( array(), 'names' ); - - $this->post_types->register_post_types(); - - $this->assertSame( - array(), - array_values( array_diff( get_post_types( array(), 'names' ), $before ) ), - 'register_post_types() must not register any post type.' - ); - $this->assertFalse( post_type_exists( 'exelearning' ) ); - } -} diff --git a/tests/unit/RestApiTest.php b/tests/unit/RestApiTest.php index 2cd3225..6388e85 100644 --- a/tests/unit/RestApiTest.php +++ b/tests/unit/RestApiTest.php @@ -1824,149 +1824,6 @@ public function test_validate_elp_file_path_missing() { $this->assertEquals( 'file_not_found', $result->get_error_code() ); } - /** - * Test cleanup_old_extraction with no extraction. - */ - public function test_cleanup_old_extraction_no_extraction() { - $attachment_id = $this->factory->attachment->create(); - - // No extraction metadata set. - $reflection = new ReflectionMethod( $this->rest_api, 'cleanup_old_extraction' ); - $reflection->setAccessible( true ); - - // Should not throw any errors. - $result = $reflection->invoke( $this->rest_api, $attachment_id ); - - $this->assertNull( $result ); - } - - /** - * Test cleanup_old_extraction with existing folder. - */ - public function test_cleanup_old_extraction_with_folder() { - $attachment_id = $this->factory->attachment->create(); - - $upload_dir = wp_upload_dir(); - $hash = str_repeat( 'c', 40 ); - $folder = trailingslashit( $upload_dir['basedir'] ) . 'exelearning/' . $hash . '/'; - - wp_mkdir_p( $folder ); - file_put_contents( $folder . 'test.html', '' ); - $this->cleanup_paths[] = $folder; - - update_post_meta( $attachment_id, '_exelearning_extracted', $hash ); - - $this->assertTrue( is_dir( $folder ) ); - - $reflection = new ReflectionMethod( $this->rest_api, 'cleanup_old_extraction' ); - $reflection->setAccessible( true ); - - $reflection->invoke( $this->rest_api, $attachment_id ); - - $this->assertFalse( is_dir( $folder ) ); - } - - /** - * Test cleanup_old_extraction with non-existent folder. - */ - public function test_cleanup_old_extraction_nonexistent_folder() { - $attachment_id = $this->factory->attachment->create(); - - $hash = str_repeat( 'd', 40 ); - update_post_meta( $attachment_id, '_exelearning_extracted', $hash ); - - $reflection = new ReflectionMethod( $this->rest_api, 'cleanup_old_extraction' ); - $reflection->setAccessible( true ); - - // Should not throw any errors. - $result = $reflection->invoke( $this->rest_api, $attachment_id ); - - $this->assertNull( $result ); - } - - /** - * Test recursive_delete with non-existent path. - */ - public function test_recursive_delete_nonexistent() { - $reflection = new ReflectionMethod( $this->rest_api, 'recursive_delete' ); - $reflection->setAccessible( true ); - - // Should not throw any errors. - $result = $reflection->invoke( $this->rest_api, '/nonexistent/path/to/delete' ); - - $this->assertNull( $result ); - } - - /** - * Test recursive_delete with file. - */ - public function test_recursive_delete_file() { - $upload_dir = wp_upload_dir(); - $file_path = $upload_dir['basedir'] . '/test-delete-file.txt'; - file_put_contents( $file_path, 'test content' ); - - $this->assertTrue( file_exists( $file_path ) ); - - $reflection = new ReflectionMethod( $this->rest_api, 'recursive_delete' ); - $reflection->setAccessible( true ); - - $reflection->invoke( $this->rest_api, $file_path ); - - $this->assertFalse( file_exists( $file_path ) ); - } - - /** - * Test recursive_delete with nested directory. - */ - public function test_recursive_delete_nested_directory() { - $upload_dir = wp_upload_dir(); - $base_dir = $upload_dir['basedir'] . '/test-nested-delete/'; - $sub_dir = $base_dir . 'subdir/'; - - wp_mkdir_p( $sub_dir ); - file_put_contents( $base_dir . 'file1.txt', 'content1' ); - file_put_contents( $sub_dir . 'file2.txt', 'content2' ); - - $this->assertTrue( is_dir( $base_dir ) ); - $this->assertTrue( is_dir( $sub_dir ) ); - - $reflection = new ReflectionMethod( $this->rest_api, 'recursive_delete' ); - $reflection->setAccessible( true ); - - $reflection->invoke( $this->rest_api, $base_dir ); - - $this->assertFalse( is_dir( $base_dir ) ); - } - - /** - * Test recursive_delete with symlink. - */ - public function test_recursive_delete_symlink() { - $upload_dir = wp_upload_dir(); - $target_file = $upload_dir['basedir'] . '/symlink-target.txt'; - $symlink = $upload_dir['basedir'] . '/test-symlink'; - - file_put_contents( $target_file, 'target content' ); - - // Create symlink if supported. - if ( @symlink( $target_file, $symlink ) ) { - $this->assertTrue( is_link( $symlink ) ); - - $reflection = new ReflectionMethod( $this->rest_api, 'recursive_delete' ); - $reflection->setAccessible( true ); - - $reflection->invoke( $this->rest_api, $symlink ); - - $this->assertFalse( is_link( $symlink ) ); - // Target should still exist. - $this->assertTrue( file_exists( $target_file ) ); - } - - if ( file_exists( $target_file ) ) { - unlink( $target_file ); - } - } - /** * Test ensure_elpx_extension normalizes any filename to .elpx. * diff --git a/tests/unit/UninstallTest.php b/tests/unit/UninstallTest.php new file mode 100644 index 0000000..f7d5476 --- /dev/null +++ b/tests/unit/UninstallTest.php @@ -0,0 +1,41 @@ +assertFalse( get_option( $option ), $option . ' survived uninstall.' ); + } + $this->assertSame( 'Kept', get_option( 'blogname' ) ); + } +} diff --git a/uninstall.php b/uninstall.php index dfac54a..d129f0a 100644 --- a/uninstall.php +++ b/uninstall.php @@ -2,21 +2,8 @@ /** * Fired when the plugin is uninstalled. * - * When populating this file, consider the following flow - * of control: - * - * - This method should be static - * - Check if the $_REQUEST content actually is the plugin name - * - Run an admin referrer check to make sure it goes through authentication - * - Verify the output of $_GET makes sense - * - Repeat with other user roles. Best directly by using the links/query string parameters. - * - Repeat things for multisite. Once for a single site in the network, once sitewide. - * - * This file may be updated more in future version of the Boilerplate; however, this is the - * general skeleton and outline for how the file should work. - * - * For more information, see the following discussion: - * https://github.com/tommcfarlin/WordPress-Plugin-Boilerplate/pull/123#issuecomment-28541913 + * Removes the plugin's settings. Uploaded .elpx attachments, their extracted + * content and uploaded style packages are user content and are left in place. * * @link https://www3.gobiernodecanarias.org/medusa/ecoescuela/ate/ * @@ -27,3 +14,29 @@ if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) { exit; } + +/** + * Delete the plugin's options on the current site. + */ +function exelearning_uninstall_site() { + $options = array( + 'exelearning_db_version', + 'exelearning_disabled_styles', + 'exelearning_proxy_assets', + 'exelearning_styles_block_import', + 'exelearning_styles_registry', + ); + foreach ( $options as $option ) { + delete_option( $option ); + } +} + +if ( is_multisite() ) { + foreach ( get_sites( array( 'fields' => 'ids' ) ) as $exelearning_site_id ) { + switch_to_blog( $exelearning_site_id ); + exelearning_uninstall_site(); + restore_current_blog(); + } +} else { + exelearning_uninstall_site(); +} From f5cd2ffa774b2e677c352565e87859f344a4a359 Mon Sep 17 00:00:00 2001 From: erseco Date: Sat, 26 Sep 2026 09:28:31 +0100 Subject: [PATCH 2/2] Uninstall from every site of a large network get_sites() returns at most 100 sites unless number is set, so networks with more sites kept the plugin's options on the rest. Ask for all of them. --- uninstall.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/uninstall.php b/uninstall.php index d129f0a..be39e4c 100644 --- a/uninstall.php +++ b/uninstall.php @@ -32,7 +32,13 @@ function exelearning_uninstall_site() { } if ( is_multisite() ) { - foreach ( get_sites( array( 'fields' => 'ids' ) ) as $exelearning_site_id ) { + $exelearning_site_ids = get_sites( + array( + 'fields' => 'ids', + 'number' => 0, // WP_Site_Query stops at 100 sites by default. + ) + ); + foreach ( $exelearning_site_ids as $exelearning_site_id ) { switch_to_blog( $exelearning_site_id ); exelearning_uninstall_site(); restore_current_blog();