feat: add parsing of WordPress taxonomies and terms (#2)

Reviewed-on: https://codeberg.org/vlw/wp/pulls/2
This commit is contained in:
Victor Westerlund 2026-02-15 09:51:02 +01:00
parent 6d1fa852b5
commit eebe93fc53
10 changed files with 324 additions and 12 deletions

View file

@ -9,8 +9,11 @@
use vlw\WP\Database; use vlw\WP\Database;
use vlw\WP\Tables\Posts; use vlw\WP\Tables\Posts;
use vlw\WP\Posts\PostMeta; use vlw\WP\Posts\PostMeta;
use vlw\WP\Posts\Taxonomy\Term;
use vlw\WP\Posts\Type\Attachment;
use function vlw\WP\Support\slugify; use function vlw\WP\Support\slugify;
require_once "Taxonomy/Term.php";
require_once dirname(__DIR__, 1) . "/Tables/Posts.php"; require_once dirname(__DIR__, 1) . "/Tables/Posts.php";
require_once dirname(__DIR__, 1) . "/Support/Slugify.php"; require_once dirname(__DIR__, 1) . "/Support/Slugify.php";
@ -21,10 +24,10 @@
* @param string $name * @param string $name
* @return static|null * @return static|null
*/ */
public static function from_name(string $name): ?static { public static function from_name(string $name, bool $slugify = true): ?static {
$query = Database::current() $query = Database::current()
->from(Database::get_table(Posts::NAME)) ->from(Database::get_table(Posts::TABLE_NAME))
->where([Posts::POST_NAME->value => slugify($name)]) ->where([Posts::POST_NAME->value => $slugify ? slugify($name) : $name])
->limit(1) ->limit(1)
->select(Posts::ID->value); ->select(Posts::ID->value);
@ -70,7 +73,7 @@
Posts::COMMENT_COUNT->value => 0 Posts::COMMENT_COUNT->value => 0
]; ];
if (!parent::create(Database::get_table(Posts::NAME), $values)) { if (!parent::create(Database::get_table(Posts::TABLE_NAME), $values)) {
throw new Exception("Failed to create database entity"); throw new Exception("Failed to create database entity");
} }
@ -79,7 +82,7 @@
public function __construct(public readonly int $id) { public function __construct(public readonly int $id) {
parent::__construct( parent::__construct(
Database::get_table(Posts::NAME), Database::get_table(Posts::TABLE_NAME),
Posts::values(), Posts::values(),
[Posts::ID->value => (int) $id] [Posts::ID->value => (int) $id]
); );
@ -95,6 +98,15 @@
return $key ? PostMeta::get_post_meta($this, $key) : PostMeta::get_all_post_meta($this); return $key ? PostMeta::get_post_meta($this, $key) : PostMeta::get_all_post_meta($this);
} }
/**
* Returns an array of all Terms assigned to this Post
*
* @return array
*/
public function terms(): array {
return Term::from_post($this);
}
public int $post_author { public int $post_author {
get => $this->get(Posts::POST_AUTHOR->value); get => $this->get(Posts::POST_AUTHOR->value);
set (int $post_author) => $this->set(Posts::POST_AUTHOR->value, $post_author); set (int $post_author) => $this->set(Posts::POST_AUTHOR->value, $post_author);

View file

@ -22,7 +22,7 @@
*/ */
public static function get_post_meta(Post $post, string $key): static|false { public static function get_post_meta(Post $post, string $key): static|false {
$query = Database::current() $query = Database::current()
->from(Database::get_table(PostMetaTable::NAME)) ->from(Database::get_table(PostMetaTable::TABLE_NAME))
->where([ ->where([
PostMetaTable::POST_ID->value => $post->id, PostMetaTable::POST_ID->value => $post->id,
PostMetaTable::META_KEY->value => $key PostMetaTable::META_KEY->value => $key
@ -41,7 +41,7 @@
*/ */
public static function get_all_post_meta(Post $post): array { public static function get_all_post_meta(Post $post): array {
$query = Database::current() $query = Database::current()
->from(Database::get_table(PostMetaTable::NAME)) ->from(Database::get_table(PostMetaTable::TABLE_NAME))
->where([PostMetaTable::POST_ID->value => $post->id]) ->where([PostMetaTable::POST_ID->value => $post->id])
->select(PostMetaTable::META_ID->value); ->select(PostMetaTable::META_ID->value);
@ -71,7 +71,7 @@
PostMetaTable::META_VALUE->value => $meta_value PostMetaTable::META_VALUE->value => $meta_value
]; ];
if (!parent::create(Database::get_table(PostMetaTable::NAME), $values)) { if (!parent::create(Database::get_table(PostMetaTable::TABLE_NAME), $values)) {
throw new Exception("Failed to create database entity"); throw new Exception("Failed to create database entity");
} }
@ -80,12 +80,23 @@
public function __construct(public readonly int $id) { public function __construct(public readonly int $id) {
parent::__construct( parent::__construct(
Database::get_table(PostMetaTable::NAME), Database::get_table(PostMetaTable::TABLE_NAME),
PostMetaTable::values(), PostMetaTable::values(),
[PostMetaTable::META_ID->value => (int) $id] [PostMetaTable::META_ID->value => (int) $id]
); );
} }
/**
* Delete this post meta field from the database
*
* @return void
*/
public function delete(): void {
$this->db
->from(PostMetaTable::TABLE_NAME)
->delete([PostMetaTable::META_ID->value => $this->id]);
}
public Post $post { public Post $post {
get => new Post($this->get(PostMetaTable::POST_ID->value)); get => new Post($this->get(PostMetaTable::POST_ID->value));
set (Post $post) => $this->set(PostMetaTable::POST_ID->value, $post->id); set (Post $post) => $this->set(PostMetaTable::POST_ID->value, $post->id);

View file

@ -0,0 +1,85 @@
<?php
namespace vlw\WP\Posts\Taxonomy;
use Exception;
use vlw\Scaffold\Database\Model;
use vlw\WP\Database;
use vlw\WP\Tables\Taxonomies;
require_once dirname(__DIR__, 2) . "/Tables/Taxonomies.php";
class Taxonomy extends Model {
/**
* Returns a Taxonomy by taxonomy name
*
* @param string $name
* @return static|null
*/
public function from(string $taxonomy): ?static {
$query = Database::current()
->from(Database::get_table(Taxonomies::TABLE_NAME))
->where([Taxonomies::TAXONOMY->value => $taxonomy])
->limit(1)
->select(Taxonomies::TERM_TAXONOMY_ID->value);
return $query->num_rows === 1 ? new static($query->fetch_assoc()[Taxonomies::TERM_ID->value]) : null;
}
/**
* Create a new post meta field for a given post
*
* @param string $title Post title
* @param string $type Post type
* @return static
*/
public static function new(string $taxonomy): static {
// Return existing instance of Taxonomy if it exists
if (self::from($taxonomy)) {
return self::from($taxonomy);
}
$values = [
Taxonomies::TERM_TAXONOMY_ID->value => null,
Taxonomies::TERM_ID->value => 0,
Taxonomies::DESCRIPTION->value => "",
Taxonomies::PARENT->value => 0,
Taxonomies::COUNT->value => 0
];
if (!parent::create(Database::get_table(Taxonomies::TABLE_NAME), $values)) {
throw new Exception("Failed to create database entity");
}
return self::from($taxonomy);
}
public function __construct(public readonly int $id) {
parent::__construct(
Database::get_table(Taxonomies::TABLE_NAME),
Taxonomies::values(),
[Taxonomies::TERM_TAXONOMY_ID->value => (int) $id]
);
}
public Term $term {
get => new Term($this->get(Taxonomies::TERM_ID->value));
set (Term $term) => $this->set(Taxonomies::TERM_ID->value, $term->id);
}
public string $taxonomy {
get => $this->get(Taxonomies::TAXONOMY->value);
set (string $taxonomy) => $this->set(Taxonomies::TAXONOMY->value, $taxonomy);
}
public string $description {
get => $this->get(Taxonomies::DESCRIPTION->value);
set (string $description) => $this->set(Taxonomies::DESCRIPTION->value, $description);
}
public ?self $parent {
get => $this->get(Taxonomies::PARENT->value) ? new self($this->get(Taxonomies::PARENT->value)) : null;
set (?self $parent) => $this->set(Taxonomies::PARENT->value, $parent->id);
}
}

155
src/Posts/Taxonomy/Term.php Normal file
View file

@ -0,0 +1,155 @@
<?php
namespace vlw\WP\Posts\Taxonomy;
use Exception;
use vlw\Scaffold\Database\Model;
use vlw\WP\Database;
use vlw\WP\Posts\Post;
use vlw\WP\Tables\Terms;
use vlw\WP\Posts\Taxonomy\Taxonomy;
use vlw\WP\Tables\TermRelationships;
require_once dirname(__DIR__, 1) . "/Post.php";
require_once dirname(__DIR__, 2) . "/Tables/Terms.php";
require_once dirname(__DIR__, 2) . "/Tables/TermRelationships.php";
class Term extends Model {
/**
* Returns a Term by a name
*
* @param string $name
* @return static|null
*/
public static function from_name(string $name): ?static {
$query = Database::current()
->from(Database::get_table(Terms::TABLE_NAME))
->where([Terms::NAME->value => $name])
->limit(1)
->select(Terms::TERM_ID->value);
return $query->num_rows === 1 ? new static($query->fetch_assoc()[Terms::TERM_ID->value]) : null;
}
/**
* Returns an array of all Terms associated with a Post
*
* @param Post $post
* @return array
*/
public static function from_post(Post $post): array {
$query = Database::current()
->from(Database::get_table(TermRelationships::TABLE_NAME))
->where([TermRelationships::OBJECT_ID->value => $post->id])
->select(TermRelationships::TERM_TAXONOMY_ID->value);
return array_map(fn(array $post_meta): static => new static($post_meta[TermRelationships::TERM_TAXONOMY_ID->value]), $query->fetch_all(MYSQLI_ASSOC));
}
/**
* Create a new post meta field for a given post
*
* @param string $title Post title
* @param string $type Post type
* @return static
*/
public static function new(string $name, ?string $slug = null): static {
// Update and return meta key for existing id
if (self::from_name($name)) {
$model = self::from_name($name);
$model->slug = $slug;
return $model;
}
$values = [
Terms::TERM_ID->value => null,
Terms::NAME->value => $name,
Terms::SLUG->value => $slug,
Terms::TERM_GROUP->value => 0
];
if (!parent::create(Database::get_table(Terms::TABLE_NAME), $values)) {
throw new Exception("Failed to create database entity");
}
return self::from_name($name);
}
public function __construct(public readonly int $id) {
parent::__construct(
Database::get_table(Terms::TABLE_NAME),
Terms::values(),
[Terms::TERM_ID->value => $id]
);
}
/**
* Returns the corresponding Taxonomy for this Term
*
* @return Taxonomy
*/
public function taxonomy(): Taxonomy {
return new Taxonomy($this->id);
}
/**
* Add this Term to a target Post
*
* @param Post $post
* @return void
*/
public function add_to_post(Post $post): void {
// Bail out if this term has already been added to the target Post
if (self::from_post($post)) {
return;
}
$query = Database::current()
->from(Database::get_table(TermRelationships::TABLE_NAME))
->insert([
TermRelationships::OBJECT_ID->value => $post->id,
TermRelationships::TERM_TAXONOMY_ID->value => $this->id,
TermRelationships::TERM_ORDER->value => 0
]);
if ($query === false) {
throw new Exception("Failed to create database entity");
}
}
/**
* Remove this Term from a target Post
*
* @param Post $post
* @return void
*/
public function remove_from_post(Post $post): void {
$query = Database::current()
->from(Database::get_table(TermRelationships::TABLE_NAME))
->delete([
TermRelationships::OBJECT_ID->value => $post->id,
TermRelationships::TERM_TAXONOMY_ID->value => $this->id
]);
if ($query === false) {
throw new Exception("Failed to create database entity");
}
}
public string $name {
get => $this->get(Terms::NAME->value);
set (string $name) => $this->set(Terms::NAME->value, $name);
}
public string $slug {
get => $this->get(Terms::SLUG->value);
set (string $slug) => $this->set(Terms::SLUG->value, $slug);
}
public int $term_group {
get => $this->get(Terms::TERM_GROUP->value);
set (int $term_group) => $this->set(Terms::TERM_GROUP->value, $term_group);
}
}

View file

@ -17,7 +17,7 @@
* @param string $name * @param string $name
* @return array<static> * @return array<static>
*/ */
public static function get_shortcodes(string &$source, string $name): array { public static function get_shortcodes(string $source, string $name): array {
$matches = []; $matches = [];
preg_match_all("/\[{$name} /", $source, $matches, PREG_OFFSET_CAPTURE); preg_match_all("/\[{$name} /", $source, $matches, PREG_OFFSET_CAPTURE);

View file

@ -7,7 +7,7 @@
enum PostMeta: string { enum PostMeta: string {
use xEnum; use xEnum;
public const NAME = "postmeta"; public const TABLE_NAME = "postmeta";
case META_ID = "meta_id"; case META_ID = "meta_id";
case POST_ID = "post_id"; case POST_ID = "post_id";

View file

@ -7,7 +7,7 @@
enum Posts: string { enum Posts: string {
use xEnum; use xEnum;
public const NAME = "posts"; public const TABLE_NAME = "posts";
case ID = 'ID'; case ID = 'ID';
case POST_AUTHOR = 'post_author'; case POST_AUTHOR = 'post_author';

18
src/Tables/Taxonomies.php Normal file
View file

@ -0,0 +1,18 @@
<?php
namespace vlw\WP\Tables;
use vlw\xEnum;
enum Taxonomies: string {
use xEnum;
public const TABLE_NAME = "term_taxonomy";
case TERM_TAXONOMY_ID = "term_taxonomy_id";
case TERM_ID = "term_id";
case TAXONOMY = "taxonomy";
case DESCRIPTION = "description";
case PARENT = "parent";
case COUNT = "count";
}

View file

@ -0,0 +1,15 @@
<?php
namespace vlw\WP\Tables;
use vlw\xEnum;
enum TermRelationships: string {
use xEnum;
public const TABLE_NAME = "term_relationships";
case OBJECT_ID = "object_id";
case TERM_TAXONOMY_ID = "term_taxonomy_id";
case TERM_ORDER = "term_order";
}

16
src/Tables/Terms.php Normal file
View file

@ -0,0 +1,16 @@
<?php
namespace vlw\WP\Tables;
use vlw\xEnum;
enum Terms: string {
use xEnum;
public const TABLE_NAME = "terms";
case TERM_ID = "term_id";
case NAME = "name";
case SLUG = "slug";
case TERM_GROUP = "term_group";
}