wp/src/Posts/Post.php
Victor Westerlund 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

286 lines
9.9 KiB
PHP

<?php
namespace vlw\WP\Posts;
use Exception;
use DateTimeImmutable;
use vlw\Scaffold\Database\Model;
use vlw\WP\Database;
use vlw\WP\Tables\Posts;
use vlw\WP\Posts\PostMeta;
use vlw\WP\Posts\Taxonomy\Term;
use vlw\WP\Posts\Type\Attachment;
use function vlw\WP\Support\slugify;
require_once "Taxonomy/Term.php";
require_once dirname(__DIR__, 1) . "/Tables/Posts.php";
require_once dirname(__DIR__, 1) . "/Support/Slugify.php";
class Post extends Model {
/**
* Get a Post by post_name
*
* @param string $name
* @return static|null
*/
public static function from_name(string $name, bool $slugify = true): ?static {
$query = Database::current()
->from(Database::get_table(Posts::TABLE_NAME))
->where([Posts::POST_NAME->value => $slugify ? slugify($name) : $name])
->limit(1)
->select(Posts::ID->value);
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
*
* @param string $title Post title
* @param string $type Post type
* @return static
*/
public static function new(string $title): static {
// Return existing post if exists
if (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 = [
Posts::ID->value => null,
Posts::POST_AUTHOR->value => 0,
Posts::POST_DATE->value => date(static::DATETIME_FORMAT),
Posts::POST_DATE_GMT->value => date(static::DATETIME_FORMAT),
Posts::POST_CONTENT->value => "",
Posts::POST_TITLE->value => $title,
Posts::POST_EXCERPT->value => "",
Posts::POST_STATUS->value => "private",
Posts::COMMENT_STATUS->value => "closed",
Posts::PING_STATUS->value => "closed",
Posts::POST_PASSWORD->value => "",
Posts::POST_NAME->value => substr(slugify($title), 0, 200),
Posts::TO_PING->value => "",
Posts::PINGED->value => "",
Posts::POST_MODIFIED->value => date(static::DATETIME_FORMAT),
Posts::POST_MODIFIED_GMT->value => date(static::DATETIME_FORMAT),
Posts::POST_CONTENT_FILTERED->value => "",
Posts::POST_PARENT->value => 0,
Posts::GUID->value => "",
Posts::MENU_ORDER->value => 0,
Posts::POST_TYPE->value => "post",
Posts::POST_MIME_TYPE->value => "",
Posts::COMMENT_COUNT->value => 0
];
if (!parent::create(Database::get_table(Posts::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(Posts::TABLE_NAME),
Posts::values(),
[Posts::ID->value => (int) $id]
);
}
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
*
* @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);
}
}