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