PageRenderTime 24ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/php/commands/post.php

https://gitlab.com/Blueprint-Marketing/wp-cli
PHP | 523 lines | 209 code | 61 blank | 253 comment | 35 complexity | 5a0615a1b004736829846a2dc9968f4c MD5 | raw file
  1. <?php
  2. /**
  3. * Manage posts.
  4. *
  5. * @package wp-cli
  6. */
  7. class Post_Command extends \WP_CLI\CommandWithDBObject {
  8. protected $obj_type = 'post';
  9. protected $obj_fields = array(
  10. 'ID',
  11. 'post_title',
  12. 'post_name',
  13. 'post_date',
  14. 'post_status'
  15. );
  16. public function __construct() {
  17. $this->fetcher = new \WP_CLI\Fetchers\Post;
  18. }
  19. /**
  20. * Create a post.
  21. *
  22. * ## OPTIONS
  23. *
  24. * [<file>]
  25. * : Read post content from <file>. If this value is present, the
  26. * `--post_content` argument will be ignored.
  27. *
  28. * Passing `-` as the filename will cause post content to
  29. * be read from STDIN.
  30. *
  31. * [--<field>=<value>]
  32. * : Associative args for the new post. See wp_insert_post().
  33. *
  34. * [--edit]
  35. * : Immediately open system's editor to write or edit post content.
  36. *
  37. * If content is read from a file, from STDIN, or from the `--post_content`
  38. * argument, that text will be loaded into the editor.
  39. *
  40. * [--porcelain]
  41. * : Output just the new post id.
  42. *
  43. * ## EXAMPLES
  44. *
  45. * wp post create --post_type=page --post_title='A future post' --post_status=future --post_date='2020-12-01 07:00:00'
  46. *
  47. * wp post create ./post-content.txt --post_category=201,345 --post_title='Post from file'
  48. */
  49. public function create( $args, $assoc_args ) {
  50. if ( ! empty( $args[0] ) ) {
  51. $assoc_args['post_content'] = $this->read_from_file_or_stdin( $args[0] );
  52. }
  53. if ( isset( $assoc_args['edit'] ) ) {
  54. $input = isset( $assoc_args['post_content'] ) ?
  55. $assoc_args['post_content'] : '';
  56. if ( $output = $this->_edit( $input, 'WP-CLI: New Post' ) )
  57. $assoc_args['post_content'] = $output;
  58. else
  59. $assoc_args['post_content'] = $input;
  60. }
  61. if ( isset( $assoc_args['post_category'] ) ) {
  62. $assoc_args['post_category'] = explode( ',', $assoc_args['post_category'] );
  63. }
  64. parent::_create( $args, $assoc_args, function ( $params ) {
  65. return wp_insert_post( $params, true );
  66. } );
  67. }
  68. /**
  69. * Update one or more posts.
  70. *
  71. * ## OPTIONS
  72. *
  73. * <id>...
  74. * : One or more IDs of posts to update.
  75. *
  76. * [<file>]
  77. * : Read post content from <file>. If this value is present, the
  78. * `--post_content` argument will be ignored.
  79. *
  80. * Passing `-` as the filename will cause post content to
  81. * be read from STDIN.
  82. *
  83. * --<field>=<value>
  84. * : One or more fields to update. See wp_update_post().
  85. *
  86. * ## EXAMPLES
  87. *
  88. * wp post update 123 --post_name=something --post_status=draft
  89. */
  90. public function update( $args, $assoc_args ) {
  91. foreach( $args as $key => $arg ) {
  92. if ( is_numeric( $arg ) ) {
  93. continue;
  94. }
  95. $assoc_args['post_content'] = $this->read_from_file_or_stdin( $arg );
  96. unset( $args[ $key ] );
  97. break;
  98. }
  99. parent::_update( $args, $assoc_args, function ( $params ) {
  100. return wp_update_post( $params, true );
  101. } );
  102. }
  103. /**
  104. * Launch system editor to edit post content.
  105. *
  106. * ## OPTIONS
  107. *
  108. * <id>
  109. * : The ID of the post to edit.
  110. *
  111. * ## EXAMPLES
  112. *
  113. * wp post edit 123
  114. */
  115. public function edit( $args, $_ ) {
  116. $post = $this->fetcher->get_check( $args[0] );
  117. $r = $this->_edit( $post->post_content, "WP-CLI post {$post->ID}" );
  118. if ( $r === false )
  119. \WP_CLI::warning( 'No change made to post content.', 'Aborted' );
  120. else
  121. $this->update( $args, array( 'post_content' => $r ) );
  122. }
  123. protected function _edit( $content, $title ) {
  124. $content = apply_filters( 'the_editor_content', $content );
  125. $output = \WP_CLI\Utils\launch_editor_for_input( $content, $title );
  126. return ( is_string( $output ) ) ?
  127. apply_filters( 'content_save_pre', $output ) : $output;
  128. }
  129. /**
  130. * Get a post's content by ID.
  131. *
  132. * ## OPTIONS
  133. *
  134. * <id>
  135. * : The ID of the post to get.
  136. *
  137. * [--field=<field>]
  138. * : Instead of returning the whole post, returns the value of a single field.
  139. *
  140. * [--fields=<fields>]
  141. * : Limit the output to specific fields. Defaults to all fields.
  142. *
  143. * [--format=<format>]
  144. * : Accepted values: table, json, csv. Default: table
  145. *
  146. * ## EXAMPLES
  147. *
  148. * # save the post content to a file
  149. * wp post get 12 --field=content > file.txt
  150. */
  151. public function get( $args, $assoc_args ) {
  152. $post = $this->fetcher->get_check( $args[0] );
  153. $post_arr = get_object_vars( $post );
  154. unset( $post_arr['filter'] );
  155. if ( empty( $assoc_args['fields'] ) ) {
  156. $assoc_args['fields'] = array_keys( $post_arr );
  157. }
  158. $formatter = $this->get_formatter( $assoc_args );
  159. $formatter->display_item( $post_arr );
  160. }
  161. /**
  162. * Delete a post by ID.
  163. *
  164. * ## OPTIONS
  165. *
  166. * <id>...
  167. * : One or more IDs of posts to delete.
  168. *
  169. * [--force]
  170. * : Skip the trash bin.
  171. *
  172. * ## EXAMPLES
  173. *
  174. * wp post delete 123 --force
  175. *
  176. * wp post delete $(wp post list --post_type='page' --format=ids)
  177. *
  178. * # delete all posts in the trash
  179. * wp post delete $(wp post list --post_status=trash --format=ids)
  180. */
  181. public function delete( $args, $assoc_args ) {
  182. $defaults = array(
  183. 'force' => false
  184. );
  185. $assoc_args = array_merge( $defaults, $assoc_args );
  186. parent::_delete( $args, $assoc_args, function ( $post_id, $assoc_args ) {
  187. $status = get_post_status( $post_id );
  188. $r = wp_delete_post( $post_id, $assoc_args['force'] );
  189. if ( $r ) {
  190. $action = $assoc_args['force'] || 'trash' === $status ? 'Deleted' : 'Trashed';
  191. return array( 'success', "$action post $post_id." );
  192. } else {
  193. return array( 'error', "Failed deleting post $post_id." );
  194. }
  195. } );
  196. }
  197. /**
  198. * Get a list of posts.
  199. *
  200. * ## OPTIONS
  201. *
  202. * [--<field>=<value>]
  203. * : One or more args to pass to WP_Query.
  204. *
  205. * [--field=<field>]
  206. * : Prints the value of a single field for each post.
  207. *
  208. * [--fields=<fields>]
  209. * : Limit the output to specific object fields.
  210. *
  211. * [--format=<format>]
  212. * : Accepted values: table, csv, json, count, ids. Default: table
  213. *
  214. * ## AVAILABLE FIELDS
  215. *
  216. * These fields will be displayed by default for each post:
  217. *
  218. * * ID
  219. * * post_title
  220. * * post_name
  221. * * post_date
  222. * * post_status
  223. *
  224. * These fields are optionally available:
  225. *
  226. * * post_author
  227. * * post_date_gmt
  228. * * post_content
  229. * * post_excerpt
  230. * * comment_status
  231. * * ping_status
  232. * * post_password
  233. * * to_ping
  234. * * pinged
  235. * * post_modified
  236. * * post_modified_gmt
  237. * * post_content_filtered
  238. * * post_parent
  239. * * guid
  240. * * menu_order
  241. * * post_type
  242. * * post_mime_type
  243. * * comment_count
  244. * * filter
  245. *
  246. * ## EXAMPLES
  247. *
  248. * wp post list --field=ID
  249. *
  250. * wp post list --post_type=post --posts_per_page=5 --format=json
  251. *
  252. * wp post list --post_type=page --fields=post_title,post_status
  253. *
  254. * wp post list --post_type=page,post --format=ids
  255. *
  256. * @subcommand list
  257. */
  258. public function list_( $_, $assoc_args ) {
  259. $formatter = $this->get_formatter( $assoc_args );
  260. $defaults = array(
  261. 'posts_per_page' => -1,
  262. 'post_status' => 'any',
  263. );
  264. $query_args = array_merge( $defaults, $assoc_args );
  265. foreach ( $query_args as $key => $query_arg ) {
  266. if ( false !== strpos( $key, '__' )
  267. || ( 'post_type' == $key && 'any' != $query_arg ) ) {
  268. $query_args[$key] = explode( ',', $query_arg );
  269. }
  270. }
  271. if ( 'ids' == $formatter->format ) {
  272. $query_args['fields'] = 'ids';
  273. $query = new WP_Query( $query_args );
  274. echo implode( ' ', $query->posts );
  275. } else {
  276. $query = new WP_Query( $query_args );
  277. $formatter->display_items( $query->posts );
  278. }
  279. }
  280. /**
  281. * Generate some posts.
  282. *
  283. * ## OPTIONS
  284. *
  285. * [--count=<number>]
  286. * : How many posts to generate. Default: 100
  287. *
  288. * [--post_type=<type>]
  289. * : The type of the generated posts. Default: 'post'
  290. *
  291. * [--post_status=<status>]
  292. * : The status of the generated posts. Default: 'publish'
  293. *
  294. * [--post_author=<login>]
  295. * : The author of the generated posts. Default: none
  296. *
  297. * [--post_date=<yyyy-mm-dd>]
  298. * : The date of the generated posts. Default: current date
  299. *
  300. * [--post_content]
  301. * : If set, the command reads the post_content from STDIN.
  302. *
  303. * [--max_depth=<number>]
  304. * : For hierarchical post types, generate child posts down to a certain depth. Default: 1
  305. *
  306. * ## EXAMPLES
  307. *
  308. * wp post generate --count=10 --post_type=page --post_date=1999-01-04
  309. * curl http://loripsum.net/api/5 | wp post generate --post_content --count=10
  310. */
  311. public function generate( $args, $assoc_args ) {
  312. global $wpdb;
  313. $defaults = array(
  314. 'count' => 100,
  315. 'max_depth' => 1,
  316. 'post_type' => 'post',
  317. 'post_status' => 'publish',
  318. 'post_author' => false,
  319. 'post_date' => current_time( 'mysql' ),
  320. 'post_content' => '',
  321. );
  322. extract( array_merge( $defaults, $assoc_args ), EXTR_SKIP );
  323. // @codingStandardsIgnoreStart
  324. if ( !post_type_exists( $post_type ) ) {
  325. WP_CLI::error( sprintf( "'%s' is not a registered post type.", $post_type ) );
  326. }
  327. if ( $post_author ) {
  328. $user_fetcher = new \WP_CLI\Fetchers\User;
  329. $post_author = $user_fetcher->get_check( $post_author )->ID;
  330. }
  331. if ( isset( $assoc_args['post_content'] ) ) {
  332. $post_content = file_get_contents( 'php://stdin' );
  333. }
  334. // Get the total number of posts
  335. $total = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->posts WHERE post_type = %s", $post_type ) );
  336. $label = get_post_type_object( $post_type )->labels->singular_name;
  337. $hierarchical = get_post_type_object( $post_type )->hierarchical;
  338. $limit = $count + $total;
  339. $notify = \WP_CLI\Utils\make_progress_bar( 'Generating posts', $count );
  340. $previous_post_id = 0;
  341. $current_depth = 1;
  342. $current_parent = 0;
  343. for ( $i = $total; $i < $limit; $i++ ) {
  344. if ( $hierarchical ) {
  345. if( $this->maybe_make_child() && $current_depth < $max_depth ) {
  346. $current_parent = $previous_post_id;
  347. $current_depth++;
  348. } else if( $this->maybe_reset_depth() ) {
  349. $current_depth = 1;
  350. $current_parent = 0;
  351. }
  352. }
  353. $args = array(
  354. 'post_type' => $post_type,
  355. 'post_title' => "$label $i",
  356. 'post_status' => $post_status,
  357. 'post_author' => $post_author,
  358. 'post_parent' => $current_parent,
  359. 'post_name' => "post-$i",
  360. 'post_date' => $post_date,
  361. 'post_content' => $post_content,
  362. );
  363. $post_id = wp_insert_post( $args, true );
  364. if ( is_wp_error( $post_id ) ) {
  365. WP_CLI::warning( $post_id );
  366. } else {
  367. $previous_post_id = $post_id;
  368. }
  369. $notify->tick();
  370. }
  371. $notify->finish();
  372. // @codingStandardsIgnoreEnd
  373. }
  374. /**
  375. * Get post url
  376. *
  377. * ## OPTIONS
  378. *
  379. * <id>...
  380. * : One or more IDs of posts get the URL.
  381. *
  382. * ## EXAMPLES
  383. *
  384. * wp post url 123
  385. *
  386. * wp post url 123 324
  387. */
  388. public function url( $args ) {
  389. parent::_url( $args, 'get_permalink' );
  390. }
  391. private function maybe_make_child() {
  392. // 50% chance of making child post
  393. return ( mt_rand(1, 2) == 1 );
  394. }
  395. private function maybe_reset_depth() {
  396. // 10% chance of reseting to root depth
  397. return ( mt_rand(1, 10) == 7 );
  398. }
  399. /**
  400. * Read post content from file or STDIN
  401. *
  402. * @param string $arg Supplied argument
  403. * @return string
  404. */
  405. private function read_from_file_or_stdin( $arg ) {
  406. if ( $arg !== '-' ) {
  407. $readfile = $arg;
  408. if ( ! file_exists( $readfile ) || ! is_file( $readfile ) ) {
  409. \WP_CLI::error( "Unable to read content from $readfile." );
  410. }
  411. } else {
  412. $readfile = 'php://stdin';
  413. }
  414. return file_get_contents( $readfile );
  415. }
  416. }
  417. /**
  418. * Manage post custom fields.
  419. *
  420. * ## OPTIONS
  421. *
  422. * [--format=json]
  423. * : Encode/decode values as JSON.
  424. *
  425. * ## EXAMPLES
  426. *
  427. * wp post meta set 123 _wp_page_template about.php
  428. */
  429. class Post_Meta_Command extends \WP_CLI\CommandWithMeta {
  430. protected $meta_type = 'post';
  431. /**
  432. * Check that the post ID exists
  433. *
  434. * @param int
  435. */
  436. protected function check_object_id( $object_id ) {
  437. $fetcher = new \WP_CLI\Fetchers\Post;
  438. $post = $fetcher->get_check( $object_id );
  439. return $post->ID;
  440. }
  441. }
  442. /**
  443. * Manage post terms.
  444. *
  445. *
  446. * ## EXAMPLES
  447. *
  448. * wp post term set 123 test category
  449. */
  450. class Post_Term_Command extends \WP_CLI\CommandWithTerms {
  451. protected $obj_type = 'post';
  452. public function __construct() {
  453. $this->fetcher = new \WP_CLI\Fetchers\Post;
  454. }
  455. protected function get_object_type() {
  456. $post = $this->fetcher->get_check( $this->get_obj_id() );
  457. return $post->post_type;
  458. }
  459. }
  460. WP_CLI::add_command( 'post', 'Post_Command' );
  461. WP_CLI::add_command( 'post meta', 'Post_Meta_Command' );
  462. WP_CLI::add_command( 'post term', 'Post_Term_Command' );