View file File name : Posts.php Content :<?php /** * Abstract for posts repository * * @package PopupMaker * @copyright Copyright (c) 2024, Code Atlantic LLC */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * 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. * * @var WP_Query */ protected $query; /** * Array of hydrated object models. * * @var array */ protected $cache = [ 'objects' => [], 'queries' => [], ]; /** * @var string */ protected $model; /** * Should return a valid post type to test against. * * @return string */ protected function get_post_type() { return 'post'; } /** * Initialize the repository. */ protected function init() { $this->query = new WP_Query(); $this->reset_strict_query_args(); } public function __construct() { $this->init(); } /** * @return array */ public function default_query_args() { return []; } /** * @var array */ protected $strict_query_args = []; /** * Returns an array of default strict query args that can't be over ridden, such as post type. * * @return array */ protected function default_strict_query_args() { return [ 'post_type' => $this->get_post_type(), ]; } /** * Returns an array of enforced query args that can't be over ridden, such as post type. * * @return array */ protected function get_strict_query_args() { return $this->strict_query_args; } /** * Sets a specific query arg to a strict value. * * @param $key * @param null $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. * * @return array */ protected function reset_strict_query_args() { $this->strict_query_args = $this->default_strict_query_args(); return $this->strict_query_args; } /** * @param array $args * * @return array */ protected function do_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() ); } /** * @param array $args * * @return array */ protected function build_wp_query_args( $args = [] ) { return $args; } /** * @param int $id * * @return WP_Post|PUM_Abstract_Model_Post * @throws \InvalidArgumentException */ public function get_item( $id ) { if ( ! $this->has_item( $id ) ) { throw new InvalidArgumentException( sprintf( /* translators: %1$s is the post type name, %2$d is the ID. */ esc_attr__( 'No %1$s found with id %2$d.', 'popup-maker' ), esc_attr( $this->get_post_type() ), absint( $id ) ) ); } return $this->get_model( $id ); } /** * @param $field * @param $value * * @return PUM_Abstract_Model_Post|\WP_Post * @throws InvalidArgumentException */ public function get_item_by( $field, $value ) { global $wpdb; // Will be circling back to this in the future to either add caching or defer to WP_Query entirely. Leaving NoCaching flagged for now. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $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( /* translators: %1$s is the post type name, %2$s is the field name, %3$s is the value. */ esc_attr__( 'No %1$s found with %2$s %3$s.', 'popup-maker' ), esc_attr( $this->get_post_type() ), esc_attr( $field ), esc_attr( $value ) ) ); } return $this->get_model( $id ); } /** * @param int $id * * @return bool */ public function has_item( $id ) { return get_post_type( $id ) === $this->get_post_type(); } /** * @param $args * * @return string */ protected function get_args_hash( $args ) { return md5( wp_json_encode( $args ) ); } /** * @param array $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->do_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; } /** @var array $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 ); } return $posts; } /** * @param array $args * * @return int */ 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->do_build_wp_query_args( $args ) ); return (int) $query->found_posts; } /** * @return \WP_Query */ public function get_last_query() { return $this->query; } /** * Assert that data is valid. * * @param array $data * * @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 ); /** * @param array $data * * @return WP_Post|PUM_Abstract_Model_Post * @throws InvalidArgumentException */ public function create_item( $data ) { $data = wp_parse_args( $data, [ 'content' => '', 'title' => '', 'meta_input' => [], ] ); $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'], ], true ); if ( is_wp_error( $post_id ) ) { throw new InvalidArgumentException( esc_html( $post_id->get_error_message() ) ); } return $this->get_item( $post_id ); } /** * @param int $id * @param array $data * * @return WP_Post|PUM_Abstract_Model_Post * @throws Exception */ public function update_item( $id, $data ) { $this->assert_data( $data ); /** @var WP_Post|PUM_Abstract_Model_Post $original */ $original = $this->get_item( $id ); $post_update = []; foreach ( $data as $key => $value ) { if ( $original->$key === $value ) { continue; } switch ( $key ) { default: $post_update[ $key ] = $value; break; case 'title': $post_update['post_title'] = $value; break; case 'content': $post_update['post_content'] = $value; break; case 'custom_meta_key': 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 ); } /** * @param $post * * @return string */ protected function get_post_hash( $post ) { return md5( wp_json_encode( $post ) ); } /** * @param $post * * @return bool */ protected function cached_model_exists( $post ) { return isset( $this->cache['objects'][ $post->ID ] ) && $this->get_post_hash( $post ) === $this->cache['objects'][ $post->ID ]['hash']; } /** * @param int|WP_Post $id * * @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. */ $model = $this->model; if ( ! $model || 'WP_Post' === $model || ! class_exists( $model ) || is_a( $post, $model ) ) { return $post; } if ( ! $this->cached_model_exists( $post ) ) { $object = new $model( $post ); $this->cache['objects'][ $post->ID ] = [ 'object' => $object, 'hash' => $this->get_post_hash( $post ), ]; } return $this->cache['objects'][ $post->ID ]['object']; } /** * @param int $id * * @return bool */ public function delete_item( $id ) { return EMPTY_TRASH_DAYS && (bool) wp_trash_post( $id ); } /** * @param int $id * * @return bool */ public function is_item_trashed( $id ) { return get_post_status( $id ) === 'trash'; } /** * @param int $id * * @return bool */ public function untrash_item( $id ) { return (bool) wp_untrash_post( $id ); } /** * @param int $id * * @return bool */ public function force_delete_item( $id ) { return (bool) wp_delete_post( $id, true ); } }