Compare commits

...

16 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
4ca9f8be5e feat: add method for querying the current AUTO_INCREMENT for a table (#5)
This will be used to retrieve the primary key for newly inserted rows.

Reviewed-on: https://codeberg.org/vlw/wp/pulls/5
2026-02-16 11:27:30 +01:00
28f31250fa fix: proper table reference for PostMeta (#7)
Reviewed-on: https://codeberg.org/vlw/wp/pulls/7
2026-02-16 11:27:16 +01:00
a70015e314 feat: improved creation and assignment of post taxonomies and terms (#6)
With this PR we can (among actually creating taxonomies and terms) assign and remove terms from a post with the following
```php
$post = new Post(123);
$term = new Term(321);
```
Add a term to a post:
```php
$post->add_term($term);
```
Remove a term from a post:
```php
$post->remove_term($term);
```

Reviewed-on: https://codeberg.org/vlw/wp/pulls/6
2026-02-16 11:26:58 +01:00
b14c14d9b3 feat: add methods for Post featured media Attachments (#4)
In this PR we add methods for setting and getting featured media attachments for posts.

Reviewed-on: https://codeberg.org/vlw/wp/pulls/4
2026-02-15 11:52:29 +01:00
ff9b5ca42c
fix: pass static database credentials for Database::current() (#3) 2026-02-15 10:10:26 +01:00
eebe93fc53 feat: add parsing of WordPress taxonomies and terms (#2)
Reviewed-on: https://codeberg.org/vlw/wp/pulls/2
2026-02-15 09:51:02 +01:00
6d1fa852b5 fix: import Database from scaffolding lib and remove instances from .env.ini (#1)
This PR fixes instancing issues with `Database` due to not being able to locate its parent class from the scaffolding library. This is definitely kind of strange and should probably be investigated further. It might be because we're not autoloading these files from the library itself.

We also remove loading of Database credentials from `.env.ini` as that does not make any sense for a bundled library. Database credentials are now provided to the constructor of the `Database` class.

Reviewed-on: https://codeberg.org/vlw/wp/pulls/1
2026-02-12 15:27:00 +01:00
22 changed files with 1091 additions and 66 deletions

View file

@ -1,4 +0,0 @@
[mariadb]
host = ""
user = ""
pass = ""

2
.gitignore vendored
View file

@ -1,3 +1 @@
.env.ini
vendor vendor

View file

@ -21,3 +21,9 @@ $post->post_content;
// Update the datetime modified for this post // Update the datetime modified for this post
$post->post_modified = new DateTimeImmutable(); $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", "name": "vlw/scaffold",
"version": "1.7.0", "version": "1.7.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://codeberg.org/vlw/scaffold", "url": "https://codeberg.org/vlw/scaffold",
"reference": "1536079fe384c0cdc8b3814b67318140dbc339ff" "reference": "ffd809f76dd50d7a6ee631f21715dca40ec40f44"
}, },
"require": { "require": {
"vlw/mysql": "3.5.*" "vlw/mysql": "3.5.*"
@ -61,7 +61,7 @@
} }
], ],
"description": "Project scaffolding (primarily) for Reflect and Vegvisir projects", "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", "name": "vlw/xenum",

View file

@ -5,11 +5,15 @@
use Exception; use Exception;
use vlw\Scaffold\Database as DatabaseFramework; use vlw\Scaffold\Database as DatabaseFramework;
require_once dirname(__DIR__, 2) . "/scaffold/src/Database/Database.php";
class Database extends DatabaseFramework { class Database extends DatabaseFramework {
public static string $name = ""; public static string $name = "";
public static string $site_url = "";
private static string $prefix = "wp"; private static string $prefix = "wp";
private static string $hostname = "";
private static string $username = "";
private static string $password = "";
/** /**
* Get a prefixed table name from the database * Get a prefixed table name from the database
@ -27,7 +31,7 @@
* @return static * @return static
*/ */
public static function current(): static { public static function current(): static {
return new static(self::$name, self::$prefix); return static::instance();
} }
/** /**
@ -36,9 +40,17 @@
* @param string $database * @param string $database
* @param string|null $table_prefix * @param string|null $table_prefix
*/ */
public function __construct(string $database, ?string $table_prefix = null) { public function __construct(
$env = parse_ini_file(dirname(__DIR__, 1) . "/.env.ini", process_sections: true); string $hostname,
string $username,
string $password,
string $database,
?string $table_prefix = null
) {
self::$hostname = $hostname;
self::$username = $username;
self::$password = $password;
self::$password = $password;
self::$name = $database; self::$name = $database;
if ($table_prefix) { if ($table_prefix) {
@ -46,23 +58,34 @@
} }
Database::set_credentials( Database::set_credentials(
$env["mariadb"]["host"], $hostname,
$env["mariadb"]["user"], $username,
$env["mariadb"]["pass"], $password,
self::$name $database
); );
$this->set_site_url();
parent::__construct(); parent::__construct();
} }
/** /**
* Fetch and set the WordPress siteurl from the options table * Return the current AUTO_INCREMENT value for a given table
*
* @param string $table
* @return int
*/
public function latest(string $table): int {
return $this->execute_query("SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?", [
self::$name,
self::get_table($table)
])->fetch_assoc()["AUTO_INCREMENT"];
}
/**
* Fetch the WordPress siteurl from the options table
* *
* @return void * @return void
*/ */
public function set_site_url() { public function site_url() {
$query = new DatabaseFramework() $query = new DatabaseFramework()
->from(static::$prefix . "_options") ->from(static::$prefix . "_options")
->where(["option_name" => "siteurl"]) ->where(["option_name" => "siteurl"])
@ -73,6 +96,6 @@
throw new Exception("Failed to fetch siteurl from options table"); 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

@ -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,16 +24,37 @@
* @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);
return $query->num_rows === 1 ? new static($query->fetch_assoc()[Posts::ID->value]) : null; 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 * Create a new post
* *
@ -44,6 +68,9 @@
return self::from_name($title); return self::from_name($title);
} }
// Current auto increment value will be the id for this entity
$id = Database::current()->latest(Posts::TABLE_NAME);
$values = [ $values = [
Posts::ID->value => null, Posts::ID->value => null,
Posts::POST_AUTHOR->value => 0, Posts::POST_AUTHOR->value => 0,
@ -56,7 +83,7 @@
Posts::COMMENT_STATUS->value => "closed", Posts::COMMENT_STATUS->value => "closed",
Posts::PING_STATUS->value => "closed", Posts::PING_STATUS->value => "closed",
Posts::POST_PASSWORD->value => "", Posts::POST_PASSWORD->value => "",
Posts::POST_NAME->value => slugify($title), Posts::POST_NAME->value => substr(slugify($title), 0, 200),
Posts::TO_PING->value => "", Posts::TO_PING->value => "",
Posts::PINGED->value => "", Posts::PINGED->value => "",
Posts::POST_MODIFIED->value => date(static::DATETIME_FORMAT), Posts::POST_MODIFIED->value => date(static::DATETIME_FORMAT),
@ -70,31 +97,21 @@
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");
} }
return self::from_name($title); return new static($id);
} }
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]
); );
} }
/**
* Get post meta fields for this Post. An array of all post meta is returned if no $key is provided
*
* @param string|null $key Return the value of this post meta key. Null for all post meta fields
* @return string|array|null Returns a string or null if a single key is selected. Array if all post meta fields are returned
*/
public function meta(?string $key = null): string|array|null {
return $key ? PostMeta::get_post_meta($this, $key) : PostMeta::get_all_post_meta($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);
@ -102,12 +119,12 @@
public DateTimeImmutable $post_date { public DateTimeImmutable $post_date {
get => new DateTimeImmutable($this->get(Posts::POST_DATE->value)); 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)); set (DateTimeImmutable $post_date) => $this->set(Posts::POST_DATE->value, $post_date->format(static::DATETIME_FORMAT), $post_date);
} }
public DateTimeImmutable $post_date_gmt { public DateTimeImmutable $post_date_gmt {
get => new DateTimeImmutable($this->get(Posts::POST_DATE_GMT->value)); 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)); 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 { public string $post_content {
@ -127,27 +144,27 @@
public string $post_status { public string $post_status {
get => $this->get(Posts::POST_STATUS->value); get => $this->get(Posts::POST_STATUS->value);
set (string $post_status) => $this->set(Posts::POST_STATUS->value, $post_status); set (string $post_status) => $this->set(Posts::POST_STATUS->value, substr($post_status, 0, 20));
} }
public string $comment_status { public string $comment_status {
get => $this->get(Posts::COMMENT_STATUS->value); get => $this->get(Posts::COMMENT_STATUS->value);
set (string $comment_status) => $this->set(Posts::COMMENT_STATUS->value, $comment_status); set (string $comment_status) => $this->set(Posts::COMMENT_STATUS->value, substr($comment_status, 0, 20));
} }
public string $ping_status { public string $ping_status {
get => $this->get(Posts::PING_STATUS->value); get => $this->get(Posts::PING_STATUS->value);
set (string $ping_status) => $this->set(Posts::PING_STATUS->value, $ping_status); set (string $ping_status) => $this->set(Posts::PING_STATUS->value, substr($ping_status, 0, 20));
} }
public string $post_password { public string $post_password {
get => $this->get(Posts::POST_PASSWORD->value); get => $this->get(Posts::POST_PASSWORD->value);
set (string $post_password) => $this->set(Posts::POST_PASSWORD->value, $post_password); set (string $post_password) => $this->set(Posts::POST_PASSWORD->value, substr($post_password, 0, 255));
} }
public string $post_name { public string $post_name {
get => $this->get(Posts::POST_NAME->value); get => $this->get(Posts::POST_NAME->value);
set (string $post_name) => $this->set(Posts::POST_NAME->value, $post_name); set (string $post_name) => $this->set(Posts::POST_NAME->value, substr($post_name, 0, 200));
} }
public string $to_ping { public string $to_ping {
@ -162,17 +179,22 @@
public DateTimeImmutable $post_modified { public DateTimeImmutable $post_modified {
get => new DateTimeImmutable($this->get(Posts::POST_MODIFIED->value)); 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)); set (DateTimeImmutable $post_modified) => $this->set(Posts::POST_MODIFIED->value, $post_modified->format(static::DATETIME_FORMAT), $post_modified);
} }
public DateTimeImmutable $post_modified_gmt { public DateTimeImmutable $post_modified_gmt {
get => new DateTimeImmutable($this->get(Posts::POST_MODIFIED_GMT->value)); 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)); set (DateTimeImmutable $post_modified_gmt) => $this->set(Posts::POST_MODIFIED_GMT->value, $post_modified_gmt->format(static::DATETIME_FORMAT), $post_modified_gmt);
} }
public Post|false|null $post_parent { public string $post_content_filtered {
get => $this->get(Posts::POST_PARENT->value) !== 0 ? new self($this->get(Posts::POST_PARENT->value)) : null; get => $this->get(Posts::POST_CONTENT_FILTERED->value);
set (Post|false|null $post_parent) => $this->set(Posts::POST_PARENT->value, $post_parent ? $post_parent->id : 0); 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 { public string $guid {
@ -187,16 +209,78 @@
public string $post_type { public string $post_type {
get => $this->get(Posts::POST_TYPE->value); get => $this->get(Posts::POST_TYPE->value);
set (string $post_type) => $this->set(Posts::POST_TYPE->value, $post_type); set (string $post_type) => $this->set(Posts::POST_TYPE->value, substr($post_type, 0, 20));
} }
public string $post_mime_type { public string $post_mime_type {
get => $this->get(Posts::POST_MIME_TYPE->value); get => $this->get(Posts::POST_MIME_TYPE->value);
set (string $post_mime_type) => $this->set(Posts::POST_MIME_TYPE->value, $post_mime_type); set (string $post_mime_type) => $this->set(Posts::POST_MIME_TYPE->value, substr($post_mime_type, 0, 100));
} }
public int $comment_count { public int $comment_count {
get => $this->get(Posts::COMMENT_COUNT->value); get => $this->get(Posts::COMMENT_COUNT->value);
set (int $comment_count) => $this->set(Posts::COMMENT_COUNT->value, $comment_count); 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
*
* @param string|null $key Return the value of this post meta key. Null for all post meta fields
* @return string|array|null Returns a string or null if a single key is selected. Array if all post meta fields are returned
*/
public function meta(?string $key = null): string|array|null {
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);
}
/**
* Add a Term to this Post
*
* @param Term $term
* @return void
*/
public function add_term(Term $term): void {
$term->add_to_post($this);
}
/**
* Remove a Term from this Post
*
* @param Term $term
* @return void
*/
public function remove_term(Term $term): void {
$term->remove_from_post($this);
}
/**
* Get, set, or unset featured media for this Post
*
* @param Attachment|false|null $attachment Pass Attachment to set featured media, false to unset, and null (or nothing) to get featured media
* @return Attachment|null Returns an Attachment if featured media is set. Null if no featured media is set
*/
public function featured_media(Attachment|false|null $featured_media = null): ?Attachment {
// Remove featured media from this Post
if ($featured_media === false) {
Attachment::remove_post_featured_media($this);
return null;
}
// Set Attachment as the featured media for this Post
if ($featured_media instanceof Attachment) {
$featured_media->set_post_featured($this);
return $featured_media;
}
return Attachment::from_post_featured($this);
}
} }

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);
@ -64,6 +64,9 @@
return $model; return $model;
} }
// Current auto increment value will be the id for this entity
$id = Database::current()->latest(PostMetaTable::TABLE_NAME);
$values = [ $values = [
PostMetaTable::META_ID->value => null, PostMetaTable::META_ID->value => null,
PostMetaTable::POST_ID->value => $post->id, PostMetaTable::POST_ID->value => $post->id,
@ -71,16 +74,16 @@
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");
} }
return new static(self::get_post_meta($post, $meta_key)->id); return new static($id);
} }
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]
); );
@ -93,11 +96,22 @@
public string $meta_key { public string $meta_key {
get => $this->get(PostMetaTable::META_KEY->value); get => $this->get(PostMetaTable::META_KEY->value);
set (string $meta_key) => $this->set(PostMetaTable::META_KEY->value, $meta_key); set (string $meta_key) => $this->set(PostMetaTable::META_KEY->value, substr($meta_key, 0, 255));
} }
public ?string $meta_value { public ?string $meta_value {
get => $this->get(PostMetaTable::META_VALUE->value); get => $this->get(PostMetaTable::META_VALUE->value);
set (?string $meta_value) => $this->set(PostMetaTable::META_VALUE->value, $meta_value); set (?string $meta_value) => $this->set(PostMetaTable::META_VALUE->value, $meta_value);
} }
/**
* Delete this post meta field from the database
*
* @return void
*/
public function delete(): void {
$this->db
->from(Database::get_table(PostMetaTable::TABLE_NAME))
->delete([PostMetaTable::META_ID->value => $this->id]);
}
} }

View file

@ -0,0 +1,100 @@
<?php
namespace vlw\WP\Posts\Taxonomy;
use Exception;
use vlw\Scaffold\Database\Model;
use vlw\WP\Database;
use vlw\WP\Tables\Taxonomies;
use vlw\WP\Posts\Taxonomy\Term;
require_once "Term.php";
require_once dirname(__DIR__, 2) . "/Tables/Taxonomies.php";
class Taxonomy extends Model {
/**
* Returns a Taxonomy by taxonomy name
*
* @param string $name
* @return array
*/
public static function from(string $taxonomy): array {
$query = Database::current()
->from(Database::get_table(Taxonomies::TABLE_NAME))
->where([Taxonomies::TAXONOMY->value => $taxonomy])
->select(Taxonomies::TERM_TAXONOMY_ID->value);
return array_map(fn(array $taxonomy): static => new static($taxonomy[Taxonomies::TERM_TAXONOMY_ID->value]), $query->fetch_all(MYSQLI_ASSOC));
}
public static function from_term(Term $term): array {
$query = Database::current()
->from(Database::get_table(Taxonomies::TABLE_NAME))
->where([Taxonomies::TERM_ID->value => $term->id])
->select(Taxonomies::TERM_TAXONOMY_ID->value);
return array_map(fn(array $taxonomy): static => new static($taxonomy[Taxonomies::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 $taxonomy, Term $term): static {
// Current auto increment value will be the id for this entity
$id = Database::current()->latest(Taxonomies::TABLE_NAME);
$values = [
Taxonomies::TERM_TAXONOMY_ID->value => null,
Taxonomies::TERM_ID->value => $term->id,
Taxonomies::TAXONOMY->value => substr($taxonomy, 0, 32),
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");
}
// Instance this Taxonomy from the last added taxonomy in the database
return new static($id);
}
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, substr($taxonomy, 0, 32));
}
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);
}
public int $count {
get => (int) $this->get(Taxonomies::COUNT->value);
set (int $count) => $this->set(Taxonomies::COUNT->value, $count);
}
}

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

@ -0,0 +1,177 @@
<?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;
use function vlw\WP\Support\slugify;
require_once dirname(__DIR__, 1) . "/Post.php";
require_once dirname(__DIR__, 2) . "/Tables/Terms.php";
require_once dirname(__DIR__, 2) . "/Support/Slugify.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 {
// Current auto increment value will be the id for this entity
$id = Database::current()->latest(Terms::TABLE_NAME);
$values = [
Terms::TERM_ID->value => null,
Terms::NAME->value => substr($name, 0, 200),
Terms::SLUG->value => substr($slug ? $slug : slugify($name), 0, 200),
Terms::TERM_GROUP->value => 0
];
if (!parent::create(Database::get_table(Terms::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(Terms::TABLE_NAME),
Terms::values(),
[Terms::TERM_ID->value => $id]
);
}
public string $name {
get => $this->get(Terms::NAME->value);
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, substr($slug, 0, 200));
}
public int $term_group {
get => $this->get(Terms::TERM_GROUP->value);
set (int $term_group) => $this->set(Terms::TERM_GROUP->value, $term_group);
}
/**
* Returns an array of Taxonomies that implement this Term
*
* @return array
*/
public function taxonomies(): array {
return Taxonomy::from_term($this);
}
/**
* 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 ($this->post_has_term($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");
}
// Increase the post count for each Taxonomy implementing this Term
foreach ($this->taxonomies() as $taxonomy) {
$taxonomy->count++;
}
}
/**
* Remove this Term from a target Post
*
* @param Post $post
* @return void
*/
public function remove_from_post(Post $post): void {
// Bail out if this Term is not set on the target Post
if (!$this->post_has_term($post)) {
return;
}
$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");
}
// Decrease the post count for each Taxonomy implementing this Term
foreach ($this->taxonomies() as $taxonomy) {
$taxonomy->count--;
}
}
/**
* Returns true if a given Post has this Term
*
* @param Post $post
* @return bool
*/
private function post_has_term(Post $post): bool {
return in_array($this->id, array_column(self::from_post($post), "id"));
}
}

View file

@ -2,14 +2,71 @@
namespace vlw\WP\Posts\Type; namespace vlw\WP\Posts\Type;
use Exception;
use vlw\MimeTypes\MimeTypes;
use vlw\WP\Database;
use vlw\WP\Posts\Post; use vlw\WP\Posts\Post;
use vlw\WP\Tables\Posts;
use vlw\WP\Posts\PostMeta;
use function vlw\WP\Support\slugify; use function vlw\WP\Support\slugify;
require_once dirname(__DIR__, 1) . "/Post.php"; 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) . "/Support/Slugify.php";
require_once dirname(__DIR__, 2) . "/Tables/Posts.php";
class Attachment { class Attachment {
private readonly Post $post; 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";
public private(set) readonly Post $post;
/**
* Return featured media for a Post if it exists
*
* @param Post $post
* @param bool $thumbnail Thumbnail is the same as the featured meta. Default to true
* @return Attachment|null
*/
public static function from_post_featured(Post $post, bool $thumbnail = true): ?Attachment {
// 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);
// Bail out, we didn't find any valid featured media for this Post
if ($query->num_rows !== 1) {
return null;
}
// We found an attachment
return new static($query->fetch_assoc()[Posts::ID->value]);
}
/**
* Remove featured media from a Post
*
* @param Post $post
* @return void
*/
public static function remove_post_featured_media(Post $post): void {
foreach ([
PostMeta::get_post_meta($post, self::META_KEY_THUMBNAIL_ID),
PostMeta::get_post_meta($post, self::META_KEY_FEATURED_MEDIA)
] as $post_meta) {
if ($post_meta) {
$post_meta->delete();
}
}
}
/** /**
* Create a new post * Create a new post
@ -20,8 +77,11 @@
*/ */
public static function new(string $title, string $url): static { public static function new(string $title, string $url): static {
$post = Post::new($title); $post = Post::new($title);
$post->post_title = slugify($title); $post->post_title = slugify($title);
$post->guid = $url; $post->guid = $url;
$post->post_status = "inherit";
$post->post_type = "attachment";
return new static($post->id); return new static($post->id);
} }
@ -30,7 +90,45 @@
$this->post = new Post($id); $this->post = new Post($id);
} }
public function get_attachment(): string { public string|false $content {
return file_get_contents($this->post->guid); 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): 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}\";}");
$this->post->post_parent = $post;
} }
} }

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";
}

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);
}
}