1<?php
2/**
3 * Post API: WP_Post class
4 *
5 * @package WordPress
6 * @subpackage Post
7 * @since 4.4.0
8 */
9
10/**
11 * Core class used to implement the WP_Post object.
12 *
13 * @since 3.5.0
14 *
15 * @property string $page_template
16 *
17 * @property-read int[]  $ancestors
18 * @property-read int    $post_category
19 * @property-read string $tag_input
20 */
21final class WP_Post {
22
23	/**
24	 * Post ID.
25	 *
26	 * @since 3.5.0
27	 * @var int
28	 */
29	public $ID;
30
31	/**
32	 * ID of post author.
33	 *
34	 * A numeric string, for compatibility reasons.
35	 *
36	 * @since 3.5.0
37	 * @var string
38	 */
39	public $post_author = 0;
40
41	/**
42	 * The post's local publication time.
43	 *
44	 * @since 3.5.0
45	 * @var string
46	 */
47	public $post_date = '0000-00-00 00:00:00';
48
49	/**
50	 * The post's GMT publication time.
51	 *
52	 * @since 3.5.0
53	 * @var string
54	 */
55	public $post_date_gmt = '0000-00-00 00:00:00';
56
57	/**
58	 * The post's content.
59	 *
60	 * @since 3.5.0
61	 * @var string
62	 */
63	public $post_content = '';
64
65	/**
66	 * The post's title.
67	 *
68	 * @since 3.5.0
69	 * @var string
70	 */
71	public $post_title = '';
72
73	/**
74	 * The post's excerpt.
75	 *
76	 * @since 3.5.0
77	 * @var string
78	 */
79	public $post_excerpt = '';
80
81	/**
82	 * The post's status.
83	 *
84	 * @since 3.5.0
85	 * @var string
86	 */
87	public $post_status = 'publish';
88
89	/**
90	 * Whether comments are allowed.
91	 *
92	 * @since 3.5.0
93	 * @var string
94	 */
95	public $comment_status = 'open';
96
97	/**
98	 * Whether pings are allowed.
99	 *
100	 * @since 3.5.0
101	 * @var string
102	 */
103	public $ping_status = 'open';
104
105	/**
106	 * The post's password in plain text.
107	 *
108	 * @since 3.5.0
109	 * @var string
110	 */
111	public $post_password = '';
112
113	/**
114	 * The post's slug.
115	 *
116	 * @since 3.5.0
117	 * @var string
118	 */
119	public $post_name = '';
120
121	/**
122	 * URLs queued to be pinged.
123	 *
124	 * @since 3.5.0
125	 * @var string
126	 */
127	public $to_ping = '';
128
129	/**
130	 * URLs that have been pinged.
131	 *
132	 * @since 3.5.0
133	 * @var string
134	 */
135	public $pinged = '';
136
137	/**
138	 * The post's local modified time.
139	 *
140	 * @since 3.5.0
141	 * @var string
142	 */
143	public $post_modified = '0000-00-00 00:00:00';
144
145	/**
146	 * The post's GMT modified time.
147	 *
148	 * @since 3.5.0
149	 * @var string
150	 */
151	public $post_modified_gmt = '0000-00-00 00:00:00';
152
153	/**
154	 * A utility DB field for post content.
155	 *
156	 * @since 3.5.0
157	 * @var string
158	 */
159	public $post_content_filtered = '';
160
161	/**
162	 * ID of a post's parent post.
163	 *
164	 * @since 3.5.0
165	 * @var int
166	 */
167	public $post_parent = 0;
168
169	/**
170	 * The unique identifier for a post, not necessarily a URL, used as the feed GUID.
171	 *
172	 * @since 3.5.0
173	 * @var string
174	 */
175	public $guid = '';
176
177	/**
178	 * A field used for ordering posts.
179	 *
180	 * @since 3.5.0
181	 * @var int
182	 */
183	public $menu_order = 0;
184
185	/**
186	 * The post's type, like post or page.
187	 *
188	 * @since 3.5.0
189	 * @var string
190	 */
191	public $post_type = 'post';
192
193	/**
194	 * An attachment's mime type.
195	 *
196	 * @since 3.5.0
197	 * @var string
198	 */
199	public $post_mime_type = '';
200
201	/**
202	 * Cached comment count.
203	 *
204	 * A numeric string, for compatibility reasons.
205	 *
206	 * @since 3.5.0
207	 * @var string
208	 */
209	public $comment_count = 0;
210
211	/**
212	 * Stores the post object's sanitization level.
213	 *
214	 * Does not correspond to a DB field.
215	 *
216	 * @since 3.5.0
217	 * @var string
218	 */
219	public $filter;
220
221	/**
222	 * Retrieve WP_Post instance.
223	 *
224	 * @since 3.5.0
225	 *
226	 * @global wpdb $wpdb WordPress database abstraction object.
227	 *
228	 * @param int $post_id Post ID.
229	 * @return WP_Post|false Post object, false otherwise.
230	 */
231	public static function get_instance( $post_id ) {
232		global $wpdb;
233
234		$post_id = (int) $post_id;
235		if ( ! $post_id ) {
236			return false;
237		}
238
239		$_post = wp_cache_get( $post_id, 'posts' );
240
241		if ( ! $_post ) {
242			$_post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID = %d LIMIT 1", $post_id ) );
243
244			if ( ! $_post ) {
245				return false;
246			}
247
248			$_post = sanitize_post( $_post, 'raw' );
249			wp_cache_add( $_post->ID, $_post, 'posts' );
250		} elseif ( empty( $_post->filter ) ) {
251			$_post = sanitize_post( $_post, 'raw' );
252		}
253
254		return new WP_Post( $_post );
255	}
256
257	/**
258	 * Constructor.
259	 *
260	 * @since 3.5.0
261	 *
262	 * @param WP_Post|object $post Post object.
263	 */
264	public function __construct( $post ) {
265		foreach ( get_object_vars( $post ) as $key => $value ) {
266			$this->$key = $value;
267		}
268	}
269
270	/**
271	 * Isset-er.
272	 *
273	 * @since 3.5.0
274	 *
275	 * @param string $key Property to check if set.
276	 * @return bool
277	 */
278	public function __isset( $key ) {
279		if ( 'ancestors' === $key ) {
280			return true;
281		}
282
283		if ( 'page_template' === $key ) {
284			return true;
285		}
286
287		if ( 'post_category' === $key ) {
288			return true;
289		}
290
291		if ( 'tags_input' === $key ) {
292			return true;
293		}
294
295		return metadata_exists( 'post', $this->ID, $key );
296	}
297
298	/**
299	 * Getter.
300	 *
301	 * @since 3.5.0
302	 *
303	 * @param string $key Key to get.
304	 * @return mixed
305	 */
306	public function __get( $key ) {
307		if ( 'page_template' === $key && $this->__isset( $key ) ) {
308			return get_post_meta( $this->ID, '_wp_page_template', true );
309		}
310
311		if ( 'post_category' === $key ) {
312			if ( is_object_in_taxonomy( $this->post_type, 'category' ) ) {
313				$terms = get_the_terms( $this, 'category' );
314			}
315
316			if ( empty( $terms ) ) {
317				return array();
318			}
319
320			return wp_list_pluck( $terms, 'term_id' );
321		}
322
323		if ( 'tags_input' === $key ) {
324			if ( is_object_in_taxonomy( $this->post_type, 'post_tag' ) ) {
325				$terms = get_the_terms( $this, 'post_tag' );
326			}
327
328			if ( empty( $terms ) ) {
329				return array();
330			}
331
332			return wp_list_pluck( $terms, 'name' );
333		}
334
335		// Rest of the values need filtering.
336		if ( 'ancestors' === $key ) {
337			$value = get_post_ancestors( $this );
338		} else {
339			$value = get_post_meta( $this->ID, $key, true );
340		}
341
342		if ( $this->filter ) {
343			$value = sanitize_post_field( $key, $value, $this->ID, $this->filter );
344		}
345
346		return $value;
347	}
348
349	/**
350	 * {@Missing Summary}
351	 *
352	 * @since 3.5.0
353	 *
354	 * @param string $filter Filter.
355	 * @return WP_Post
356	 */
357	public function filter( $filter ) {
358		if ( $this->filter === $filter ) {
359			return $this;
360		}
361
362		if ( 'raw' === $filter ) {
363			return self::get_instance( $this->ID );
364		}
365
366		return sanitize_post( $this, $filter );
367	}
368
369	/**
370	 * Convert object to array.
371	 *
372	 * @since 3.5.0
373	 *
374	 * @return array Object as array.
375	 */
376	public function to_array() {
377		$post = get_object_vars( $this );
378
379		foreach ( array( 'ancestors', 'page_template', 'post_category', 'tags_input' ) as $key ) {
380			if ( $this->__isset( $key ) ) {
381				$post[ $key ] = $this->__get( $key );
382			}
383		}
384
385		return $post;
386	}
387}
388