root/trunk/wp-app.php

Revision 8390, 33.3 kB (checked in by ryan, 4 days ago)

Update AtomPub? auth to use latest API

  • Property svn:eol-style set to native
Line 
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