From 76e2941c029493abe8ea84c791ee1da700bbf5aa Mon Sep 17 00:00:00 2001 From: sect Date: Tue, 26 Nov 2024 15:02:25 +0900 Subject: [PATCH 1/2] feat: add support for programmatic tag order management --- README.md | 30 +++++ includes/class-tag-updater.php | 89 ++++++++++++++ includes/index.php | 16 ++- includes/rest-api.php | 6 +- tests/class-test-tag-updater.php | 196 +++++++++++++++++++++++++++++++ wp-tag-order.php | 1 + 6 files changed, 330 insertions(+), 8 deletions(-) create mode 100644 includes/class-tag-updater.php create mode 100644 tests/class-test-tag-updater.php diff --git a/README.md b/README.md index 9b149a0..7090e97 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ - 🔍 Works with default WordPress tag and custom taxonomy systems - 🔢 Drag-and-Drop interface for easy tag reordering - 📊 Supports multiple post types and taxonomies +- 🔧 **NEW: Programmatic Tag Order Management** - 🌐 **NEW: REST API Support** - Retrieve ordered tags via GET endpoint - Update tag order programmatically @@ -126,6 +127,35 @@ if ( $terms && ! is_wp_error( $terms ) ) : ID, 'post_tag' ); ?> ``` +## Programmatic Tag Order Management + +The plugin provides a `Tag_Updater` class for developers to programmatically manage tag order: + +```php +$tag_updater = new \WP_Tag_Order\Tag_Updater(); + +try { + // Update tag order using an array or comma-separated string + $result = $tag_updater->update_tag_order( + get_the_ID(), // Post ID + 'post_tag', // Taxonomy that enabled in plugin settings + [1, 2, 3] // Tag IDs in desired order + ); +} catch ( \InvalidArgumentException $e ) { + // Error handling + error_log( $e->getMessage() ); +} +``` + +This class allows flexible tag order updates directly from your theme or custom plugin code, supporting both array and string inputs with robust validation. + +#### Return Value + +`update_tag_order()` returns: `int|bool` +- `int`: Meta ID if the tag order meta key didn't previously exist +- `true`: Successful update of existing meta +- `false`: Update failed or the new tag order is identical to the existing order + ## REST API The WP Tag Order plugin provides two REST API endpoints for managing tag order: diff --git a/includes/class-tag-updater.php b/includes/class-tag-updater.php new file mode 100644 index 0000000..8e39bb2 --- /dev/null +++ b/includes/class-tag-updater.php @@ -0,0 +1,89 @@ +|string $tag_ids Array of tag IDs or comma-separated tag IDs. + * + * @return int|bool True if meta update was successful, false otherwise. + * @throws \InvalidArgumentException If input validation fails. + */ + public function update_tag_order( int $post_id, string $taxonomy, array|string $tag_ids ): int|bool { + // Validate inputs. + if ( $post_id <= 0 ) { + throw new \InvalidArgumentException( 'Invalid post ID' ); + } + + if ( empty( $taxonomy ) ) { + throw new \InvalidArgumentException( 'Taxonomy cannot be empty' ); + } + + if ( ! taxonomy_exists( $taxonomy ) ) { + throw new \InvalidArgumentException( sprintf( 'Invalid taxonomy: %s', esc_html( $taxonomy ) ) ); + } + + if ( is_taxonomy_hierarchical( $taxonomy ) ) { + throw new \InvalidArgumentException( sprintf( 'Taxonomy %s is hierarchical and not supported', esc_html( $taxonomy ) ) ); + } + + // Convert string to array if needed. + if ( is_string( $tag_ids ) ) { + $tag_ids = explode( ',', wp_unslash( $tag_ids ) ); + } + + if ( empty( $tag_ids ) ) { + throw new \InvalidArgumentException( 'Tag IDs cannot be empty' ); + } + + if ( ! wto_is_enabled_taxonomy( $taxonomy ) ) { + throw new \InvalidArgumentException( sprintf( 'Taxonomy %s is not enabled for tag ordering', esc_html( $taxonomy ) ) ); + } + + // Sanitize tag IDs. + $sanitized_tag_ids = array_map( + function ( $tag_id ): int { + // Ensure each tag ID is a positive integer. + $sanitized_id = filter_var( + $tag_id, + FILTER_VALIDATE_INT, + array( + 'options' => array( + 'min_range' => 1, + ), + ) + ); + + if ( false === $sanitized_id ) { + throw new \InvalidArgumentException( 'Invalid tag ID: ' . esc_html( is_string( $tag_id ) ? $tag_id : (string) $tag_id ) ); + } + + return $sanitized_id; + }, + $tag_ids + ); + + // Serialize tags for storage. + $meta_box_tags_value = serialize( $sanitized_tag_ids ); + + // Update post meta. + return update_post_meta( $post_id, 'wp-tag-order-' . $taxonomy, $meta_box_tags_value ); + } +} diff --git a/includes/index.php b/includes/index.php index fe83efc..bca52ad 100644 --- a/includes/index.php +++ b/includes/index.php @@ -425,12 +425,18 @@ function ajax_wto_update_tags(): void { exit; } - $newordertags = array_map( 'sanitize_text_field', explode( ',', wp_unslash( $tags ) ) ); - $meta_box_tags_value = serialize( $newordertags ); - $result = update_post_meta( intval( $id ), 'wp-tag-order-' . $taxonomy, $meta_box_tags_value ); + try { + $tag_updater = new \WP_Tag_Order\Tag_Updater(); + $result = $tag_updater->update_tag_order( + intval( $id ), + $taxonomy, + $tags + ); - echo wp_json_encode( $result ); - exit; + wp_send_json_success( $result ); + } catch ( \InvalidArgumentException $e ) { + wp_send_json_error( $e->getMessage(), 400 ); + } } add_action( 'wp_ajax_wto_update_tags', 'ajax_wto_update_tags' ); add_action( 'wp_ajax_nopriv_wto_update_tags', 'ajax_wto_update_tags' ); diff --git a/includes/rest-api.php b/includes/rest-api.php index 8eb80e3..a6e253e 100644 --- a/includes/rest-api.php +++ b/includes/rest-api.php @@ -327,14 +327,14 @@ function ( $tag_id ) use ( $taxonomy ) { } // Prepare and update metadata. - $meta_box_tags_value = serialize( $tags ); - $meta_result = update_post_meta( $post_id, 'wp-tag-order-' . $taxonomy, $meta_box_tags_value ); + $tag_updater = new \WP_Tag_Order\Tag_Updater(); + $result = $tag_updater->update_tag_order( $post_id, $taxonomy, $tags ); // Update post terms. $term_taxonomy_ids = wp_set_object_terms( $post_id, $tags, $taxonomy ); // Handle metadata update failure. - if ( false === $meta_result ) { + if ( false === $result ) { return rest_ensure_response( array( 'success' => false, diff --git a/tests/class-test-tag-updater.php b/tests/class-test-tag-updater.php new file mode 100644 index 0000000..f19c691 --- /dev/null +++ b/tests/class-test-tag-updater.php @@ -0,0 +1,196 @@ +tag_updater = new Tag_Updater(); + $this->test_taxonomy = 'post_tag'; + + // Ensure the test taxonomy is enabled for the plugin. + add_filter( 'wto_is_enabled_taxonomy', array( $this, 'enable_test_taxonomy' ), 10, 1 ); + + // Register test taxonomy if not already registered. + if ( ! taxonomy_exists( $this->test_taxonomy ) ) { + register_taxonomy( $this->test_taxonomy, 'post' ); + } + + // Enable specific taxonomy for the test. + $enabled_taxonomies = array( 'post_tag' ); + update_option( 'wpto_enabled_taxonomies', $enabled_taxonomies ); + } + + /** + * Teardown after each test. + * Resets the enabled taxonomies option. + */ + protected function tearDown(): void { + // Remove the filter after the test. + remove_filter( 'wto_is_enabled_taxonomy', array( $this, 'enable_test_taxonomy' ) ); + + // Reset the enabled taxonomies option. + delete_option( 'wpto_enabled_taxonomies' ); + + parent::tearDown(); + } + + /** + * Mock method to enable test taxonomy for the plugin + * + * @param string $taxonomy Taxonomy name. + * @return bool + */ + public function enable_test_taxonomy( $taxonomy ): bool { + return $taxonomy === $this->test_taxonomy; + } + + /** + * Test successful tag order update + * + * @covers Tag_Updater::update_tag_order + */ + public function test_update_tag_order_with_array(): void { + // Create a test post. + $post_id = $this->factory()->post->create(); + + // Create test tags. + $tag_ids = array( + $this->factory()->term->create( array( 'taxonomy' => $this->test_taxonomy ) ), + $this->factory()->term->create( array( 'taxonomy' => $this->test_taxonomy ) ), + $this->factory()->term->create( array( 'taxonomy' => $this->test_taxonomy ) ), + ); + + // Update tag order. + $result = $this->tag_updater->update_tag_order( $post_id, $this->test_taxonomy, $tag_ids ); + + $this->assertNotFalse( $result, 'Failed to update metadata for tag order' ); + + // Verify meta was saved correctly. + $saved_tags = unserialize( get_post_meta( $post_id, 'wp-tag-order-' . $this->test_taxonomy, true ) ); + $this->assertSame( $tag_ids, $saved_tags ); + } + + /** + * Test tag order update with comma-separated string + * + * @covers Tag_Updater::update_tag_order + */ + public function test_update_tag_order_with_string(): void { + // Create a test post. + $post_id = $this->factory()->post->create(); + + // Create test tags. + $tag_ids = array( + $this->factory()->term->create( array( 'taxonomy' => $this->test_taxonomy ) ), + $this->factory()->term->create( array( 'taxonomy' => $this->test_taxonomy ) ), + ); + + $tag_ids_string = implode( ',', $tag_ids ); + + // Update tag order. + $result = $this->tag_updater->update_tag_order( $post_id, $this->test_taxonomy, $tag_ids_string ); + + $this->assertNotFalse( $result, 'Failed to update metadata for tag order' ); + + // Verify meta was saved correctly. + $saved_tags = unserialize( get_post_meta( $post_id, 'wp-tag-order-' . $this->test_taxonomy, true ) ); + $this->assertSame( $tag_ids, $saved_tags ); + } + + /** + * Test invalid post ID throws exception + * + * @covers Tag_Updater::update_tag_order + */ + public function test_update_tag_order_with_invalid_post_id(): void { + $this->expectException( \InvalidArgumentException::class ); + $this->expectExceptionMessage( 'Invalid post ID' ); + + $this->tag_updater->update_tag_order( 0, $this->test_taxonomy, array( 1 ) ); + } + + /** + * Test empty taxonomy throws exception + * + * @covers Tag_Updater::update_tag_order + */ + public function test_update_tag_order_with_empty_taxonomy(): void { + $this->expectException( \InvalidArgumentException::class ); + $this->expectExceptionMessage( 'Taxonomy cannot be empty' ); + + $post_id = $this->factory()->post->create(); + $this->tag_updater->update_tag_order( $post_id, '', array( 1 ) ); + } + + /** + * Test non-existent taxonomy throws exception + * + * @covers Tag_Updater::update_tag_order + */ + public function test_update_tag_order_with_non_existent_taxonomy(): void { + $this->expectException( \InvalidArgumentException::class ); + $this->expectExceptionMessage( 'Invalid taxonomy: non_existent_taxonomy' ); + + $post_id = $this->factory()->post->create(); + $this->tag_updater->update_tag_order( $post_id, 'non_existent_taxonomy', array( 1 ) ); + } + + /** + * Test empty tag IDs throws exception + * + * @covers Tag_Updater::update_tag_order + */ + public function test_update_tag_order_with_empty_tag_ids(): void { + $this->expectException( \InvalidArgumentException::class ); + $this->expectExceptionMessage( 'Tag IDs cannot be empty' ); + + $post_id = $this->factory()->post->create(); + $this->tag_updater->update_tag_order( $post_id, $this->test_taxonomy, array() ); + } + + /** + * Test invalid tag ID throws exception + * + * @covers Tag_Updater::update_tag_order + */ + public function test_update_tag_order_with_invalid_tag_id(): void { + $this->expectException( \InvalidArgumentException::class ); + $this->expectExceptionMessage( 'Invalid tag ID: invalid' ); + + $post_id = $this->factory()->post->create(); + $this->tag_updater->update_tag_order( $post_id, $this->test_taxonomy, array( 'invalid' ) ); + } +} diff --git a/wp-tag-order.php b/wp-tag-order.php index 06cffcc..88edc3a 100644 --- a/wp-tag-order.php +++ b/wp-tag-order.php @@ -77,6 +77,7 @@ function my_plugin_row_meta( array $plugin_meta, string $plugin_file, array $plu if ( wptagorder_phpversioncheck() ) { require_once plugin_dir_path( __FILE__ ) . 'includes/functions.php'; + require_once plugin_dir_path( __FILE__ ) . 'includes/class-tag-updater.php'; require_once plugin_dir_path( __FILE__ ) . 'includes/category-template.php'; require_once plugin_dir_path( __FILE__ ) . 'includes/index.php'; require_once plugin_dir_path( __FILE__ ) . 'includes/rest-api.php'; From 370b01ba4a7355bd3799fb58dde69c8c558cecf5 Mon Sep 17 00:00:00 2001 From: sect Date: Tue, 26 Nov 2024 15:15:35 +0900 Subject: [PATCH 2/2] test: fix errors on unit test --- tests/bootstrap.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index d90a5a2..aac6be8 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -37,6 +37,7 @@ */ function _manually_load_plugin() { require dirname( __DIR__, 1 ) . '/wp-tag-order.php'; + require_once dirname( __DIR__, 1 ) . '/includes/class-tag-updater.php'; } tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );