Compare commits

...

9 commits

Author SHA1 Message Date
729663a113 feat: add Post::clone() method for shallow copying a Post (#18)
This PR adds a new static method `Post::clone()` which takes a `Post` instance as an argument and creates a new shallow copy of that Post. The copied Post is then returned from this method.

Reviewed-on: https://codeberg.org/vlw/wp/pulls/18
2026-02-24 11:52:12 +01:00
vlw
dba0fe4ebb feat: add basic support for the plugin "Redirection" (#17)
This PR adds very basic support for manipulating the database tables associated with the plugin "[Redirection](https://wordpress.org/plugins/redirection/)". Only the `Items` table is implemented with this PR, and a single method `Item::match()` that tests a URL for matches in Redirection

Reviewed-on: https://codeberg.org/vlw/wp/pulls/17
Co-authored-by: vlw <victor@vlw.se>
Co-committed-by: vlw <victor@vlw.se>
2026-02-24 11:51:37 +01:00
2bfc93ceda refactor: use Database instance from scaffold library (#16)
Bumped vlw/scaffold to 1.7.6 and make use of the `Database::instance()` method.

This is an almost-fix for issue #15. It will not close it though. The Database class is still a hot mess.

Reviewed-on: https://codeberg.org/vlw/wp/pulls/16
2026-02-24 11:51:14 +01:00
0837975758 feat: add support for WP users tables (#10)
This PR adds support for manipulating the WordPress user tables through this library

Reviewed-on: https://codeberg.org/vlw/wp/pulls/10
2026-02-22 11:04:39 +01:00
22f7f26f75 fix: return types for Post property hooks (#11)
Reviewed-on: https://codeberg.org/vlw/wp/pulls/11
2026-02-22 11:04:25 +01:00
1ff1e1aabe feat: add new methods for Attachment type (#12)
Reviewed-on: https://codeberg.org/vlw/wp/pulls/12
2026-02-22 11:04:10 +01:00
1296208866 refactor: set site_url from method instead of static property (#13)
`site_url` is not needed for every operation, let's make it a method that can be called as needed instead of always setting it as static property on the `Database` class.

Reviewed-on: https://codeberg.org/vlw/wp/pulls/13
2026-02-22 11:03:54 +01:00
1b0a9f385f fix: use AUTO_INCREMENT of table as id of new posts (#9)
Reviewed-on: https://codeberg.org/vlw/wp/pulls/9
2026-02-16 13:37:52 +01:00
d52154192d refactor: move properties above methods and substr() varchar table columns (#8)
Reviewed-on: https://codeberg.org/vlw/wp/pulls/8
2026-02-16 13:37:33 +01:00
14 changed files with 712 additions and 170 deletions

View file

@ -21,3 +21,9 @@ $post->post_content;
// Update the datetime modified for this post
$post->post_modified = new DateTimeImmutable();
```
# Plugins
This library also has support for the following plugins:
Plugin|Versions|Reference
--|--|--
[Redirection](https://wordpress.org/plugins/redirection/)|5.3.10 (limited support)|[Plugins/redirection](/vlw/wp/src/branch/feat/plugin-redirection/src/Plugins/redirection)

6
composer.lock generated
View file

@ -35,11 +35,11 @@
},
{
"name": "vlw/scaffold",
"version": "1.7.0",
"version": "1.7.6",
"source": {
"type": "git",
"url": "https://codeberg.org/vlw/scaffold",
"reference": "1536079fe384c0cdc8b3814b67318140dbc339ff"
"reference": "ffd809f76dd50d7a6ee631f21715dca40ec40f44"
},
"require": {
"vlw/mysql": "3.5.*"
@ -61,7 +61,7 @@
}
],
"description": "Project scaffolding (primarily) for Reflect and Vegvisir projects",
"time": "2026-02-11T16:25:22+00:00"
"time": "2026-02-24T09:44:05+00:00"
},
{
"name": "vlw/xenum",

View file

@ -9,7 +9,6 @@
class Database extends DatabaseFramework {
public static string $name = "";
public static string $site_url = "";
private static string $prefix = "wp";
private static string $hostname = "";
@ -32,13 +31,7 @@
* @return static
*/
public static function current(): static {
return new static(
self::$hostname,
self::$username,
self::$password,
self::$name,
self::$prefix
);
return static::instance();
}
/**
@ -71,8 +64,6 @@
$database
);
$this->set_site_url();
parent::__construct();
}
@ -90,11 +81,11 @@
}
/**
* Fetch and set the WordPress siteurl from the options table
* Fetch the WordPress siteurl from the options table
*
* @return void
*/
public function set_site_url() {
public function site_url() {
$query = new DatabaseFramework()
->from(static::$prefix . "_options")
->where(["option_name" => "siteurl"])
@ -105,6 +96,6 @@
throw new Exception("Failed to fetch siteurl from options table");
}
self::$site_url = $query->fetch_assoc()["option_value"];
return $query->fetch_assoc()["option_value"];
}
}

View file

@ -0,0 +1,148 @@
<?php
namespace vlw\WP\Plugins\redirection;
use Exception;
use DateTimeImmutable;
use vlw\Scaffold\Database\Model;
use vlw\WP\Database;
use vlw\WP\Plugins\redirection\Tables\{Items, StatusEnum};
require_once "Tables/Items.php";
class Item extends Model {
public static function match(string $subject): array {
$matches = [];
$subject = self::get_pathname($subject);
$query = Database::current()
->from(Database::get_table(Items::TABLE_NAME))
->where([Items::STATUS->value => StatusEnum::ENABLED->value])
->select([
Items::ID->value,
Items::URL->value,
Items::MATCH_URL->value,
Items::REGEX->value
]);
foreach ($query->fetch_all(MYSQLI_ASSOC) as $row) {
// Check if subject is a direct match against the value of URL if its not a RegEx
if (!$row[Items::REGEX->value]) {
if ($subject === $row[Items::URL->value]) {
$matches[] = new static($row[Items::ID->value]);
}
continue;
}
$preg_match = [];
$pattern = str_replace("/", "\\/", $row[Items::URL->value]);
preg_match("/{$pattern}/", $subject, $preg_match);
// This subject matched this RegEx, instance this redirection Item
if (!$preg_match) {
continue;
}
$matches[] = new static($row[Items::ID->value]);
}
return $matches;
}
/**
* Return the pathname of a given URL
*
* @param string $subject
* @return string
*/
private static function get_pathname(string $subject): string {
// Coerce relative URL into absolute so parse_url() does not freak out
if (substr($subject, 0, 4) !== "http") {
$subject = trim($subject);
$subject = "http://example.com/{$subject}/";
}
return parse_url($subject, PHP_URL_PATH);
}
public function __construct(public readonly int $id) {
parent::__construct(
Database::get_table(Items::TABLE_NAME),
Items::values(),
[Items::ID->value => $id]
);
}
public string $url {
get => $this->get(Items::URL->value);
set (string $url) => $this->set(Items::URL->value, $url);
}
public string $match_url {
get => $this->get(Items::URL->value);
set (string $match_url) => $this->set(Items::URL->value, substr($match_url, 0, 2000));
}
public string $match_data {
get => $this->get(Items::MATCH_DATA->value);
set (string $match_data) => $this->set(Items::MATCH_DATA->value, $match_data);
}
public bool $regex {
get => $this->get(Items::REGEX->value);
set (bool $regex) => $this->set(Items::REGEX->value, $regex);
}
public int $position {
get => $this->get(Items::POSITION->value);
set (int $position) => $this->set(Items::POSITION->value, $position);
}
public int $last_count {
get => $this->get(Items::LAST_COUNT->value);
set (int $last_count) => $this->set(Items::LAST_COUNT->value, $last_count);
}
public DateTimeImmutable $last_access {
get => new DateTimeImmutable($this->get(Items::LAST_ACCESS->value));
set (DateTimeImmutable $last_access) => $this->set(Items::LAST_ACCESS->value, $last_access->format(static::DATETIME_FORMAT), $last_access);
}
public int $group_id {
get => $this->get(Items::GROUP_ID->value);
set (int $group_id) => $this->set(Items::GROUP_ID->value, $group_id);
}
public StatusEnum $status {
get => StatusEnum::from($this->get(Items::STATUS->value));
set (StatusEnum $status) => $this->set(Items::STATUS->value, $status->value, $status);
}
public string $action_type {
get => $this->get(Items::MATCH_TYPE->value);
set (string $action_type) => $this->set(Items::ACTION_TYPE->value, substr($action_type, 0, 20));
}
public int $action_code {
get => $this->get(Items::ACTION_CODE->value);
set (int $action_code) => $this->set(Items::ACTION_CODE->value, $action_code);
}
public string $action_data {
get => $this->get(Items::ACTION_DATA->value);
set (string $action_data) => $this->set(Items::ACTION_DATA->value, $action_data);
}
public string $match_type {
get => $this->get(Items::MATCH_TYPE->value);
set (string $match_type) => $this->set(Items::MATCH_TYPE->value, substr($match_type, 0, 20));
}
public string $title {
get => $this->get(Items::TITLE->value);
set (string $title) => $this->set(Items::TITLE->value, $title);
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace vlw\WP\Plugins\redirection\Tables;
use vlw\xEnum;
enum StatusEnum: string {
use xEnum;
case ENABLED = "enabled";
case DISABLED = "disabled";
}
enum Items: string {
use xEnum;
public const TABLE_NAME = "redirection_items";
case ID = "id";
case URL = "url";
case MATCH_URL = "match_url";
case MATCH_DATA = "match_data";
case REGEX = "regex";
case POSITION = "position";
case LAST_COUNT = "last_count";
case LAST_ACCESS = "last_access";
case GROUP_ID = "group_id";
case STATUS = "status";
case ACTION_TYPE = "action_type";
case ACTION_CODE = "action_code";
case ACTION_DATA = "action_data";
case MATCH_TYPE = "match_type";
case TITLE = "title";
}

View file

@ -34,6 +34,27 @@
return $query->num_rows === 1 ? new static($query->fetch_assoc()[Posts::ID->value]) : null;
}
/**
* Create and return a shallow copy of a Post
*
* @param Post $post
* @return static
*/
public static function clone(Post $post): static {
$clone = Post::new($post->post_title);
foreach (Posts::cases() as $column) {
// ID column has to be unique, skip it
if ($column === Posts::ID) {
continue;
}
$clone->{$column->value} = $post->{$column->value};
}
return $clone;
}
/**
* Create a new post
*
@ -47,6 +68,9 @@
return self::from_name($title);
}
// Current auto increment value will be the id for this entity
$id = Database::current()->latest(Posts::TABLE_NAME);
$values = [
Posts::ID->value => null,
Posts::POST_AUTHOR->value => 0,
@ -59,7 +83,7 @@
Posts::COMMENT_STATUS->value => "closed",
Posts::PING_STATUS->value => "closed",
Posts::POST_PASSWORD->value => "",
Posts::POST_NAME->value => slugify($title),
Posts::POST_NAME->value => substr(slugify($title), 0, 200),
Posts::TO_PING->value => "",
Posts::PINGED->value => "",
Posts::POST_MODIFIED->value => date(static::DATETIME_FORMAT),
@ -77,7 +101,7 @@
throw new Exception("Failed to create database entity");
}
return self::from_name($title);
return new static($id);
}
public function __construct(public readonly int $id) {
@ -88,6 +112,116 @@
);
}
public int $post_author {
get => $this->get(Posts::POST_AUTHOR->value);
set (int $post_author) => $this->set(Posts::POST_AUTHOR->value, $post_author);
}
public DateTimeImmutable $post_date {
get => new DateTimeImmutable($this->get(Posts::POST_DATE->value));
set (DateTimeImmutable $post_date) => $this->set(Posts::POST_DATE->value, $post_date->format(static::DATETIME_FORMAT), $post_date);
}
public DateTimeImmutable $post_date_gmt {
get => new DateTimeImmutable($this->get(Posts::POST_DATE_GMT->value));
set (DateTimeImmutable $post_date_gmt) => $this->set(Posts::POST_DATE_GMT->value, $post_date_gmt->format(static::DATETIME_FORMAT), $post_date_gmt);
}
public string $post_content {
get => $this->get(Posts::POST_CONTENT->value);
set (string $post_content) => $this->set(Posts::POST_CONTENT->value, $post_content);
}
public string $post_title {
get => $this->get(Posts::POST_TITLE->value);
set (string $post_title) => $this->set(Posts::POST_TITLE->value, $post_title);
}
public string $post_excerpt {
get => $this->get(Posts::POST_EXCERPT->value);
set (string $post_excerpt) => $this->set(Posts::POST_EXCERPT->value, $post_excerpt);
}
public string $post_status {
get => $this->get(Posts::POST_STATUS->value);
set (string $post_status) => $this->set(Posts::POST_STATUS->value, substr($post_status, 0, 20));
}
public string $comment_status {
get => $this->get(Posts::COMMENT_STATUS->value);
set (string $comment_status) => $this->set(Posts::COMMENT_STATUS->value, substr($comment_status, 0, 20));
}
public string $ping_status {
get => $this->get(Posts::PING_STATUS->value);
set (string $ping_status) => $this->set(Posts::PING_STATUS->value, substr($ping_status, 0, 20));
}
public string $post_password {
get => $this->get(Posts::POST_PASSWORD->value);
set (string $post_password) => $this->set(Posts::POST_PASSWORD->value, substr($post_password, 0, 255));
}
public string $post_name {
get => $this->get(Posts::POST_NAME->value);
set (string $post_name) => $this->set(Posts::POST_NAME->value, substr($post_name, 0, 200));
}
public string $to_ping {
get => $this->get(Posts::TO_PING->value);
set (string $to_ping) => $this->set(Posts::TO_PING->value, $to_ping);
}
public string $pinged {
get => $this->get(Posts::PINGED->value);
set (string $pinged) => $this->set(Posts::PINGED->value, $pinged);
}
public DateTimeImmutable $post_modified {
get => new DateTimeImmutable($this->get(Posts::POST_MODIFIED->value));
set (DateTimeImmutable $post_modified) => $this->set(Posts::POST_MODIFIED->value, $post_modified->format(static::DATETIME_FORMAT), $post_modified);
}
public DateTimeImmutable $post_modified_gmt {
get => new DateTimeImmutable($this->get(Posts::POST_MODIFIED_GMT->value));
set (DateTimeImmutable $post_modified_gmt) => $this->set(Posts::POST_MODIFIED_GMT->value, $post_modified_gmt->format(static::DATETIME_FORMAT), $post_modified_gmt);
}
public string $post_content_filtered {
get => $this->get(Posts::POST_CONTENT_FILTERED->value);
set (string $post_content_filtered) => $this->set(Posts::POST_CONTENT_FILTERED->value, $post_content_filtered);
}
public ?Post $post_parent {
get => $this->get(Posts::POST_PARENT->value) !== 0 ? new self($this->get(Posts::POST_PARENT->value)) : null;
set (?Post $post_parent) => $this->set(Posts::POST_PARENT->value, $post_parent ? $post_parent->id : 0, $post_parent);
}
public string $guid {
get => $this->get(Posts::GUID->value);
set (string $guid) => $this->set(Posts::GUID->value, $guid);
}
public int $menu_order {
get => $this->get(Posts::MENU_ORDER->value);
set (int $menu_order) => $this->set(Posts::MENU_ORDER->value, $menu_order);
}
public string $post_type {
get => $this->get(Posts::POST_TYPE->value);
set (string $post_type) => $this->set(Posts::POST_TYPE->value, substr($post_type, 0, 20));
}
public string $post_mime_type {
get => $this->get(Posts::POST_MIME_TYPE->value);
set (string $post_mime_type) => $this->set(Posts::POST_MIME_TYPE->value, substr($post_mime_type, 0, 100));
}
public int $comment_count {
get => $this->get(Posts::COMMENT_COUNT->value);
set (int $comment_count) => $this->set(Posts::COMMENT_COUNT->value, $comment_count);
}
/**
* Get post meta fields for this Post. An array of all post meta is returned if no $key is provided
*
@ -149,109 +283,4 @@
return Attachment::from_post_featured($this);
}
public int $post_author {
get => $this->get(Posts::POST_AUTHOR->value);
set (int $post_author) => $this->set(Posts::POST_AUTHOR->value, $post_author);
}
public DateTimeImmutable $post_date {
get => new DateTimeImmutable($this->get(Posts::POST_DATE->value));
set (DateTimeImmutable $post_date) => $this->set(Posts::POST_DATE->value, $post_date->format(static::DATETIME_FORMAT));
}
public DateTimeImmutable $post_date_gmt {
get => new DateTimeImmutable($this->get(Posts::POST_DATE_GMT->value));
set (DateTimeImmutable $post_date_gmt) => $this->set(Posts::POST_DATE_GMT->value, $post_date_gmt->format(static::DATETIME_FORMAT));
}
public string $post_content {
get => $this->get(Posts::POST_CONTENT->value);
set (string $post_content) => $this->set(Posts::POST_CONTENT->value, $post_content);
}
public string $post_title {
get => $this->get(Posts::POST_TITLE->value);
set (string $post_title) => $this->set(Posts::POST_TITLE->value, $post_title);
}
public string $post_excerpt {
get => $this->get(Posts::POST_EXCERPT->value);
set (string $post_excerpt) => $this->set(Posts::POST_EXCERPT->value, $post_excerpt);
}
public string $post_status {
get => $this->get(Posts::POST_STATUS->value);
set (string $post_status) => $this->set(Posts::POST_STATUS->value, $post_status);
}
public string $comment_status {
get => $this->get(Posts::COMMENT_STATUS->value);
set (string $comment_status) => $this->set(Posts::COMMENT_STATUS->value, $comment_status);
}
public string $ping_status {
get => $this->get(Posts::PING_STATUS->value);
set (string $ping_status) => $this->set(Posts::PING_STATUS->value, $ping_status);
}
public string $post_password {
get => $this->get(Posts::POST_PASSWORD->value);
set (string $post_password) => $this->set(Posts::POST_PASSWORD->value, $post_password);
}
public string $post_name {
get => $this->get(Posts::POST_NAME->value);
set (string $post_name) => $this->set(Posts::POST_NAME->value, $post_name);
}
public string $to_ping {
get => $this->get(Posts::TO_PING->value);
set (string $to_ping) => $this->set(Posts::TO_PING->value, $to_ping);
}
public string $pinged {
get => $this->get(Posts::PINGED->value);
set (string $pinged) => $this->set(Posts::PINGED->value, $pinged);
}
public DateTimeImmutable $post_modified {
get => new DateTimeImmutable($this->get(Posts::POST_MODIFIED->value));
set (DateTimeImmutable $post_modified) => $this->set(Posts::POST_MODIFIED->value, $post_modified->format(static::DATETIME_FORMAT));
}
public DateTimeImmutable $post_modified_gmt {
get => new DateTimeImmutable($this->get(Posts::POST_MODIFIED_GMT->value));
set (DateTimeImmutable $post_modified_gmt) => $this->set(Posts::POST_MODIFIED_GMT->value, $post_modified_gmt->format(static::DATETIME_FORMAT));
}
public Post|false|null $post_parent {
get => $this->get(Posts::POST_PARENT->value) !== 0 ? new self($this->get(Posts::POST_PARENT->value)) : null;
set (Post|false|null $post_parent) => $this->set(Posts::POST_PARENT->value, $post_parent ? $post_parent->id : 0);
}
public string $guid {
get => $this->get(Posts::GUID->value);
set (string $guid) => $this->set(Posts::GUID->value, $guid);
}
public int $menu_order {
get => $this->get(Posts::MENU_ORDER->value);
set (int $menu_order) => $this->set(Posts::MENU_ORDER->value, $menu_order);
}
public string $post_type {
get => $this->get(Posts::POST_TYPE->value);
set (string $post_type) => $this->set(Posts::POST_TYPE->value, $post_type);
}
public string $post_mime_type {
get => $this->get(Posts::POST_MIME_TYPE->value);
set (string $post_mime_type) => $this->set(Posts::POST_MIME_TYPE->value, $post_mime_type);
}
public int $comment_count {
get => $this->get(Posts::COMMENT_COUNT->value);
set (int $comment_count) => $this->set(Posts::COMMENT_COUNT->value, $comment_count);
}
}

View file

@ -64,6 +64,9 @@
return $model;
}
// Current auto increment value will be the id for this entity
$id = Database::current()->latest(PostMetaTable::TABLE_NAME);
$values = [
PostMetaTable::META_ID->value => null,
PostMetaTable::POST_ID->value => $post->id,
@ -75,7 +78,7 @@
throw new Exception("Failed to create database entity");
}
return new static(self::get_post_meta($post, $meta_key)->id);
return new static($id);
}
public function __construct(public readonly int $id) {
@ -86,6 +89,21 @@
);
}
public Post $post {
get => new Post($this->get(PostMetaTable::POST_ID->value));
set (Post $post) => $this->set(PostMetaTable::POST_ID->value, $post->id);
}
public string $meta_key {
get => $this->get(PostMetaTable::META_KEY->value);
set (string $meta_key) => $this->set(PostMetaTable::META_KEY->value, substr($meta_key, 0, 255));
}
public ?string $meta_value {
get => $this->get(PostMetaTable::META_VALUE->value);
set (?string $meta_value) => $this->set(PostMetaTable::META_VALUE->value, $meta_value);
}
/**
* Delete this post meta field from the database
*
@ -96,19 +114,4 @@
->from(Database::get_table(PostMetaTable::TABLE_NAME))
->delete([PostMetaTable::META_ID->value => $this->id]);
}
public Post $post {
get => new Post($this->get(PostMetaTable::POST_ID->value));
set (Post $post) => $this->set(PostMetaTable::POST_ID->value, $post->id);
}
public string $meta_key {
get => $this->get(PostMetaTable::META_KEY->value);
set (string $meta_key) => $this->set(PostMetaTable::META_KEY->value, $meta_key);
}
public ?string $meta_value {
get => $this->get(PostMetaTable::META_VALUE->value);
set (?string $meta_value) => $this->set(PostMetaTable::META_VALUE->value, $meta_value);
}
}

View file

@ -51,7 +51,7 @@
$values = [
Taxonomies::TERM_TAXONOMY_ID->value => null,
Taxonomies::TERM_ID->value => $term->id,
Taxonomies::TAXONOMY->value => $taxonomy,
Taxonomies::TAXONOMY->value => substr($taxonomy, 0, 32),
Taxonomies::DESCRIPTION->value => "",
Taxonomies::PARENT->value => 0,
Taxonomies::COUNT->value => 0
@ -80,7 +80,7 @@
public string $taxonomy {
get => $this->get(Taxonomies::TAXONOMY->value);
set (string $taxonomy) => $this->set(Taxonomies::TAXONOMY->value, $taxonomy);
set (string $taxonomy) => $this->set(Taxonomies::TAXONOMY->value, substr($taxonomy, 0, 32));
}
public string $description {

View file

@ -62,8 +62,8 @@
$values = [
Terms::TERM_ID->value => null,
Terms::NAME->value => $name,
Terms::SLUG->value => $slug ? $slug : slugify($name),
Terms::NAME->value => substr($name, 0, 200),
Terms::SLUG->value => substr($slug ? $slug : slugify($name), 0, 200),
Terms::TERM_GROUP->value => 0
];
@ -84,12 +84,12 @@
public string $name {
get => $this->get(Terms::NAME->value);
set (string $name) => $this->set(Terms::NAME->value, $name);
set (string $name) => $this->set(Terms::NAME->value, substr($name, 0, 200));
}
public string $slug {
get => $this->get(Terms::SLUG->value);
set (string $slug) => $this->set(Terms::SLUG->value, $slug);
set (string $slug) => $this->set(Terms::SLUG->value, substr($slug, 0, 200));
}
public int $term_group {

View file

@ -2,19 +2,27 @@
namespace vlw\WP\Posts\Type;
use Exception;
use vlw\MimeTypes\MimeTypes;
use vlw\WP\Database;
use vlw\WP\Posts\Post;
use vlw\WP\Tables\Posts;
use vlw\WP\Posts\PostMeta;
use function vlw\WP\Support\slugify;
require_once dirname(__DIR__, 1) . "/Post.php";
require_once dirname(__DIR__, 1) . "/PostMeta.php";
require_once dirname(__DIR__, 2) . "/Support/Slugify.php";
require_once dirname(__DIR__, 2) . "/Tables/Posts.php";
class Attachment {
private const META_KEY_THUMBNAIL_ID = "_thumbnail_id";
private const META_KEY_FEATURED_MEDIA = "_featured_media";
private const POST_TYPE = "attachment";
private const META_KEY_THUMBNAIL_ID = "_thumbnail_id";
private const META_KEY_FEATURED_MEDIA = "_featured_media";
private const META_KEY_WP_ATTACHED_FILE = "_wp_attached_file";
private readonly Post $post;
public private(set) readonly Post $post;
/**
* Return featured media for a Post if it exists
@ -24,24 +32,23 @@
* @return Attachment|null
*/
public static function from_post_featured(Post $post, bool $thumbnail = true): ?Attachment {
// Check the thumbnail for an attachment ID. This operation is slightly less demanding than unserialize
if ($thumbnail) {
$post_meta = PostMeta::get_post_meta($post, self::META_KEY_THUMBNAIL_ID);
// We didn't find any post meta, let's search the post table for an attachment with this Post as its parent
$query = Database::current()
->from(Database::get_table("posts"))
->where([
Posts::POST_PARENT->value => $post->id,
Posts::POST_TYPE->value => self::POST_TYPE
])
->limit(1)
->select(Posts::ID->value);
if ($post_meta) {
return new static($post_meta->id);
}
}
$post_meta = PostMeta::get_post_meta($post, self::META_KEY_FEATURED_MEDIA);
// Bail out, the target Post does not have any featured media
if (!$post_meta) {
// Bail out, we didn't find any valid featured media for this Post
if ($query->num_rows !== 1) {
return null;
}
// New instance from post ID in featured media serialized object
return new static((int) unserialize($post_meta->meta_value)[0]);
// We found an attachment
return new static($query->fetch_assoc()[Posts::ID->value]);
}
/**
@ -73,6 +80,7 @@
$post->post_title = slugify($title);
$post->guid = $url;
$post->post_status = "inherit";
$post->post_type = "attachment";
return new static($post->id);
@ -82,20 +90,45 @@
$this->post = new Post($id);
}
public string|false $content {
get => file_get_contents($this->post->guid);
}
/**
* Set the pathname for this Attachemnt to a file on disk
*
* @param string $pathname
* @return void
*/
public function set_attached_file(string $pathname): void {
PostMeta::new($this->post, self::META_KEY_WP_ATTACHED_FILE, $pathname);
}
/**
* Set Attachemnt data from a file on disk
*
* @param string $file
* @return void
*/
public function set_from_file(string $file): void {
if (!is_file($file)) {
throw new Exception("No file found at location '{$file}'");
}
$this->post->post_mime_type = new MimeTypes()->get_type_from_file($file);
$this->set_attached_file($file);
}
/**
* Make this Attachment the featured media of a Post
*
* @param Post $post
* @return object
*/
public function set_post_featured(Post $post): object {
return (object) [
"thumbnail" => PostMeta::new($post, self::META_KEY_THUMBNAIL_ID, $this->id),
"featured_media" => PostMeta::new($post, self::META_KEY_FEATURED_MEDIA, "a:1:{i:0;s:3:\"{$this->id}\";}")
];
}
public function set_post_featured(Post $post): void {
PostMeta::new($post, self::META_KEY_THUMBNAIL_ID, $this->id);
PostMeta::new($post, self::META_KEY_FEATURED_MEDIA, "a:1:{i:0;s:3:\"{$this->id}\";}");
public string|false $content {
get => file_get_contents($this->post->guid);
$this->post->post_parent = $post;
}
}

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

@ -0,0 +1,16 @@
<?php
namespace vlw\WP\Tables;
use vlw\xEnum;
enum UserMeta: string {
use xEnum;
public const TABLE_NAME = "usermeta";
case UMETA_ID = "umeta_id";
case USER_ID = "user_id";
case META_KEY = "meta_key";
case META_VALUE = "meta_value";
}

22
src/Tables/Users.php Normal file
View file

@ -0,0 +1,22 @@
<?php
namespace vlw\WP\Tables;
use vlw\xEnum;
enum Users: string {
use xEnum;
public const TABLE_NAME = "users";
case ID = "id";
case USER_LOGIN = "user_login";
case USER_PASS = "user_pass";
case USER_NICENAME = "user_nicename";
case USER_EMAIL = "user_email";
case USER_URL = "user_url";
case USER_REGISTERED = "user_registered";
case USER_ACTIVATION_KEY = "user_activation_key";
case USER_STATUS = "user_status";
case DISPLAY_NAME = "display_name";
}

162
src/Users/User.php Normal file
View file

@ -0,0 +1,162 @@
<?php
namespace vlw\WP\Users;
use Exception;
use DateTimeImmutable;
use vlw\Scaffold\Database\Model;
use vlw\WP\Database;
use vlw\WP\Tables\Users;
use function vlw\WP\Support\slugify;
require_once dirname(__DIR__, 1) . "/Tables/Users.php";
require_once dirname(__DIR__, 1) . "/Support/Slugify.php";
class User extends Model {
/**
* Return a User for a given login if that user exists
*
* @param string $email
* @return static|null
*/
public static function from_login(string $login): ?static {
$query = Database::current()
->from(Database::get_table(Users::TABLE_NAME))
->where([Users::USER_LOGIN->value => $login])
->limit(1)
->select(Users::ID->value);
return $query->num_rows === 1 ? new static($query->fetch_assoc()[Users::ID->value]) : null;
}
/**
* Return a User for a given email if that user exists
*
* @param string $email
* @return static|null
*/
public static function from_email(string $email): ?static {
$query = Database::current()
->from(Database::get_table(Users::TABLE_NAME))
->where([Users::USER_EMAIL->value => $email])
->limit(1)
->select(Users::ID->value);
return $query->num_rows === 1 ? new static($query->fetch_assoc()[Users::ID->value]) : null;
}
/**
* Create a new user
*
* @param string $title
* @param string $type
* @return static
*/
public static function new(string $login, string $email, ?string $display_name = null): static {
// Bail out with existing User for $login if exists
if (static::from_login($login)) {
return static::from_login($login);
}
// Bail out with existing User for $email if exists
if (static::from_email($email)) {
return static::from_email($email);
}
// Current auto increment value will be the id for this entity
$id = Database::current()->latest(Users::TABLE_NAME);
$values = [
Users::ID->value => null,
Users::USER_LOGIN->value => $login,
Users::USER_PASS->value => "",
Users::USER_NICENAME->value => slugify($login),
Users::USER_EMAIL->value => $email,
Users::USER_URL->value => "",
Users::USER_REGISTERED->value => date(static::DATETIME_FORMAT),
Users::USER_ACTIVATION_KEY->value => "",
Users::USER_STATUS->value => 0,
Users::DISPLAY_NAME->value => $display_name ?? $login
];
if (!parent::create(Database::get_table(Users::TABLE_NAME), $values)) {
throw new Exception("Failed to create database entity");
}
return new static($id);
}
public function __construct(public readonly int $id) {
parent::__construct(
Database::get_table(Users::TABLE_NAME),
Users::values(),
[Users::ID->value => (int) $id]
);
}
public string $user_login {
get => $this->get(Users::USER_LOGIN->value);
set (string $user_login) => $this->set(Users::USER_LOGIN->value, substr($user_login, 0, 60));
}
public string $user_pass {
get => $this->get(Users::USER_PASS->value);
set (string $user_pass) => $this->set(Users::USER_PASS->value, substr($user_pass, 0, 255));
}
public string $user_nicename {
get => $this->get(Users::USER_NICENAME->value);
set (string $user_nicename) => $this->set(Users::USER_NICENAME->value, substr($user_nicename, 0, 50));
}
public string $user_email {
get => $this->get(Users::USER_EMAIL->value);
set (string $user_email) => $this->set(Users::USER_EMAIL->value, substr($user_email, 0, 100));
}
public string $user_url {
get => $this->get(Users::USER_URL->value);
set (string $user_url) => $this->set(Users::USER_URL->value, substr($user_url, 0, 100));
}
public DateTimeImmutable $user_registered {
get => new DateTimeImmutable($this->get(Users::USER_REGISTERED->value));
set (DateTimeImmutable $user_registered) => $this->set(Users::USER_REGISTERED->value, $user_registered->format(static::DATETIME_FORMAT), $user_registered);
}
public string $user_activation_key {
get => $this->get(Users::USER_ACTIVATION_KEY->value);
set (string $user_activation_key) => $this->set(Users::USER_ACTIVATION_KEY->value, substr($user_activation_key, 0, 255));
}
public int $user_status {
get => $this->get(Users::USER_STATUS->value);
set (int $user_status) => $this->set(Users::USER_STATUS->value, $user_status);
}
public string $display_name {
get => $this->get(Users::DISPLAY_NAME->value);
set (string $display_name) => $this->set(Users::DISPLAY_NAME->value, substr($display_name, 0, 250));
}
/**
* Set the password for this User
*
* @param string $password
* @return void
*/
public function set_password(string $password): void {
$this->user_pass = password_hash($password, PASSWORD_DEFAULT);
}
/**
* Check if the password for this User is $password
*
* @param string $password
* @return bool
*/
public function validate_password(string $password): bool {
return password_verify($password, $this->user_pass);
}
}

98
src/Users/UserMeta.php Normal file
View file

@ -0,0 +1,98 @@
<?php
namespace vlw\WP\Users;
use Exception;
use vlw\Scaffold\Database\Model;
use vlw\WP\Database;
use vlw\WP\Users\User;
use vlw\WP\Tables\UserMeta as UserMetaTable;
require_once "User.php";
require_once dirname(__DIR__, 1) . "/Tables/UserMeta.php";
require_once dirname(__DIR__, 1) . "/Support/Slugify.php";
class UserMeta extends Model {
/**
* Get a user meta value for a User and a user meta key
*
* @param string $name
* @return static|false
*/
public static function get_user_meta(User $user, string $key): static|false {
$query = Database::current()
->from(Database::get_table(UserMetaTable::TABLE_NAME))
->where([
UserMetaTable::USER_ID->value => $user->id,
UserMetaTable::META_KEY->value => $key
])
->limit(1)
->select(UserMetaTable::UMETA_ID->value);
return $query->num_rows === 1 ? new static($query->fetch_assoc()[UserMetaTable::UMETA_ID->value]) : false;
}
/**
* Get all user meta entries for a given User
*
* @param string $name
* @return array
*/
public static function get_all_user_meta(User $user): array {
$query = Database::current()
->from(Database::get_table(UserMetaTable::TABLE_NAME))
->where([UserMetaTable::USER_ID->value => $user->id])
->select(UserMetaTable::UMETA_ID->value);
return array_map(fn(array $post_meta): static => new static($post_meta[UserMetaTable::UMETA_ID->value]), $query->fetch_all(MYSQLI_ASSOC));
}
/**
* Create a new user
*
* @param string $title
* @param string $type
* @return static
*/
public static function new(User $user, ?string $meta_key = null, ?string $meta_value = null): static {
// Current auto increment value will be the id for this entity
$id = Database::current()->latest(UserMetaTable::TABLE_NAME);
$values = [
UserMetaTable::UMETA_ID->value => null,
UserMetaTable::USER_ID->value => $user->id,
UserMetaTable::META_KEY->value => $meta_key,
UserMetaTable::META_VALUE->value => $meta_value
];
if (!parent::create(Database::get_table(UserMetaTable::TABLE_NAME), $values)) {
throw new Exception("Failed to create database entity");
}
return new static($id);
}
public function __construct(public readonly int $id) {
parent::__construct(
Database::get_table(UserMetaTable::TABLE_NAME),
UserMetaTable::values(),
[UserMetaTable::UMETA_ID->value => (int) $id]
);
}
public User $user {
get => new User($this->get(UserMetaTable::USER_ID->value));
set (User $user) => $this->set(UserMetaTable::USER_ID->value, $user->id, $user);
}
public string $meta_key {
get => $this->get(UserMetaTable::META_KEY->value);
set (string $meta_key) => $this->set(UserMetaTable::META_KEY->value, substr($meta_key, 0, 255));
}
public string $meta_value {
get => $this->get(UserMetaTable::META_VALUE->value);
set (string $meta_value) => $this->set(UserMetaTable::META_VALUE->value, $meta_value);
}
}