Нема описа

Posts.php 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. <?php
  2. /*******************************************************************************
  3. * Copyright (c) 2019, Code Atlantic LLC
  4. ******************************************************************************/
  5. if ( ! defined( 'ABSPATH' ) ) {
  6. exit;
  7. }
  8. /**
  9. * Class PUM_Abstract_Repository_Posts
  10. *
  11. * Interface between WP_Query and our data needs. Essentially a query factory.
  12. */
  13. abstract class PUM_Abstract_Repository_Posts implements PUM_Interface_Repository {
  14. /**
  15. * WordPress query object.
  16. *
  17. * @var WP_Query
  18. */
  19. protected $query;
  20. /**
  21. * Array of hydrated object models.
  22. *
  23. * @var array
  24. */
  25. protected $cache = array(
  26. 'objects' => array(),
  27. 'queries' => array(),
  28. );
  29. /**
  30. * @var string
  31. */
  32. protected $model;
  33. /**
  34. * Should return a valid post type to test against.
  35. *
  36. * @return string
  37. */
  38. protected function get_post_type() {
  39. return 'post';
  40. }
  41. /**
  42. * Initialize the repository.
  43. */
  44. protected function init() {
  45. $this->query = new WP_Query;
  46. $this->reset_strict_query_args();
  47. }
  48. public function __construct() {
  49. $this->init();
  50. }
  51. /**
  52. * @return array
  53. */
  54. public function default_query_args() {
  55. return array();
  56. }
  57. /**
  58. * @var array
  59. */
  60. protected $strict_query_args = array();
  61. /**
  62. * Returns an array of default strict query args that can't be over ridden, such as post type.
  63. *
  64. * @return array
  65. */
  66. protected function default_strict_query_args() {
  67. return array(
  68. 'post_type' => $this->get_post_type(),
  69. );
  70. }
  71. /**
  72. * Returns an array of enforced query args that can't be over ridden, such as post type.
  73. *
  74. * @return array
  75. */
  76. protected function get_strict_query_args() {
  77. return $this->strict_query_args;
  78. }
  79. /**
  80. * Sets a specific query arg to a strict value.
  81. *
  82. * @param $key
  83. * @param null $value
  84. */
  85. protected function set_strict_query_arg( $key, $value = null ) {
  86. $this->strict_query_args[ $key ] = $value;
  87. }
  88. /**
  89. * Returns an array of enforced query args that can't be over ridden, such as post type.
  90. *
  91. * @return array
  92. */
  93. protected function reset_strict_query_args() {
  94. $this->strict_query_args = $this->default_strict_query_args();
  95. return $this->strict_query_args;
  96. }
  97. /**
  98. * @param array $args
  99. *
  100. * @return array
  101. */
  102. protected function _build_wp_query_args( $args = array() ) {
  103. $args = wp_parse_args( $args, $this->default_query_args() );
  104. $args = $this->build_wp_query_args( $args );
  105. return array_merge( $args, $this->get_strict_query_args() );
  106. }
  107. /**
  108. * @param array $args
  109. *
  110. * @return array
  111. */
  112. protected function build_wp_query_args( $args = array() ) {
  113. return $args;
  114. }
  115. /**
  116. * @param int $id
  117. *
  118. * @return WP_Post|PUM_Abstract_Model_Post
  119. * @throws \InvalidArgumentException
  120. */
  121. public function get_item( $id ) {
  122. if ( ! $this->has_item( $id ) ) {
  123. throw new InvalidArgumentException( sprintf( __( 'No %s found with id %d.', 'popup-maker' ), $this->get_post_type(), $id ) );
  124. }
  125. return $this->get_model( $id );
  126. }
  127. /**
  128. * @param $field
  129. * @param $value
  130. *
  131. * @return PUM_Abstract_Model_Post|\WP_Post
  132. */
  133. public function get_item_by( $field, $value ) {
  134. global $wpdb;
  135. $id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE %s = %s", $field, $value ) );
  136. if ( ! $id || ! $this->has_item( $id ) ) {
  137. throw new InvalidArgumentException( sprintf( __( 'No user found with %s %s.', 'popup-maker' ), $field, $value ) );
  138. }
  139. return $this->get_model( $id );
  140. }
  141. /**
  142. * @param int $id
  143. *
  144. * @return bool
  145. */
  146. public function has_item( $id ) {
  147. return get_post_type( $id ) === $this->get_post_type();
  148. }
  149. /**
  150. * @param $args
  151. *
  152. * @return string
  153. */
  154. protected function get_args_hash( $args ) {
  155. return md5( serialize( $args ) );
  156. }
  157. /**
  158. * @param array $args
  159. *
  160. * @return WP_Post[]|PUM_Abstract_Model_Post[]
  161. */
  162. public function get_items( $args = array() ) {
  163. /** Reset default strict query args. */
  164. $this->reset_strict_query_args();
  165. $args = $this->_build_wp_query_args( $args );
  166. $hash = $this->get_args_hash( $args );
  167. if ( ! isset( $this->cache['queries'][ $hash ] ) ) {
  168. /**
  169. * Initialize a new query and return it.
  170. *
  171. * This also keeps the query cached for potential later usage via $this->get_last_query();
  172. */
  173. $this->query->query( $args );
  174. $this->cache['queries'][ $hash ] = (array) $this->query->posts;
  175. }
  176. /** @var array $posts */
  177. $posts = $this->cache['queries'][ $hash ];
  178. /**
  179. * Only convert to models if the model set is valid and not the WP_Post default.
  180. */
  181. foreach ( $posts as $key => $post ) {
  182. $posts[ $key ] = $this->get_model( $post );
  183. }
  184. return $posts;
  185. }
  186. /**
  187. * @param array $args
  188. *
  189. * @return int
  190. */
  191. public function count_items( $args = array() ) {
  192. /** Reset default strict query args. */
  193. $this->reset_strict_query_args();
  194. /** Set several strict query arg overrides, no matter what args were passed. */
  195. $this->set_strict_query_arg( 'fields', 'ids' );
  196. $this->set_strict_query_arg( 'posts_per_page', 1 );
  197. /** We don't use $this->query here to avoid returning count queries via $this->>get_last_query(); */
  198. $query = new WP_Query( $this->_build_wp_query_args( $args ) );
  199. return (int) $query->found_posts;
  200. }
  201. /**
  202. * @return \WP_Query
  203. */
  204. public function get_last_query() {
  205. return $this->query;
  206. }
  207. /**
  208. * Assert that data is valid.
  209. *
  210. * @param array $data
  211. *
  212. * @throws InvalidArgumentException
  213. *
  214. * TODO Add better Exceptions via these guides:
  215. * - https://www.brandonsavage.net/using-interfaces-for-exceptions/
  216. * - https://www.alainschlesser.com/structuring-php-exceptions/
  217. *
  218. * if ( isset( $data['subject'] ) && ! $data['subject'] ) {
  219. * throw new InvalidArgumentException( 'The subject is required.' );
  220. * }
  221. */
  222. abstract protected function assert_data( $data );
  223. /**
  224. * @param array $data
  225. *
  226. * @return WP_Post|PUM_Abstract_Model_Post
  227. * @throws InvalidArgumentException
  228. */
  229. public function create_item( $data ) {
  230. $data = wp_parse_args( $data, array(
  231. 'content' => '',
  232. 'title' => '',
  233. 'meta_input' => array(),
  234. ) );
  235. $this->assert_data( $data );
  236. $post_id = wp_insert_post( array(
  237. 'post_type' => $this->get_post_type(),
  238. 'post_status' => 'publish',
  239. 'post_title' => $data['title'],
  240. 'post_content' => $data['content'],
  241. 'meta_input' => $data['meta_input'],
  242. ), true );
  243. if ( is_wp_error( $post_id ) ) {
  244. throw new InvalidArgumentException( $post_id->get_error_message() );
  245. }
  246. return $this->get_item( $post_id );
  247. }
  248. /**
  249. * @param int $id
  250. * @param array $data
  251. *
  252. * @return WP_Post|PUM_Abstract_Model_Post
  253. * @throws Exception
  254. */
  255. public function update_item( $id, $data ) {
  256. $this->assert_data( $data );
  257. /** @var WP_Post|PUM_Abstract_Model_Post $original */
  258. $original = $this->get_item( $id );
  259. $post_update = array();
  260. foreach ( $data as $key => $value ) {
  261. if ( $original->$key === $value ) {
  262. continue;
  263. }
  264. switch ( $key ) {
  265. default:
  266. $post_update[ $key ] = $value;
  267. break;
  268. case 'title':
  269. $post_update['post_title'] = $value;
  270. break;
  271. case 'content':
  272. $post_update['post_content'] = $value;
  273. break;
  274. case 'custom_meta_key':
  275. update_post_meta( $id, '_custom_meta_key', $value );
  276. }
  277. }
  278. if ( count( $post_update ) ) {
  279. $post_update['ID'] = $id;
  280. wp_update_post( $post_update );
  281. }
  282. return $this->get_item( $id );
  283. }
  284. /**
  285. * @param $post
  286. *
  287. * @return string
  288. */
  289. protected function get_post_hash( $post ) {
  290. return md5( serialize( $post ) );
  291. }
  292. /**
  293. * @param $post
  294. *
  295. * @return bool
  296. */
  297. protected function cached_model_exists( $post ) {
  298. return isset( $this->cache['objects'][ $post->ID ] ) && $this->get_post_hash( $post ) === $this->cache['objects'][ $post->ID ]['hash'];
  299. }
  300. /**
  301. * @param int|WP_Post $id
  302. *
  303. * @return WP_Post|PUM_Abstract_Model_Post
  304. */
  305. protected function get_model( $id ) {
  306. $post = is_a( $id, 'WP_Post' ) ? $id : get_post( $id );
  307. /**
  308. * Only convert to models if the model set is valid and not the WP_Post default.
  309. */
  310. $model = $this->model;
  311. if ( ! $model || 'WP_Post' === $model || ! class_exists( $model ) || is_a( $post, $model ) ) {
  312. return $post;
  313. }
  314. if ( ! $this->cached_model_exists( $post ) ) {
  315. $object = new $model( $post );
  316. $this->cache['objects'][ $post->ID ] = array(
  317. 'object' => $object,
  318. 'hash' => $this->get_post_hash( $post )
  319. );
  320. }
  321. return $this->cache['objects'][ $post->ID ]['object'];
  322. }
  323. /**
  324. * @param int $id
  325. *
  326. * @return bool
  327. */
  328. public function delete_item( $id ) {
  329. return EMPTY_TRASH_DAYS && (bool) wp_trash_post( $id );
  330. }
  331. /**
  332. * @param int $id
  333. *
  334. * @return bool
  335. */
  336. public function is_item_trashed( $id ) {
  337. return get_post_status( $id ) === 'trash';
  338. }
  339. /**
  340. * @param int $id
  341. *
  342. * @return bool
  343. */
  344. public function untrash_item( $id ) {
  345. return (bool) wp_untrash_post( $id );
  346. }
  347. /**
  348. * @param int $id
  349. *
  350. * @return bool
  351. */
  352. public function force_delete_item( $id ) {
  353. return (bool) wp_delete_post( $id, true );
  354. }
  355. }