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