+
Skip to content

feat: add support for programmatic tag order management #84

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

Merged
merged 2 commits into from
Nov 26, 2024
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
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -126,6 +127,35 @@ if ( $terms && ! is_wp_error( $terms ) ) :
<?php the_terms_ordered( $post->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:
Expand Down
89 changes: 89 additions & 0 deletions includes/class-tag-updater.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php
/**
* WP Tag Order Tag Updater Class
*
* Provides a method to update tag order for a specific post and taxonomy.
*
* @package WP_Tag_Order
* @since 3.11.0
*/

declare(strict_types=1);

namespace WP_Tag_Order;

/**
* Class responsible for updating tag order for a specific post.
*/
class Tag_Updater {
/**
* Update tag order for a specific post and taxonomy.
*
* @param int $post_id The post ID to update tags for.
* @param string $taxonomy The taxonomy name.
* @param array<int>|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 );
}
}
16 changes: 11 additions & 5 deletions includes/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' );
Expand Down
6 changes: 3 additions & 3 deletions includes/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' );
Expand Down
196 changes: 196 additions & 0 deletions tests/class-test-tag-updater.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
<?php
/**
* Test Tag_Updater Class
*
* @package WP_Tag_Order
* @since 3.11.0
*/

declare(strict_types=1);

namespace WP_Tag_Order\Tests;

use WP_Tag_Order\Tag_Updater;
use WP_UnitTestCase;

/**
* Test cases for Tag_Updater class
*/
class Test_Tag_Updater extends WP_UnitTestCase {
/**
* Tag_Updater instance
*
* @var Tag_Updater
*/
private $tag_updater;

/**
* Test taxonomy name
*
* @var string
*/
private $test_taxonomy;

/**
* Setup before each test.
* Enables specific taxonomy for the plugin.
*/
protected function setUp(): void {
parent::setUp();
$this->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' ) );
}
}
1 change: 1 addition & 0 deletions wp-tag-order.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载