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

Lay down foundation for utilizing TransformersRegistry class for transformers registration #190

Open
wants to merge 9 commits into
base: trunk
Choose a base branch
from
15 changes: 9 additions & 6 deletions src/plugin/class-engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,28 @@

class Engine {

private string $storage_post_type = 'liberated_data';
public const string STORAGE_POST_TYPE = 'liberated_data';

public function __construct() {
require 'enum-subject-type.php';

require 'class-transformersregistry.php';
require 'class-post-type-ui.php';
require 'class-transformer.php';
require 'class-subjects-controller.php';
require 'class-storage.php';
require 'class-schema.php';
require 'utils.php';
require 'class-subject.php';

( function () {
$transformer = new Transformer();

new Post_Type_UI( $this->storage_post_type, $transformer );
new Transformer();
new Post_Type_UI( self::STORAGE_POST_TYPE );

// REST API
new Subjects_Controller( $this->storage_post_type );
new Subjects_Controller( self::STORAGE_POST_TYPE );

new Storage( $this->storage_post_type );
new Storage( self::STORAGE_POST_TYPE );
} )();
}
}
8 changes: 3 additions & 5 deletions src/plugin/class-post-type-ui.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@

class Post_Type_UI {
private string $post_type;
private Transformer $transformer;

public function __construct( $custom_post_type, Transformer $transformer ) {
$this->post_type = $custom_post_type;
$this->transformer = $transformer;
public function __construct( $custom_post_type ) {
$this->post_type = $custom_post_type;

$this->remove_add_new_option( $this->post_type );

Expand Down Expand Up @@ -100,7 +98,7 @@ function () {
global $post;

$post_id = $post->ID;
$transformed_post_id = $this->transformer->get_transformed_post_id( $post_id );
$transformed_post_id = get_post_meta( $post_id, Transformer::META_KEY_LIBERATED_OUTPUT, true );

if ( $transformed_post_id ) {
echo '<pre>PostID: ' . esc_html( $transformed_post_id ) . '</pre>';
Expand Down
69 changes: 69 additions & 0 deletions src/plugin/class-subject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace DotOrg\TryWordPress;

use WP_Post;

/**
* Subject class offers an easy view of liberated data, abstracting away the implementation details
*
* WP specific data is private and only exposed via methods to limit leakage of implementation details
* Raw data is available as public fields
*/
class Subject {

private int $id;
private int $author_id;

public string $source_html;
public string $source_url;
public string $type;
public string $title;
public string $date;
public string $content;

/**
* Creates a new Subject instance from a WordPress post.
*
* @param int $post_id The WordPress post ID to create the subject from.
* @return Subject|false The Subject instance or false if the post doesn't exist.
*/
public static function from_post( int $post_id ): Subject|false {
$post = get_post( $post_id );

if ( ! $post instanceof WP_Post ) {
return false;
}

return new self( $post );
}

/**
* Private constructor to enforce using the factory method.
*
* @param WP_Post $post The WordPress post to create the subject from.
*/
private function __construct( WP_Post $post ) {
$this->id = $post->ID;
$this->author_id = $post->post_author;
$this->source_html = $post->post_content_filtered;
$this->source_url = $post->guid;

$this->type = get_post_meta( $post->ID, 'subject_type', true );
$this->title = get_post_meta( $post->ID, 'raw_title', true );
$this->date = get_post_meta( $post->ID, 'raw_date', true );
$this->content = get_post_meta( $post->ID, 'raw_content', true );
}

public function id(): int {
return $this->id;
}

public function author_id(): int {
return $this->author_id;
}

public function transformed_post_id(): int {
return absint( get_post_meta( $this->id, Transformer::META_KEY_LIBERATED_OUTPUT, true ) );
}
}
6 changes: 3 additions & 3 deletions src/plugin/class-subjects-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ public function create_item( $request ): WP_REST_Response|WP_Error {
$subject_type = $this->get_subject_type( $request );
update_post_meta( $item['ID'], 'subject_type', $subject_type );

do_action( 'dl_data_saved', $item['ID'], 'create' );
do_action( 'dl_data_saved', Subject::from_post( $item['ID'] ), 'create' );

return $this->prepare_item_for_response( $item, $request, $subject_type );
}
Expand All @@ -316,7 +316,7 @@ public function update_item( $request ): WP_REST_Response|WP_Error {
update_post_meta( $item['ID'], $key, $value );
}

do_action( 'dl_data_saved', $item['ID'], 'update' );
do_action( 'dl_data_saved', Subject::from_post( $item['ID'] ), 'update' );

return $this->prepare_item_for_response( $item, $request );
}
Expand Down Expand Up @@ -382,7 +382,7 @@ public function prepare_item_for_response( $item, $request ): WP_REST_Response|W
'authorId' => $item['post_author'] ?? '',
'sourceUrl' => $item['guid'] ?? '',
'sourceHtml' => $item['post_content_filtered'] ?? '',
'transformedId' => absint( get_post_meta( $item['ID'], '_dl_transformed', true ) ),
'transformedId' => absint( get_post_meta( $item['ID'], Transformer::META_KEY_LIBERATED_OUTPUT, true ) ),
);

foreach ( array_keys( Schema::get()[ $subject_type ]['fields'] ) as $field_name ) {
Expand Down
22 changes: 8 additions & 14 deletions src/plugin/class-transformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
use WP_Post;

class Transformer {
private string $meta_key_for_transformed_post = '_dl_transformed';
public const string META_KEY_LIBERATED_SOURCE = '_data_liberation_source';
public const string META_KEY_LIBERATED_OUTPUT = '_data_liberation_output';

public function __construct() {
add_action( 'dl_data_saved', array( $this, 'transform' ), 10, 2 );
Expand Down Expand Up @@ -35,23 +36,15 @@ private function get_post_type_for_transformed_post( int|WP_Post $liberated_post
return apply_filters( 'post_type_for_transformed_post', $post_type, $liberated_post );
}

public function get_transformed_post_id( $liberated_post_id ): int|null {
$value = get_post_meta( $liberated_post_id, $this->meta_key_for_transformed_post, true );
if ( '' === $value ) {
return null;
}

return absint( $value );
}

public function transform( int $liberated_post_id, string $verb ): bool {
public function transform( Subject $subject, string $verb ): bool {
if ( apply_filters( 'skip_native_transformation', false ) ) {
return true;
}

$liberated_post = get_post( $liberated_post_id );
$liberated_post_id = $subject->id();
$liberated_post = get_post( $liberated_post_id );

$transformed_post_id = get_post_meta( $liberated_post->ID, $this->meta_key_for_transformed_post, true );
$transformed_post_id = get_post_meta( $liberated_post->ID, self::META_KEY_LIBERATED_OUTPUT, true );

$title = get_post_meta( $liberated_post->ID, 'parsed_title', true );
if ( empty( $title ) ) {
Expand Down Expand Up @@ -86,7 +79,8 @@ public function transform( int $liberated_post_id, string $verb ): bool {
return false;
}

add_post_meta( $liberated_post->ID, $this->meta_key_for_transformed_post, $inserted_post_id );
update_post_meta( $inserted_post_id, self::META_KEY_LIBERATED_SOURCE, $liberated_post->ID );
update_post_meta( $liberated_post->ID, self::META_KEY_LIBERATED_OUTPUT, $inserted_post_id );
return true;
}
}
125 changes: 125 additions & 0 deletions src/plugin/class-transformersregistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

namespace DotOrg\TryWordPress;

use Exception;
use InvalidArgumentException;

class TransformersRegistry {
private static string $user_choice_meta_key_prefix = '_data_liberation_chosen_handler_';
private static array $handlers = array();

/**
* Add a handler for a specific subject type
*
* @param SubjectType $type The subject type for which handler should be registered.
* @param array $identifier Array containing unique slug and description.
* @param callable $handler The handler function.
* @return void
* @throws InvalidArgumentException If handler is not callable.
*/
public static function add( SubjectType $type, array $identifier, callable $handler ): void {
if ( ! is_callable( $handler ) ) {
throw new InvalidArgumentException( 'Handler must be callable' );
}

if ( ! isset( $identifier['slug'] ) ) {
throw new InvalidArgumentException( 'Identifier slug must be defined' );
}

if ( ! isset( self::$handlers[ $type->value ] ) ) {
self::$handlers[ $type->value ] = array();
}

self::$handlers[ $type->value ][ $identifier['slug'] ] = array(
'slug' => $identifier['slug'],
'description' => $identifier['desc'],
'handler' => $handler,
);
}

/**
* Check if handlers exist for a type
*
* @param SubjectType $type The subject type to check for.
* @return bool True if handlers exist
*/
public static function has( SubjectType $type ): bool {
return isset( self::$handlers[ $type->value ] ) && ! empty( self::$handlers[ $type->value ] );
}

/**
* Check if there is a "compete" i.e., multiple handlers for a type
*
* @param SubjectType $type The subject type to check for.
* @return bool True if handlers exist
*/
public static function is_compete( SubjectType $type ): bool {
return isset( self::$handlers[ $type->value ] ) && count( self::$handlers[ $type->value ] ) > 1;
}

/**
* Execute all handlers for a type
*
* @param SubjectType $type The subject type to handle.
* @param Subject $subject Data to pass to handlers.
* @return void
* @throws Exception If no handler has been registered or user choice hasn't been set when multiples are registered.
*/
public static function handle( SubjectType $type, Subject $subject ): void {
if ( ! self::has( $type ) ) {
throw new Exception( sprintf( 'no handler registered for type: %s', esc_html( $type->value ) ) );
}

if ( self::is_compete( $type ) ) {
$choice = self::get_user_choice( $type );
if ( ! empty( $choice ) ) {
$chosen = self::$handlers[ $type->value ][ $choice ];
} else {
throw new Exception( 'handle() invoked without user choice on compete' );
}
} else {
$chosen = current( self::$handlers[ $type->value ] );
}

$transformed_post_id = $chosen['handler']( $subject );

if ( $transformed_post_id ) {
update_post_meta( $subject->id(), Transformer::META_KEY_LIBERATED_OUTPUT, $transformed_post_id );
update_post_meta( $transformed_post_id, Transformer::META_KEY_LIBERATED_SOURCE, $subject->id() );
}
}

/**
* Remove all handlers for a type
*
* @param SubjectType $type The type to clear handlers for.
* @return void
*/
public static function clear( SubjectType $type ): void {
if ( isset( self::$handlers[ $type->value ] ) ) {
unset( self::$handlers[ $type->value ] );
}
}

/**
* Set user choice for what transformer to run for a subject type when multiples are registered
*
* @param SubjectType $type The subject type for which choice is to be saved.
* @param string $transformer_slug Identifying slug of the chosen transformer.
* @return void
*/
public static function set_user_choice( SubjectType $type, string $transformer_slug ): void {
update_user_meta( get_current_user_id(), self::$user_choice_meta_key_prefix . $type->value, $transformer_slug );
}

/**
* Retrieves the user choice for what transformer to run for a subject type when multiples are registered
*
* @param SubjectType $type The subject type for which choice is to be retrieved.
* @return string $transformer_slug Identifying slug of the chosen transformer.
*/
public static function get_user_choice( SubjectType $type ): string {
return get_user_meta( get_current_user_id(), self::$user_choice_meta_key_prefix . $type->value, true );
}
}
13 changes: 13 additions & 0 deletions src/plugin/enum-subject-type.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace DotOrg\TryWordPress;

enum SubjectType: string {
case BLOGPOST = 'blog-post';
case PAGE = 'page';
case PRODUCT = 'product';

public function get_display_name(): string {
return ucfirst( $this->value );
}
}
19 changes: 7 additions & 12 deletions tests/plugin/test-transformer.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php

use DotOrg\TryWordPress\Engine;
use DotOrg\TryWordPress\Transformer;
use DotOrg\TryWordPress\Subject;
use PHPUnit\Framework\TestCase;

class Transformer_Test extends TestCase {
Expand All @@ -21,7 +23,7 @@ protected function setUp(): void {
'post_status' => 'draft',
'post_content_filtered' => '<div><p>Content 1</p><p>Content 2</p></div>',
'guid' => 'https://example.com/x',
'post_type' => 'liberated_data',
'post_type' => Engine::STORAGE_POST_TYPE,
)
);
update_post_meta( $this->post_id_in_db, 'subject_type', 'blog-post' );
Expand All @@ -32,11 +34,11 @@ protected function setUp(): void {
protected function tearDown(): void {
parent::tearDown();

$transformed_post_id = $this->transformer->get_transformed_post_id( $this->post_id_in_db );
$transformed_post_id = get_post_meta( $this->post_id_in_db, Transformer::META_KEY_LIBERATED_OUTPUT, true );
wp_delete_post( $transformed_post_id, true );
wp_delete_post( $this->post_id_in_db, true );

delete_post_meta( 99, '_dl_transformed' );
delete_post_meta( 99, Transformer::META_KEY_LIBERATED_OUTPUT );
}

public function testGetPostTypeForTransformedPost() {
Expand All @@ -52,17 +54,10 @@ public function testGetPostTypeForTransformedPost() {
$this->assertEquals( 'product', $result );
}

public function testGetTransformedPost() {
add_post_meta( 99, '_dl_transformed', 999 );

$this->assertEquals( 999, $this->transformer->get_transformed_post_id( 99 ) );
$this->assertEquals( null, $this->transformer->get_transformed_post_id( 88 ) );
}

public function testTransform(): void {
$result = $this->transformer->transform( $this->post_id_in_db, 'whatever' ); // verb isn't currently used
$result = $this->transformer->transform( Subject::from_post( $this->post_id_in_db ), 'whatever' ); // verb isn't currently used

$transformed_post_id = absint( get_post_meta( $this->post_id_in_db, '_dl_transformed', true ) );
$transformed_post_id = absint( get_post_meta( $this->post_id_in_db, Transformer::META_KEY_LIBERATED_OUTPUT, true ) );

$this->assertEquals( $this->post_id_in_db + 1, $transformed_post_id );
$this->assertTrue( $result );
Expand Down
Loading