: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* Abstract for posts repository
* @copyright Copyright (c) 2023, Code Atlantic LLC
if ( ! defined( 'ABSPATH' ) ) {
* Class PUM_Abstract_Repository_Posts
* Interface between WP_Query and our data needs. Essentially a query factory.
abstract class PUM_Abstract_Repository_Posts implements PUM_Interface_Repository {
* WordPress query object.
* Array of hydrated object models.
* Should return a valid post type to test against.
protected function get_post_type() {
* Initialize the repository.
protected function init() {
$this->query = new WP_Query();
$this->reset_strict_query_args();
public function __construct() {
public function default_query_args() {
protected $strict_query_args = [];
* Returns an array of default strict query args that can't be over ridden, such as post type.
protected function default_strict_query_args() {
'post_type' => $this->get_post_type(),
* Returns an array of enforced query args that can't be over ridden, such as post type.
protected function get_strict_query_args() {
return $this->strict_query_args;
* Sets a specific query arg to a strict value.
protected function set_strict_query_arg( $key, $value = null ) {
$this->strict_query_args[ $key ] = $value;
* Returns an array of enforced query args that can't be over ridden, such as post type.
protected function reset_strict_query_args() {
$this->strict_query_args = $this->default_strict_query_args();
return $this->strict_query_args;
protected function _build_wp_query_args( $args = [] ) {
$args = wp_parse_args( $args, $this->default_query_args() );
$args = $this->build_wp_query_args( $args );
return array_merge( $args, $this->get_strict_query_args() );
protected function build_wp_query_args( $args = [] ) {
* @return WP_Post|PUM_Abstract_Model_Post
* @throws \InvalidArgumentException
public function get_item( $id ) {
if ( ! $this->has_item( $id ) ) {
throw new InvalidArgumentException( sprintf( __( 'No %1$s found with id %2$d.', 'popup-maker' ), $this->get_post_type(), $id ) );
return $this->get_model( $id );
* @return PUM_Abstract_Model_Post|\WP_Post
public function get_item_by( $field, $value ) {
$id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE %s = %s", $field, $value ) );
if ( ! $id || ! $this->has_item( $id ) ) {
throw new InvalidArgumentException( sprintf( __( 'No user found with %1$s %2$s.', 'popup-maker' ), $field, $value ) );
return $this->get_model( $id );
public function has_item( $id ) {
return get_post_type( $id ) === $this->get_post_type();
protected function get_args_hash( $args ) {
return md5( serialize( $args ) );
* @return WP_Post[]|PUM_Abstract_Model_Post[]
public function get_items( $args = [] ) {
/** Reset default strict query args. */
$this->reset_strict_query_args();
$args = $this->_build_wp_query_args( $args );
$hash = $this->get_args_hash( $args );
if ( ! isset( $this->cache['queries'][ $hash ] ) ) {
* Initialize a new query and return it.
* This also keeps the query cached for potential later usage via $this->get_last_query();
$this->query->query( $args );
$this->cache['queries'][ $hash ] = (array) $this->query->posts;
$posts = $this->cache['queries'][ $hash ];
* Only convert to models if the model set is valid and not the WP_Post default.
foreach ( $posts as $key => $post ) {
$posts[ $key ] = $this->get_model( $post );
public function count_items( $args = [] ) {
/** Reset default strict query args. */
$this->reset_strict_query_args();
/** Set several strict query arg overrides, no matter what args were passed. */
$this->set_strict_query_arg( 'fields', 'ids' );
$this->set_strict_query_arg( 'posts_per_page', 1 );
/** We don't use $this->query here to avoid returning count queries via $this->>get_last_query(); */
$query = new WP_Query( $this->_build_wp_query_args( $args ) );
return (int) $query->found_posts;
public function get_last_query() {
* Assert that data is valid.
* @throws InvalidArgumentException
* TODO Add better Exceptions via these guides:
* - https://www.brandonsavage.net/using-interfaces-for-exceptions/
* - https://www.alainschlesser.com/structuring-php-exceptions/
* if ( isset( $data['subject'] ) && ! $data['subject'] ) {
* throw new InvalidArgumentException( 'The subject is required.' );
abstract protected function assert_data( $data );
* @return WP_Post|PUM_Abstract_Model_Post
* @throws InvalidArgumentException
public function create_item( $data ) {
$this->assert_data( $data );
$post_id = wp_insert_post(
'post_type' => $this->get_post_type(),
'post_status' => 'publish',
'post_title' => $data['title'],
'post_content' => $data['content'],
'meta_input' => $data['meta_input'],
if ( is_wp_error( $post_id ) ) {
throw new InvalidArgumentException( $post_id->get_error_message() );
return $this->get_item( $post_id );
* @return WP_Post|PUM_Abstract_Model_Post
public function update_item( $id, $data ) {
$this->assert_data( $data );
/** @var WP_Post|PUM_Abstract_Model_Post $original */
$original = $this->get_item( $id );
foreach ( $data as $key => $value ) {
if ( $original->$key === $value ) {
$post_update[ $key ] = $value;
$post_update['post_title'] = $value;
$post_update['post_content'] = $value;
update_post_meta( $id, '_custom_meta_key', $value );
if ( count( $post_update ) ) {
$post_update['ID'] = $id;
wp_update_post( $post_update );
return $this->get_item( $id );
protected function get_post_hash( $post ) {
return md5( serialize( $post ) );
protected function cached_model_exists( $post ) {
return isset( $this->cache['objects'][ $post->ID ] ) && $this->get_post_hash( $post ) === $this->cache['objects'][ $post->ID ]['hash'];
* @return WP_Post|PUM_Abstract_Model_Post
protected function get_model( $id ) {
$post = is_a( $id, 'WP_Post' ) ? $id : get_post( $id );
* Only convert to models if the model set is valid and not the WP_Post default.
if ( ! $model || 'WP_Post' === $model || ! class_exists( $model ) || is_a( $post, $model ) ) {
if ( ! $this->cached_model_exists( $post ) ) {
$object = new $model( $post );
$this->cache['objects'][ $post->ID ] = [
'hash' => $this->get_post_hash( $post ),
return $this->cache['objects'][ $post->ID ]['object'];
public function delete_item( $id ) {
return EMPTY_TRASH_DAYS && (bool) wp_trash_post( $id );
public function is_item_trashed( $id ) {
return get_post_status( $id ) === 'trash';
public function untrash_item( $id ) {
return (bool) wp_untrash_post( $id );
public function force_delete_item( $id ) {
return (bool) wp_delete_post( $id, true );