PageRenderTime 59ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/wordpress/wp-includes/class-wp-atom-server.php

https://bitbucket.org/mpizza/aws-pizza
PHP | 1488 lines | 786 code | 223 blank | 479 comment | 152 complexity | 3b5db0512c358ecdf1b0200cb750bde9 MD5 | raw file
  1. <?php
  2. /**
  3. * WordPress AtomPub API implementation.
  4. *
  5. * @package WordPress
  6. * @subpackage Publishing
  7. * @since 2.2.0
  8. */
  9. class wp_atom_server {
  10. /**
  11. * ATOM content type.
  12. *
  13. * @since 2.2.0
  14. * @var string
  15. */
  16. var $ATOM_CONTENT_TYPE = 'application/atom+xml';
  17. /**
  18. * Categories ATOM content type.
  19. *
  20. * @since 2.2.0
  21. * @var string
  22. */
  23. var $CATEGORIES_CONTENT_TYPE = 'application/atomcat+xml';
  24. /**
  25. * Service ATOM content type.
  26. *
  27. * @since 2.3.0
  28. * @var string
  29. */
  30. var $SERVICE_CONTENT_TYPE = 'application/atomsvc+xml';
  31. /**
  32. * ATOM XML namespace.
  33. *
  34. * @since 2.3.0
  35. * @var string
  36. */
  37. var $ATOM_NS = 'http://www.w3.org/2005/Atom';
  38. /**
  39. * ATOMPUB XML namespace.
  40. *
  41. * @since 2.3.0
  42. * @var string
  43. */
  44. var $ATOMPUB_NS = 'http://www.w3.org/2007/app';
  45. /**
  46. * Entries path.
  47. *
  48. * @since 2.2.0
  49. * @var string
  50. */
  51. var $ENTRIES_PATH = "posts";
  52. /**
  53. * Categories path.
  54. *
  55. * @since 2.2.0
  56. * @var string
  57. */
  58. var $CATEGORIES_PATH = "categories";
  59. /**
  60. * Media path.
  61. *
  62. * @since 2.2.0
  63. * @var string
  64. */
  65. var $MEDIA_PATH = "attachments";
  66. /**
  67. * Entry path.
  68. *
  69. * @since 2.2.0
  70. * @var string
  71. */
  72. var $ENTRY_PATH = "post";
  73. /**
  74. * Service path.
  75. *
  76. * @since 2.2.0
  77. * @var string
  78. */
  79. var $SERVICE_PATH = "service";
  80. /**
  81. * Media single path.
  82. *
  83. * @since 2.2.0
  84. * @var string
  85. */
  86. var $MEDIA_SINGLE_PATH = "attachment";
  87. /**
  88. * ATOMPUB parameters.
  89. *
  90. * @since 2.2.0
  91. * @var array
  92. */
  93. var $params = array();
  94. /**
  95. * Supported ATOMPUB media types.
  96. *
  97. * @since 2.3.0
  98. * @var array
  99. */
  100. var $media_content_types = array('image/*','audio/*','video/*');
  101. /**
  102. * ATOMPUB content type(s).
  103. *
  104. * @since 2.2.0
  105. * @var array
  106. */
  107. var $atom_content_types = array('application/atom+xml');
  108. /**
  109. * ATOMPUB methods.
  110. *
  111. * @since 2.2.0
  112. * @var unknown_type
  113. */
  114. var $selectors = array();
  115. /**
  116. * Whether to do output.
  117. *
  118. * Support for head.
  119. *
  120. * @since 2.2.0
  121. * @var bool
  122. */
  123. var $do_output = true;
  124. /**
  125. * Constructor - Sets up object properties.
  126. *
  127. * @since 2.2.0
  128. * @return AtomServer
  129. */
  130. function __construct() {
  131. $var_by_ref = explode( '/', $_SERVER['SCRIPT_NAME'] );
  132. $this->script_name = array_pop( $var_by_ref );
  133. $this->app_base = site_url( $this->script_name . '/' );
  134. $this->selectors = array(
  135. '@/service$@' =>
  136. array('GET' => 'get_service'),
  137. '@/categories$@' =>
  138. array('GET' => 'get_categories_xml'),
  139. '@/post/(\d+)$@' =>
  140. array('GET' => 'get_post',
  141. 'PUT' => 'put_post',
  142. 'DELETE' => 'delete_post'),
  143. '@/posts/?(\d+)?$@' =>
  144. array('GET' => 'get_posts',
  145. 'POST' => 'create_post'),
  146. '@/attachments/?(\d+)?$@' =>
  147. array('GET' => 'get_attachment',
  148. 'POST' => 'create_attachment'),
  149. '@/attachment/file/(\d+)$@' =>
  150. array('GET' => 'get_file'),
  151. '@/attachment/(\d+)$@' =>
  152. array('GET' => 'get_attachment',
  153. 'PUT' => 'put_attachment',
  154. 'DELETE' => 'delete_attachment'),
  155. );
  156. }
  157. /**
  158. * Handle ATOMPUB request.
  159. *
  160. * @since 2.2.0
  161. */
  162. function handle_request() {
  163. if ( !empty( $_SERVER['ORIG_PATH_INFO'] ) )
  164. $path = $_SERVER['ORIG_PATH_INFO'];
  165. else
  166. $path = $_SERVER['PATH_INFO'];
  167. $method = $_SERVER['REQUEST_METHOD'];
  168. $this->process_conditionals();
  169. //$this->process_conditionals();
  170. // exception case for HEAD (treat exactly as GET, but don't output)
  171. if ($method == 'HEAD') {
  172. $this->do_output = false;
  173. $method = 'GET';
  174. }
  175. // redirect to /service in case no path is found.
  176. if (strlen($path) == 0 || $path == '/')
  177. $this->redirect($this->get_service_url());
  178. // check to see if AtomPub is enabled
  179. if ( !get_option( 'enable_app' ) )
  180. $this->forbidden( sprintf( __( 'AtomPub services are disabled on this site. An admin user can enable them at %s' ), admin_url('options-writing.php') ) );
  181. // dispatch
  182. foreach ( $this->selectors as $regex => $funcs ) {
  183. if ( preg_match($regex, $path, $matches) ) {
  184. if ( isset($funcs[$method]) ) {
  185. // authenticate regardless of the operation and set the current
  186. // user. each handler will decide if auth is required or not.
  187. if ( !$this->authenticate() ) {
  188. $this->auth_required('Credentials required.');
  189. }
  190. array_shift($matches);
  191. call_user_func_array(array($this,$funcs[$method]), $matches);
  192. wp_die();
  193. } else {
  194. // only allow what we have handlers for...
  195. $this->not_allowed(array_keys($funcs));
  196. }
  197. }
  198. }
  199. // oops, nothing found
  200. $this->not_found();
  201. }
  202. /**
  203. * Retrieve XML for ATOMPUB service.
  204. *
  205. * @since 2.2.0
  206. */
  207. function get_service() {
  208. if ( !current_user_can( 'edit_posts' ) )
  209. $this->auth_required( __( 'Sorry, you do not have the right to access this site.' ) );
  210. $entries_url = esc_attr($this->get_entries_url());
  211. $categories_url = esc_attr($this->get_categories_url());
  212. $media_url = esc_attr($this->get_attachments_url());
  213. $accepted_media_types = '';
  214. foreach ($this->media_content_types as $med) {
  215. $accepted_media_types = $accepted_media_types . "<accept>" . $med . "</accept>";
  216. }
  217. $atom_prefix="atom";
  218. $atom_blogname = get_bloginfo('name');
  219. $service_doc = <<<EOD
  220. <service xmlns="$this->ATOMPUB_NS" xmlns:$atom_prefix="$this->ATOM_NS">
  221. <workspace>
  222. <$atom_prefix:title>$atom_blogname Workspace</$atom_prefix:title>
  223. <collection href="$entries_url">
  224. <$atom_prefix:title>$atom_blogname Posts</$atom_prefix:title>
  225. <accept>$this->ATOM_CONTENT_TYPE;type=entry</accept>
  226. <categories href="$categories_url" />
  227. </collection>
  228. <collection href="$media_url">
  229. <$atom_prefix:title>$atom_blogname Media</$atom_prefix:title>
  230. $accepted_media_types
  231. </collection>
  232. </workspace>
  233. </service>
  234. EOD;
  235. $this->output($service_doc, $this->SERVICE_CONTENT_TYPE);
  236. }
  237. /**
  238. * Retrieve categories list in XML format.
  239. *
  240. * @since 2.2.0
  241. */
  242. function get_categories_xml() {
  243. if ( !current_user_can( 'edit_posts' ) )
  244. $this->auth_required( __( 'Sorry, you do not have the right to access this site.' ) );
  245. $home = esc_attr(get_bloginfo_rss('url'));
  246. $categories = "";
  247. $cats = get_categories(array('hierarchical' => 0, 'hide_empty' => 0));
  248. foreach ( (array) $cats as $cat ) {
  249. $categories .= " <category term=\"" . esc_attr($cat->name) . "\" />\n";
  250. }
  251. $output = <<<EOD
  252. <app:categories xmlns:app="$this->ATOMPUB_NS"
  253. xmlns="$this->ATOM_NS"
  254. fixed="yes" scheme="$home">
  255. $categories
  256. </app:categories>
  257. EOD;
  258. $this->output($output, $this->CATEGORIES_CONTENT_TYPE);
  259. }
  260. /**
  261. * Create new post.
  262. *
  263. * @since 2.2.0
  264. */
  265. function create_post() {
  266. global $user_ID;
  267. $this->get_accepted_content_type($this->atom_content_types);
  268. $parser = new AtomParser();
  269. if ( !$parser->parse() )
  270. $this->client_error();
  271. $entry = array_pop($parser->feed->entries);
  272. $publish = ! ( isset( $entry->draft ) && 'yes' == trim( $entry->draft ) );
  273. $cap = ($publish) ? 'publish_posts' : 'edit_posts';
  274. if ( !current_user_can($cap) )
  275. $this->auth_required(__('Sorry, you do not have the right to edit/publish new posts.'));
  276. $catnames = array();
  277. if ( !empty( $entry->categories ) ) {
  278. foreach ( $entry->categories as $cat ) {
  279. array_push($catnames, $cat["term"]);
  280. }
  281. }
  282. $wp_cats = get_categories(array('hide_empty' => false));
  283. $post_category = array();
  284. foreach ( $wp_cats as $cat ) {
  285. if ( in_array($cat->name, $catnames) )
  286. array_push($post_category, $cat->term_id);
  287. }
  288. $blog_ID = get_current_blog_id();
  289. $post_status = ($publish) ? 'publish' : 'draft';
  290. $post_author = (int) $user_ID;
  291. $post_title = '';
  292. $post_content = '';
  293. $post_excerpt = '';
  294. $pubtimes = '';
  295. if ( isset( $entry->title ) && is_array( $entry->title ) && !empty( $entry->title[1] ) )
  296. $post_title = (string) $entry->title[1];
  297. if ( isset( $entry->content ) && is_array( $entry->content ) && !empty( $entry->content[1] ) )
  298. $post_content = (string) $entry->content[1];
  299. if ( isset( $entry->summary ) && is_array( $entry->summary ) && !empty( $entry->summary[1] ) )
  300. $post_excerpt = (string) $entry->summary[1];
  301. if ( !empty( $entry->published ) )
  302. $pubtimes = (string) $entry->published;
  303. $pubtimes = $this->get_publish_time( $pubtimes );
  304. $post_date = $pubtimes[0];
  305. $post_date_gmt = $pubtimes[1];
  306. if ( isset( $_SERVER['HTTP_SLUG'] ) )
  307. $post_name = $_SERVER['HTTP_SLUG'];
  308. $post_data = compact('blog_ID', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'post_name');
  309. $this->escape($post_data);
  310. $postID = wp_insert_post($post_data);
  311. if ( is_wp_error( $postID ) )
  312. $this->internal_error($postID->get_error_message());
  313. if ( !$postID )
  314. $this->internal_error(__('Sorry, your entry could not be posted. Something wrong happened.'));
  315. // getting warning here about unable to set headers
  316. // because something in the cache is printing to the buffer
  317. // could we clean up wp_set_post_categories or cache to not print
  318. // this could affect our ability to send back the right headers
  319. @wp_set_post_categories($postID, $post_category);
  320. do_action( 'atompub_create_post', $postID, $entry );
  321. $output = $this->get_entry($postID);
  322. $this->created($postID, $output);
  323. }
  324. /**
  325. * Retrieve post.
  326. *
  327. * @since 2.2.0
  328. *
  329. * @param int $postID Post ID.
  330. */
  331. function get_post($postID) {
  332. global $entry;
  333. if ( ! get_post( $postID ) || ! current_user_can( 'edit_post', $postID ) )
  334. $this->auth_required( __( 'Sorry, you do not have the right to access this post.' ) );
  335. $this->set_current_entry($postID);
  336. $output = $this->get_entry($postID);
  337. $this->output($output);
  338. }
  339. /**
  340. * Update post.
  341. *
  342. * @since 2.2.0
  343. *
  344. * @param int $postID Post ID.
  345. */
  346. function put_post($postID) {
  347. // checked for valid content-types (atom+xml)
  348. // quick check and exit
  349. $this->get_accepted_content_type($this->atom_content_types);
  350. $parser = new AtomParser();
  351. if ( !$parser->parse() )
  352. $this->bad_request();
  353. $parsed = array_pop($parser->feed->entries);
  354. // check for not found
  355. global $entry;
  356. $this->set_current_entry($postID);
  357. if ( !current_user_can('edit_post', $postID) )
  358. $this->auth_required(__('Sorry, you do not have the right to edit this post.'));
  359. $publish = ! ( isset($parsed->draft) && 'yes' == trim($parsed->draft) );
  360. if ( $publish && ! current_user_can( 'publish_posts' ) )
  361. $this->auth_required( __( 'Sorry, you do not have the right to publish this post.' ) );
  362. $post_status = ($publish) ? 'publish' : 'draft';
  363. extract($entry);
  364. $post_title = $parsed->title[1];
  365. $post_content = $parsed->content[1];
  366. $post_excerpt = $parsed->summary[1];
  367. $pubtimes = $this->get_publish_time($entry->published);
  368. $post_date = $pubtimes[0];
  369. $post_date_gmt = $pubtimes[1];
  370. $pubtimes = $this->get_publish_time($parsed->updated);
  371. $post_modified = $pubtimes[0];
  372. $post_modified_gmt = $pubtimes[1];
  373. $postdata = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt');
  374. $this->escape($postdata);
  375. $result = wp_update_post($postdata);
  376. if ( !$result )
  377. $this->internal_error(__('For some strange yet very annoying reason, this post could not be edited.'));
  378. do_action( 'atompub_put_post', $ID, $parsed );
  379. $this->ok();
  380. }
  381. /**
  382. * Remove post.
  383. *
  384. * @since 2.2.0
  385. *
  386. * @param int $postID Post ID.
  387. */
  388. function delete_post($postID) {
  389. // check for not found
  390. global $entry;
  391. $this->set_current_entry($postID);
  392. if ( !current_user_can('delete_post', $postID) )
  393. $this->auth_required(__('Sorry, you do not have the right to delete this post.'));
  394. if ( $entry['post_type'] == 'attachment' ) {
  395. $this->delete_attachment($postID);
  396. } else {
  397. $result = wp_delete_post($postID);
  398. if ( !$result ) {
  399. $this->internal_error(__('For some strange yet very annoying reason, this post could not be deleted.'));
  400. }
  401. $this->ok();
  402. }
  403. }
  404. /**
  405. * Retrieve attachment.
  406. *
  407. * @since 2.2.0
  408. *
  409. * @param int $postID Optional. Post ID.
  410. */
  411. function get_attachment($postID = null) {
  412. if ( !current_user_can( 'upload_files' ) )
  413. $this->auth_required( __( 'You do not have permission to upload files.' ) );
  414. if ( !isset($postID) ) {
  415. $this->get_attachments();
  416. } else {
  417. if ( ! current_user_can( 'edit_post', $postID ) )
  418. $this->auth_required( __( 'Sorry, you do not have the right to edit this post.' ) );
  419. $this->set_current_entry($postID);
  420. $output = $this->get_entry($postID, 'attachment');
  421. $this->output($output);
  422. }
  423. }
  424. /**
  425. * Create new attachment.
  426. *
  427. * @since 2.2.0
  428. */
  429. function create_attachment() {
  430. $type = $this->get_accepted_content_type();
  431. if ( !current_user_can('upload_files') )
  432. $this->auth_required( __( 'You do not have permission to upload files.' ) );
  433. $fp = fopen("php://input", "rb");
  434. $bits = null;
  435. while ( !feof($fp) ) {
  436. $bits .= fread($fp, 4096);
  437. }
  438. fclose($fp);
  439. $slug = '';
  440. if ( isset( $_SERVER['HTTP_SLUG'] ) )
  441. $slug = $_SERVER['HTTP_SLUG'];
  442. elseif ( isset( $_SERVER['HTTP_TITLE'] ) )
  443. $slug = $_SERVER['HTTP_TITLE'];
  444. elseif ( empty( $slug ) ) // just make a random name
  445. $slug = substr( md5( uniqid( microtime() ) ), 0, 7);
  446. $ext = preg_replace( '|.*/([a-z0-9]+)|', '$1', $_SERVER['CONTENT_TYPE'] );
  447. $slug = sanitize_file_name( "$slug.$ext" );
  448. $file = wp_upload_bits( $slug, null, $bits);
  449. $url = $file['url'];
  450. $file = $file['file'];
  451. do_action('wp_create_file_in_uploads', $file); // replicate
  452. // Construct the attachment array
  453. $attachment = array(
  454. 'post_title' => $slug,
  455. 'post_content' => $slug,
  456. 'post_status' => 'attachment',
  457. 'post_parent' => 0,
  458. 'post_mime_type' => $type,
  459. 'guid' => $url
  460. );
  461. // Save the data
  462. $postID = wp_insert_attachment($attachment, $file);
  463. if (!$postID)
  464. $this->internal_error(__('Sorry, your entry could not be posted. Something wrong happened.'));
  465. $output = $this->get_entry($postID, 'attachment');
  466. $this->created($postID, $output, 'attachment');
  467. }
  468. /**
  469. * Update attachment.
  470. *
  471. * @since 2.2.0
  472. *
  473. * @param int $postID Post ID.
  474. */
  475. function put_attachment($postID) {
  476. // checked for valid content-types (atom+xml)
  477. // quick check and exit
  478. $this->get_accepted_content_type($this->atom_content_types);
  479. $parser = new AtomParser();
  480. if (!$parser->parse()) {
  481. $this->bad_request();
  482. }
  483. $parsed = array_pop($parser->feed->entries);
  484. // check for not found
  485. global $entry;
  486. $this->set_current_entry($postID);
  487. if ( !current_user_can('edit_post', $entry['ID']) || 'attachment' != $entry['post_type'] )
  488. $this->auth_required(__('Sorry, you do not have the right to edit this post.'));
  489. extract($entry);
  490. $post_title = $parsed->title[1];
  491. $post_content = $parsed->summary[1];
  492. $pubtimes = $this->get_publish_time($parsed->updated);
  493. $post_modified = $pubtimes[0];
  494. $post_modified_gmt = $pubtimes[1];
  495. $postdata = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'post_modified', 'post_modified_gmt');
  496. $this->escape($postdata);
  497. $result = wp_update_post($postdata);
  498. if ( !$result )
  499. $this->internal_error(__('For some strange yet very annoying reason, this post could not be edited.'));
  500. $this->ok();
  501. }
  502. /**
  503. * Remove attachment.
  504. *
  505. * @since 2.2.0
  506. *
  507. * @param int $postID Post ID.
  508. */
  509. function delete_attachment($postID) {
  510. // check for not found
  511. global $entry;
  512. $this->set_current_entry($postID);
  513. if ( !current_user_can('delete_post', $postID) )
  514. $this->auth_required(__('Sorry, you do not have the right to delete this post.'));
  515. $location = get_post_meta($entry['ID'], '_wp_attached_file', true);
  516. $filetype = wp_check_filetype($location);
  517. if ( !isset($location) || 'attachment' != $entry['post_type'] || empty($filetype['ext']) )
  518. $this->internal_error(__('Error occurred while accessing post metadata for file location.'));
  519. // delete attachment
  520. $result = wp_delete_attachment($postID);
  521. if ( !$result )
  522. $this->internal_error(__('For some strange yet very annoying reason, this post could not be deleted.'));
  523. $this->ok();
  524. }
  525. /**
  526. * Retrieve attachment from post.
  527. *
  528. * @since 2.2.0
  529. *
  530. * @param int $postID Post ID.
  531. */
  532. function get_file($postID) {
  533. // check for not found
  534. global $entry;
  535. $this->set_current_entry($postID);
  536. // then whether user can edit the specific post
  537. if ( !current_user_can('edit_post', $postID) )
  538. $this->auth_required(__('Sorry, you do not have the right to edit this post.'));
  539. $location = get_post_meta($entry['ID'], '_wp_attached_file', true);
  540. $location = get_option ('upload_path') . '/' . $location;
  541. $filetype = wp_check_filetype($location);
  542. if ( !isset($location) || 'attachment' != $entry['post_type'] || empty($filetype['ext']) )
  543. $this->internal_error(__('Error occurred while accessing post metadata for file location.'));
  544. status_header('200');
  545. header('Content-Type: ' . $entry['post_mime_type']);
  546. header('Connection: close');
  547. if ( $fp = fopen($location, "rb") ) {
  548. status_header('200');
  549. header('Content-Type: ' . $entry['post_mime_type']);
  550. header('Connection: close');
  551. while ( !feof($fp) ) {
  552. echo fread($fp, 4096);
  553. }
  554. fclose($fp);
  555. } else {
  556. status_header ('404');
  557. }
  558. wp_die();
  559. }
  560. /**
  561. * Upload file to blog and add attachment to post.
  562. *
  563. * @since 2.2.0
  564. *
  565. * @param int $postID Post ID.
  566. */
  567. function put_file($postID) {
  568. // first check if user can upload
  569. if ( !current_user_can('upload_files') )
  570. $this->auth_required(__('You do not have permission to upload files.'));
  571. // check for not found
  572. global $entry;
  573. $this->set_current_entry($postID);
  574. // then whether user can edit the specific post
  575. if ( !current_user_can('edit_post', $postID) )
  576. $this->auth_required(__('Sorry, you do not have the right to edit this post.'));
  577. $upload_dir = wp_upload_dir( );
  578. $location = get_post_meta($entry['ID'], '_wp_attached_file', true);
  579. $filetype = wp_check_filetype($location);
  580. $location = "{$upload_dir['basedir']}/{$location}";
  581. if (!isset($location) || 'attachment' != $entry['post_type'] || empty($filetype['ext']))
  582. $this->internal_error(__('Error occurred while accessing post metadata for file location.'));
  583. $fp = fopen("php://input", "rb");
  584. $localfp = fopen($location, "w+");
  585. while ( !feof($fp) ) {
  586. fwrite($localfp,fread($fp, 4096));
  587. }
  588. fclose($fp);
  589. fclose($localfp);
  590. $ID = $entry['ID'];
  591. $pubtimes = $this->get_publish_time($entry->published);
  592. $post_date = $pubtimes[0];
  593. $post_date_gmt = $pubtimes[1];
  594. $pubtimes = $this->get_publish_time($parsed->updated);
  595. $post_modified = $pubtimes[0];
  596. $post_modified_gmt = $pubtimes[1];
  597. $post_data = compact('ID', 'post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt');
  598. $result = wp_update_post($post_data);
  599. if ( !$result )
  600. $this->internal_error(__('Sorry, your entry could not be posted. Something wrong happened.'));
  601. wp_update_attachment_metadata( $postID, wp_generate_attachment_metadata( $postID, $location ) );
  602. $this->ok();
  603. }
  604. /**
  605. * Retrieve entries URL.
  606. *
  607. * @since 2.2.0
  608. *
  609. * @param int $page Page ID.
  610. * @return string
  611. */
  612. function get_entries_url($page = null) {
  613. if ( isset($GLOBALS['post_type']) && ( $GLOBALS['post_type'] == 'attachment' ) )
  614. $path = $this->MEDIA_PATH;
  615. else
  616. $path = $this->ENTRIES_PATH;
  617. $url = $this->app_base . $path;
  618. if ( isset($page) && is_int($page) )
  619. $url .= "/$page";
  620. return $url;
  621. }
  622. /**
  623. * Display entries URL.
  624. *
  625. * @since 2.2.0
  626. *
  627. * @param int $page Page ID.
  628. */
  629. function the_entries_url($page = null) {
  630. echo $this->get_entries_url($page);
  631. }
  632. /**
  633. * Retrieve categories URL.
  634. *
  635. * @since 2.2.0
  636. *
  637. * @param mixed $deprecated Not used.
  638. * @return string
  639. */
  640. function get_categories_url($deprecated = '') {
  641. if ( !empty( $deprecated ) )
  642. _deprecated_argument( __FUNCTION__, '2.5' );
  643. return $this->app_base . $this->CATEGORIES_PATH;
  644. }
  645. /**
  646. * Display category URL.
  647. *
  648. * @since 2.2.0
  649. */
  650. function the_categories_url() {
  651. echo $this->get_categories_url();
  652. }
  653. /**
  654. * Retrieve attachment URL.
  655. *
  656. * @since 2.2.0
  657. *
  658. * @param int $page Page ID.
  659. * @return string
  660. */
  661. function get_attachments_url($page = null) {
  662. $url = $this->app_base . $this->MEDIA_PATH;
  663. if (isset($page) && is_int($page)) {
  664. $url .= "/$page";
  665. }
  666. return $url;
  667. }
  668. /**
  669. * Display attachment URL.
  670. *
  671. * @since 2.2.0
  672. *
  673. * @param int $page Page ID.
  674. */
  675. function the_attachments_url($page = null) {
  676. echo $this->get_attachments_url($page);
  677. }
  678. /**
  679. * Retrieve service URL.
  680. *
  681. * @since 2.3.0
  682. *
  683. * @return string
  684. */
  685. function get_service_url() {
  686. return $this->app_base . $this->SERVICE_PATH;
  687. }
  688. /**
  689. * Retrieve entry URL.
  690. *
  691. * @since 2.7.0
  692. *
  693. * @param int $postID Post ID.
  694. * @return string
  695. */
  696. function get_entry_url($postID = null) {
  697. if (!isset($postID)) {
  698. global $post;
  699. $postID = (int) $post->ID;
  700. }
  701. $url = $this->app_base . $this->ENTRY_PATH . "/$postID";
  702. return $url;
  703. }
  704. /**
  705. * Display entry URL.
  706. *
  707. * @since 2.7.0
  708. *
  709. * @param int $postID Post ID.
  710. */
  711. function the_entry_url($postID = null) {
  712. echo $this->get_entry_url($postID);
  713. }
  714. /**
  715. * Retrieve media URL.
  716. *
  717. * @since 2.2.0
  718. *
  719. * @param int $postID Post ID.
  720. * @return string
  721. */
  722. function get_media_url($postID = null) {
  723. if (!isset($postID)) {
  724. global $post;
  725. $postID = (int) $post->ID;
  726. }
  727. $url = $this->app_base . $this->MEDIA_SINGLE_PATH ."/file/$postID";
  728. return $url;
  729. }
  730. /**
  731. * Display the media URL.
  732. *
  733. * @since 2.2.0
  734. *
  735. * @param int $postID Post ID.
  736. */
  737. function the_media_url($postID = null) {
  738. echo $this->get_media_url($postID);
  739. }
  740. /**
  741. * Set the current entry to post ID.
  742. *
  743. * @since 2.2.0
  744. *
  745. * @param int $postID Post ID.
  746. */
  747. function set_current_entry($postID) {
  748. global $entry;
  749. if (!isset($postID)) {
  750. // $this->bad_request();
  751. $this->not_found();
  752. }
  753. $entry = wp_get_single_post($postID,ARRAY_A);
  754. if (!isset($entry) || !isset($entry['ID']))
  755. $this->not_found();
  756. return;
  757. }
  758. /**
  759. * Display posts XML.
  760. *
  761. * @since 2.2.0
  762. *
  763. * @param int $page Optional. Page ID.
  764. * @param string $post_type Optional, default is 'post'. Post Type.
  765. */
  766. function get_posts($page = 1, $post_type = 'post') {
  767. $feed = $this->get_feed($page, $post_type);
  768. $this->output($feed);
  769. }
  770. /**
  771. * Display attachment XML.
  772. *
  773. * @since 2.2.0
  774. *
  775. * @param int $page Page ID.
  776. * @param string $post_type Optional, default is 'attachment'. Post type.
  777. */
  778. function get_attachments($page = 1, $post_type = 'attachment') {
  779. $GLOBALS['post_type'] = $post_type;
  780. $feed = $this->get_feed($page, $post_type);
  781. $this->output($feed);
  782. }
  783. /**
  784. * Retrieve feed XML.
  785. *
  786. * @since 2.2.0
  787. *
  788. * @param int $page Page ID.
  789. * @param string $post_type Optional, default is post. Post type.
  790. * @return string
  791. */
  792. function get_feed($page = 1, $post_type = 'post') {
  793. global $post, $wp, $wp_query, $posts, $wpdb, $blog_id;
  794. ob_start();
  795. $this->ENTRY_PATH = $post_type;
  796. if (!isset($page)) {
  797. $page = 1;
  798. }
  799. $page = (int) $page;
  800. $count = get_option('posts_per_rss');
  801. wp('posts_per_page=' . $count . '&offset=' . ($count * ($page-1)) . '&orderby=modified&perm=readable');
  802. $post = $GLOBALS['post'];
  803. $posts = $GLOBALS['posts'];
  804. $wp = $GLOBALS['wp'];
  805. $wp_query = $GLOBALS['wp_query'];
  806. $wpdb = $GLOBALS['wpdb'];
  807. $blog_id = (int) $GLOBALS['blog_id'];
  808. $last_page = $wp_query->max_num_pages;
  809. $next_page = (($page + 1) > $last_page) ? null : $page + 1;
  810. $prev_page = ($page - 1) < 1 ? null : $page - 1;
  811. $last_page = ((int)$last_page == 1 || (int)$last_page == 0) ? null : (int) $last_page;
  812. $self_page = $page > 1 ? $page : null;
  813. ?><feed xmlns="<?php echo $this->ATOM_NS ?>" xmlns:app="<?php echo $this->ATOMPUB_NS ?>" xml:lang="<?php bloginfo_rss( 'language' ); ?>" <?php do_action('app_ns'); ?> >
  814. <id><?php $this->the_entries_url() ?></id>
  815. <updated><?php echo mysql2date('Y-m-d\TH:i:s\Z', get_lastpostmodified('GMT'), false); ?></updated>
  816. <title type="text"><?php bloginfo_rss('name') ?></title>
  817. <subtitle type="text"><?php bloginfo_rss("description") ?></subtitle>
  818. <link rel="first" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url() ?>" />
  819. <?php if (isset($prev_page)): ?>
  820. <link rel="previous" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($prev_page) ?>" />
  821. <?php endif; ?>
  822. <?php if (isset($next_page)): ?>
  823. <link rel="next" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($next_page) ?>" />
  824. <?php endif; ?>
  825. <link rel="last" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($last_page) ?>" />
  826. <link rel="self" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($self_page) ?>" />
  827. <rights type="text">Copyright <?php echo date('Y'); ?></rights>
  828. <?php do_action('app_head'); ?>
  829. <?php if ( have_posts() ) {
  830. while ( have_posts() ) {
  831. the_post();
  832. $this->echo_entry();
  833. }
  834. }
  835. ?></feed>
  836. <?php
  837. $feed = ob_get_contents();
  838. ob_end_clean();
  839. return $feed;
  840. }
  841. /**
  842. * Display entry XML.
  843. *
  844. * @since 2.2.0
  845. *
  846. * @param int $postID Post ID.
  847. * @param string $post_type Optional, default is post. Post type.
  848. * @return string.
  849. */
  850. function get_entry($postID, $post_type = 'post') {
  851. ob_start();
  852. switch($post_type) {
  853. case 'post':
  854. $varname = 'p';
  855. break;
  856. case 'attachment':
  857. $this->ENTRY_PATH = 'attachment';
  858. $varname = 'attachment_id';
  859. break;
  860. }
  861. query_posts($varname . '=' . $postID);
  862. if ( have_posts() ) {
  863. while ( have_posts() ) {
  864. the_post();
  865. $this->echo_entry();
  866. $entry = ob_get_contents();
  867. break;
  868. }
  869. }
  870. ob_end_clean();
  871. return $entry;
  872. }
  873. /**
  874. * Display post content XML.
  875. *
  876. * @since 2.3.0
  877. */
  878. function echo_entry() { ?>
  879. <entry xmlns="<?php echo $this->ATOM_NS ?>"
  880. xmlns:app="<?php echo $this->ATOMPUB_NS ?>" xml:lang="<?php bloginfo_rss( 'language' ); ?>">
  881. <id><?php the_guid( $GLOBALS['post']->ID ); ?></id>
  882. <?php list($content_type, $content) = prep_atom_text_construct(get_the_title()); ?>
  883. <title type="<?php echo $content_type ?>"><?php echo $content ?></title>
  884. <updated><?php echo get_post_modified_time('Y-m-d\TH:i:s\Z', true); ?></updated>
  885. <published><?php echo get_post_time('Y-m-d\TH:i:s\Z', true); ?></published>
  886. <app:edited><?php echo get_post_modified_time('Y-m-d\TH:i:s\Z', true); ?></app:edited>
  887. <app:control>
  888. <app:draft><?php echo ($GLOBALS['post']->post_status == 'draft' ? 'yes' : 'no') ?></app:draft>
  889. </app:control>
  890. <author>
  891. <name><?php the_author()?></name>
  892. <?php if ( get_the_author_meta('url') && get_the_author_meta('url') != 'http://' ) { ?>
  893. <uri><?php the_author_meta('url') ?></uri>
  894. <?php } ?>
  895. </author>
  896. <?php if ($GLOBALS['post']->post_type == 'attachment') { ?>
  897. <link rel="edit-media" href="<?php $this->the_media_url() ?>" />
  898. <content type="<?php echo $GLOBALS['post']->post_mime_type ?>" src="<?php the_guid() ; ?>"/>
  899. <?php } else { ?>
  900. <link href="<?php the_permalink_rss() ?>" />
  901. <?php if ( strlen( $GLOBALS['post']->post_content ) ) :
  902. list($content_type, $content) = prep_atom_text_construct(get_the_content()); ?>
  903. <content type="<?php echo $content_type ?>"><?php echo $content ?></content>
  904. <?php endif; ?>
  905. <?php } ?>
  906. <link rel="edit" href="<?php $this->the_entry_url() ?>" />
  907. <?php the_category_rss( 'atom' ); ?>
  908. <?php list($content_type, $content) = prep_atom_text_construct(get_the_excerpt()); ?>
  909. <summary type="<?php echo $content_type ?>"><?php echo $content ?></summary>
  910. <?php do_action('app_entry'); ?>
  911. </entry>
  912. <?php }
  913. /**
  914. * Set 'OK' (200) status header.
  915. *
  916. * @since 2.2.0
  917. */
  918. function ok() {
  919. header('Content-Type: text/plain');
  920. status_header('200');
  921. wp_die();
  922. }
  923. /**
  924. * Set 'No Content' (204) status header.
  925. *
  926. * @since 2.2.0
  927. */
  928. function no_content() {
  929. header('Content-Type: text/plain');
  930. status_header('204');
  931. echo "Moved to Trash.";
  932. wp_die();
  933. }
  934. /**
  935. * Display 'Internal Server Error' (500) status header.
  936. *
  937. * @since 2.2.0
  938. *
  939. * @param string $msg Optional. Status string.
  940. */
  941. function internal_error($msg = 'Internal Server Error') {
  942. header('Content-Type: text/plain');
  943. status_header('500');
  944. echo $msg;
  945. wp_die();
  946. }
  947. /**
  948. * Set 'Bad Request' (400) status header.
  949. *
  950. * @since 2.2.0
  951. */
  952. function bad_request() {
  953. header('Content-Type: text/plain');
  954. status_header('400');
  955. wp_die();
  956. }
  957. /**
  958. * Set 'Length Required' (411) status header.
  959. *
  960. * @since 2.2.0
  961. */
  962. function length_required() {
  963. header("HTTP/1.1 411 Length Required");
  964. header('Content-Type: text/plain');
  965. status_header('411');
  966. wp_die();
  967. }
  968. /**
  969. * Set 'Unsupported Media Type' (415) status header.
  970. *
  971. * @since 2.2.0
  972. */
  973. function invalid_media() {
  974. header("HTTP/1.1 415 Unsupported Media Type");
  975. header('Content-Type: text/plain');
  976. wp_die();
  977. }
  978. /**
  979. * Set 'Forbidden' (403) status header.
  980. *
  981. * @since 2.6.0
  982. */
  983. function forbidden($reason='') {
  984. header('Content-Type: text/plain');
  985. status_header('403');
  986. echo $reason;
  987. wp_die();
  988. }
  989. /**
  990. * Set 'Not Found' (404) status header.
  991. *
  992. * @since 2.2.0
  993. */
  994. function not_found() {
  995. header('Content-Type: text/plain');
  996. status_header('404');
  997. wp_die();
  998. }
  999. /**
  1000. * Set 'Not Allowed' (405) status header.
  1001. *
  1002. * @since 2.2.0
  1003. */
  1004. function not_allowed($allow) {
  1005. header('Allow: ' . join(',', $allow));
  1006. status_header('405');
  1007. wp_die();
  1008. }
  1009. /**
  1010. * Display Redirect (302) content and set status headers.
  1011. *
  1012. * @since 2.3.0
  1013. */
  1014. function redirect($url) {
  1015. $escaped_url = esc_attr($url);
  1016. $content = <<<EOD
  1017. <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
  1018. <html>
  1019. <head>
  1020. <title>302 Found</title>
  1021. </head>
  1022. <body>
  1023. <h1>Found</h1>
  1024. <p>The document has moved <a href="$escaped_url">here</a>.</p>
  1025. </body>
  1026. </html>
  1027. EOD;
  1028. header('HTTP/1.1 302 Moved');
  1029. header('Content-Type: text/html');
  1030. header('Location: ' . $url);
  1031. echo $content;
  1032. wp_die();
  1033. }
  1034. /**
  1035. * Set 'Client Error' (400) status header.
  1036. *
  1037. * @since 2.2.0
  1038. */
  1039. function client_error($msg = 'Client Error') {
  1040. header('Content-Type: text/plain');
  1041. status_header('400');
  1042. wp_die();
  1043. }
  1044. /**
  1045. * Set created status headers (201).
  1046. *
  1047. * Sets the 'content-type', 'content-location', and 'location'.
  1048. *
  1049. * @since 2.2.0
  1050. */
  1051. function created($post_ID, $content, $post_type = 'post') {
  1052. $edit = $this->get_entry_url($post_ID);
  1053. switch($post_type) {
  1054. case 'post':
  1055. $ctloc = $this->get_entry_url($post_ID);
  1056. break;
  1057. case 'attachment':
  1058. $edit = $this->app_base . "attachments/$post_ID";
  1059. break;
  1060. }
  1061. header("Content-Type: $this->ATOM_CONTENT_TYPE");
  1062. if (isset($ctloc))
  1063. header('Content-Location: ' . $ctloc);
  1064. header('Location: ' . $edit);
  1065. status_header('201');
  1066. echo $content;
  1067. wp_die();
  1068. }
  1069. /**
  1070. * Set 'Auth Required' (401) headers.
  1071. *
  1072. * @since 2.2.0
  1073. *
  1074. * @param string $msg Status header content and HTML content.
  1075. */
  1076. function auth_required($msg) {
  1077. nocache_headers();
  1078. header('WWW-Authenticate: Basic realm="WordPress Atom Protocol"');
  1079. header("HTTP/1.1 401 $msg");
  1080. header('Status: 401 ' . $msg);
  1081. header('Content-Type: text/html');
  1082. $content = <<<EOD
  1083. <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
  1084. <html>
  1085. <head>
  1086. <title>401 Unauthorized</title>
  1087. </head>
  1088. <body>
  1089. <h1>401 Unauthorized</h1>
  1090. <p>$msg</p>
  1091. </body>
  1092. </html>
  1093. EOD;
  1094. echo $content;
  1095. wp_die();
  1096. }
  1097. /**
  1098. * Display XML and set headers with content type.
  1099. *
  1100. * @since 2.2.0
  1101. *
  1102. * @param string $xml Display feed content.
  1103. * @param string $ctype Optional, default is 'atom+xml'. Feed content type.
  1104. */
  1105. function output($xml, $ctype = 'application/atom+xml') {
  1106. status_header('200');
  1107. $xml = '<?xml version="1.0" encoding="' . strtolower(get_option('blog_charset')) . '"?>'."\n".$xml;
  1108. header('Connection: close');
  1109. header('Content-Length: '. strlen($xml));
  1110. header('Content-Type: ' . $ctype);
  1111. header('Content-Disposition: attachment; filename=atom.xml');
  1112. header('Date: '. date('r'));
  1113. if ($this->do_output)
  1114. echo $xml;
  1115. wp_die();
  1116. }
  1117. /**
  1118. * Sanitize content for database usage.
  1119. *
  1120. * @since 2.2.0
  1121. *
  1122. * @param array $array Sanitize array and multi-dimension array.
  1123. */
  1124. function escape(&$array) {
  1125. global $wpdb;
  1126. foreach ($array as $k => $v) {
  1127. if (is_array($v)) {
  1128. $this->escape($array[$k]);
  1129. } else if (is_object($v)) {
  1130. //skip
  1131. } else {
  1132. $array[$k] = $wpdb->escape($v);
  1133. }
  1134. }
  1135. }
  1136. /**
  1137. * Access credential through various methods and perform login.
  1138. *
  1139. * @since 2.2.0
  1140. *
  1141. * @return bool
  1142. */
  1143. function authenticate() {
  1144. // if using mod_rewrite/ENV hack
  1145. // http://www.besthostratings.com/articles/http-auth-php-cgi.html
  1146. if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
  1147. list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) =
  1148. explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
  1149. } else if (isset($_SERVER['REDIRECT_REMOTE_USER'])) {
  1150. // Workaround for setups that do not forward HTTP_AUTHORIZATION
  1151. // See http://trac.wordpress.org/ticket/7361
  1152. list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) =
  1153. explode(':', base64_decode(substr($_SERVER['REDIRECT_REMOTE_USER'], 6)));
  1154. }
  1155. // If Basic Auth is working...
  1156. if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
  1157. $user = wp_authenticate($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
  1158. if ( $user && !is_wp_error($user) ) {
  1159. wp_set_current_user($user->ID);
  1160. return true;
  1161. }
  1162. }
  1163. return false;
  1164. }
  1165. /**
  1166. * Retrieve accepted content types.
  1167. *
  1168. * @since 2.2.0
  1169. *
  1170. * @param array $types Optional. Content Types.
  1171. * @return string
  1172. */
  1173. function get_accepted_content_type($types = null) {
  1174. if (!isset($types)) {
  1175. $types = $this->media_content_types;
  1176. }
  1177. if (!isset($_SERVER['CONTENT_LENGTH']) || !isset($_SERVER['CONTENT_TYPE'])) {
  1178. $this->length_required();
  1179. }
  1180. $type = $_SERVER['CONTENT_TYPE'];
  1181. list($type,$subtype) = explode('/',$type);
  1182. list($subtype) = explode(";",$subtype); // strip MIME parameters
  1183. foreach($types as $t) {
  1184. list($acceptedType,$acceptedSubtype) = explode('/',$t);
  1185. if ($acceptedType == '*' || $acceptedType == $type) {
  1186. if ($acceptedSubtype == '*' || $acceptedSubtype == $subtype)
  1187. return $type . "/" . $subtype;
  1188. }
  1189. }
  1190. $this->invalid_media();
  1191. }
  1192. /**
  1193. * Process conditionals for posts.
  1194. *
  1195. * @since 2.2.0
  1196. */
  1197. function process_conditionals() {
  1198. if (empty($this->params)) return;
  1199. if ($_SERVER['REQUEST_METHOD'] == 'DELETE') return;
  1200. switch($this->params[0]) {
  1201. case $this->ENTRY_PATH:
  1202. global $post;
  1203. $post = wp_get_single_post($this->params[1]);
  1204. $wp_last_modified = get_post_modified_time('D, d M Y H:i:s', true);
  1205. $post = null;
  1206. break;
  1207. case $this->ENTRIES_PATH:
  1208. $wp_last_modified = mysql2date('D, d M Y H:i:s', get_lastpostmodified('GMT'), 0).' GMT';
  1209. break;
  1210. default:
  1211. return;
  1212. }
  1213. $wp_etag = md5($wp_last_modified);
  1214. @header("Last-Modified: $wp_last_modified");
  1215. @header("ETag: $wp_etag");
  1216. // Support for Conditional GET
  1217. if (isset($_SERVER['HTTP_IF_NONE_MATCH']))
  1218. $client_etag = stripslashes($_SERVER['HTTP_IF_NONE_MATCH']);
  1219. else
  1220. $client_etag = false;
  1221. $client_last_modified = trim( $_SERVER['HTTP_IF_MODIFIED_SINCE']);
  1222. // If string is empty, return 0. If not, attempt to parse into a timestamp
  1223. $client_modified_timestamp = $client_last_modified ? strtotime($client_last_modified) : 0;
  1224. // Make a timestamp for our most recent modification...
  1225. $wp_modified_timestamp = strtotime($wp_last_modified);
  1226. if ( ($client_last_modified && $client_etag) ?
  1227. (($client_modified_timestamp >= $wp_modified_timestamp) && ($client_etag == $wp_etag)) :
  1228. (($client_modified_timestamp >= $wp_modified_timestamp) || ($client_etag == $wp_etag)) ) {
  1229. status_header( 304 );
  1230. wp_die();
  1231. }
  1232. }
  1233. /**
  1234. * Convert RFC3339 time string to timestamp.
  1235. *
  1236. * @since 2.3.0
  1237. *
  1238. * @param string $str String to time.
  1239. * @return bool|int false if format is incorrect.
  1240. */
  1241. function rfc3339_str2time($str) {
  1242. $match = false;
  1243. if (!preg_match("/(\d{4}-\d{2}-\d{2})T(\d{2}\:\d{2}\:\d{2})\.?\d{0,3}(Z|[+-]+\d{2}\:\d{2})/", $str, $match))
  1244. return false;
  1245. if ($match[3] == 'Z')
  1246. $match[3] = '+0000';
  1247. return strtotime($match[1] . " " . $match[2] . " " . $match[3]);
  1248. }
  1249. /**
  1250. * Retrieve published time to display in XML.
  1251. *
  1252. * @since 2.3.0
  1253. *
  1254. * @param string $published Time string.
  1255. * @return string
  1256. */
  1257. function get_publish_time($published) {
  1258. $pubtime = $this->rfc3339_str2time($published);
  1259. if (!$pubtime) {
  1260. return array(current_time('mysql'),current_time('mysql',1));
  1261. } else {
  1262. return array(date("Y-m-d H:i:s", $pubtime), gmdate("Y-m-d H:i:s", $pubtime));
  1263. }
  1264. }
  1265. }