root/branches/2.2/wp-app.php

Revision 5766, 35.5 kB (checked in by markjaquith, 1 year ago)

Check post type after upload. Props Alexander Concha

  • Property svn:eol-style set to native
Line 
1 <?php
2 /*
3  * wp-app.php - Atom Publishing Protocol support for WordPress
4  * Original code by: Elias Torres, http://torrez.us/archives/2006/08/31/491/
5  * Modified by: Dougal Campbell, http://dougal.gunters.org/
6  *
7  * Version: 1.0.5-dc
8  */
9
10 define('APP_REQUEST', true);
11
12 require_once('wp-config.php');
13 require_once('wp-includes/post-template.php');
14
15 // Attempt to automatically detect whether to use querystring
16 // or PATH_INFO, based on our environment:
17 $use_querystring = $wp_version == 'MU' ? 1 : 0;
18
19 // If using querystring, we need to put the path together manually:
20 if ($use_querystring) {
21     $GLOBALS['use_querystring'] = $use_querystring;
22     $action = $_GET['action'];
23     $eid = (int) $_GET['eid'];
24
25     $_SERVER['PATH_INFO'] = $action;
26
27     if ($eid) {
28         $_SERVER['PATH_INFO'] .= "/$eid";
29     }
30 } else {
31     $_SERVER['PATH_INFO'] = str_replace( '/wp-app.php', '', $_SERVER['REQUEST_URI'] );
32 }
33
34 $app_logging = 0;
35
36 function log_app($label,$msg) {
37     global $app_logging;
38     if ($app_logging) {
39         $fp = fopen( 'app.log', 'a+');
40         $date = gmdate( 'Y-m-d H:i:s' );
41         fwrite($fp, "\n\n$date - $label\n$msg\n");
42         fclose($fp);
43     }
44 }
45
46 if ( !function_exists('wp_set_current_user') ) :
47 function wp_set_current_user($id, $name = '') {
48     global $current_user;
49
50     if ( isset($current_user) && ($id == $current_user->ID) )
51         return $current_user;
52
53     $current_user = new WP_User($id, $name);
54
55     return $current_user;
56 }
57 endif;
58
59 function wa_posts_where_include_drafts_filter($where) {
60     $where = ereg_replace("post_author = ([0-9]+) AND post_status != 'draft'","post_author = \\1 AND post_status = 'draft'", $where);
61     return $where;
62 }
63 add_filter('posts_where', 'wa_posts_where_include_drafts_filter');
64
65 class AtomEntry {
66     var $links = array();
67     var $categories = array();
68 }
69
70 class AtomParser {
71
72     var $ATOM_CONTENT_ELEMENTS = array('content','summary','title','subtitle','rights');
73     var $ATOM_SIMPLE_ELEMENTS = array('id','updated','published','draft');
74
75     var $depth = 0;
76     var $indent = 2;
77     var $in_content;
78     var $ns_contexts = array();
79     var $ns_decls = array();
80     var $is_xhtml = false;
81     var $skipped_div = false;
82
83     var $entry;
84
85     function AtomParser() {
86
87         $this->entry = new AtomEntry();
88         $this->map_attrs_func = create_function('$k,$v', 'return "$k=\"$v\"";');
89         $this->map_xmlns_func = create_function('$p,$n', '$xd = "xmlns"; if(strlen($n[0])>0) $xd .= ":{$n[0]}"; return "{$xd}=\"{$n[1]}\"";');
90     }
91
92     function parse() {
93
94         global $app_logging;
95         array_unshift($this->ns_contexts, array());
96
97         $parser = xml_parser_create_ns();
98         xml_set_object($parser, $this);
99         xml_set_element_handler($parser, "start_element", "end_element");
100         xml_parser_set_option($parser,XML_OPTION_CASE_FOLDING,0);
101         xml_parser_set_option($parser,XML_OPTION_SKIP_WHITE,0);
102         xml_set_character_data_handler($parser, "cdata");
103         xml_set_default_handler($parser, "_default");
104         xml_set_start_namespace_decl_handler($parser, "start_ns");
105         xml_set_end_namespace_decl_handler($parser, "end_ns");
106
107         $contents = "";
108
109         $fp = fopen("php://input", "r");
110         while(!feof($fp)) {
111             $line = fgets($fp, 4096);
112         
113             if($app_logging) $contents .= $line;
114
115             if(!xml_parse($parser, $line)) {
116                 log_app("xml_parse_error", "line: $line");
117                 $this->error = sprintf(__('XML error: %s at line %d')."\n",
118                     xml_error_string(xml_get_error_code($xml_parser)),
119                     xml_get_current_line_number($xml_parser));
120                 log_app("xml_parse_error", $this->error);
121                 return false;
122             }
123         }
124         fclose($fp);
125
126         xml_parser_free($parser);
127
128         log_app("AtomParser->parse()",trim($contents));
129
130         return true;
131     }
132
133     function start_element($parser, $name, $attrs) {
134
135         $tag = array_pop(split(":", $name));
136
137         array_unshift($this->ns_contexts, $this->ns_decls);
138
139         $this->depth++;
140
141         #print str_repeat(" ", $this->depth * $this->indent) . "start_element('$name')" ."\n";
142         #print str_repeat(" ", $this->depth+1 * $this->indent) . print_r($this->ns_contexts,true) ."\n";
143
144         if(!empty($this->in_content)) {
145             $attrs_prefix = array();
146
147             // resolve prefixes for attributes
148             foreach($attrs as $key => $value) {
149                 $attrs_prefix[$this->ns_to_prefix($key)] = $this->xml_escape($value);
150             }
151             $attrs_str = join(' ', array_map($this->map_attrs_func, array_keys($attrs_prefix), array_values($attrs_prefix)));
152             if(strlen($attrs_str) > 0) {
153                 $attrs_str = " " . $attrs_str;
154             }
155
156             $xmlns_str = join(' ', array_map($this->map_xmlns_func, array_keys($this->ns_contexts[0]), array_values($this->ns_contexts[0])));
157             if(strlen($xmlns_str) > 0) {
158                 $xmlns_str = " " . $xmlns_str;
159             }
160
161             // handle self-closing tags (case: a new child found right-away, no text node)
162             if(count($this->in_content) == 2) {
163                 array_push($this->in_content, ">");
164             }
165         
166             array_push($this->in_content, "<". $this->ns_to_prefix($name) ."{$xmlns_str}{$attrs_str}");
167         } else if(in_array($tag, $this->ATOM_CONTENT_ELEMENTS) || in_array($tag, $this->ATOM_SIMPLE_ELEMENTS)) {
168             $this->in_content = array();
169             $this->is_xhtml = $attrs['type'] == 'xhtml';
170             array_push($this->in_content, array($tag,$this->depth));
171         } else if($tag == 'link') {
172             array_push($this->entry->links, $attrs);
173         } else if($tag == 'category') {
174             array_push($this->entry->categories, $attrs);
175         }
176
177         $this->ns_decls = array();
178     }
179
180     function end_element($parser, $name) {
181
182         $tag = array_pop(split(":", $name));
183
184         if(!empty($this->in_content)) {
185             if($this->in_content[0][0] == $tag &&
186             $this->in_content[0][1] == $this->depth) {
187                 array_shift($this->in_content);
188                 if($this->is_xhtml) {
189                     $this->in_content = array_slice($this->in_content, 2, count($this->in_content)-3);
190                 }
191                 $this->entry->$tag = join('',$this->in_content);
192                 $this->in_content = array();
193             } else {
194                 $endtag = $this->ns_to_prefix($name);
195                 if (strpos($this->in_content[count($this->in_content)-1], '<' . $endtag) !== false) {
196                     array_push($this->in_content, "/>");
197                 } else {
198                     array_push($this->in_content, "</$endtag>");
199                 }
200             }
201         }
202
203         array_shift($this->ns_contexts);
204
205         #print str_repeat(" ", $this->depth * $this->indent) . "end_element('$name')" ."\n";
206
207         $this->depth--;
208     }
209
210     function start_ns($parser, $prefix, $uri) {
211         #print str_repeat(" ", $this->depth * $this->indent) . "starting: " . $prefix . ":" . $uri . "\n";
212         array_push($this->ns_decls, array($prefix,$uri));
213     }
214
215     function end_ns($parser, $prefix) {
216         #print str_repeat(" ", $this->depth * $this->indent) . "ending: #" . $prefix . "#\n";
217     }
218
219     function cdata($parser, $data) {
220         #print str_repeat(" ", $this->depth * $this->indent) . "data: #" . $data . "#\n";
221         if(!empty($this->in_content)) {
222             // handle self-closing tags (case: text node found, need to close element started)
223             if (strpos($this->in_content[count($this->in_content)-1], '<') !== false) {
224                 array_push($this->in_content, ">");
225             }
226             array_push($this->in_content, $this->xml_escape($data));
227         }
228     }
229
230     function _default($parser, $data) {
231         # when does this gets called?
232     }
233
234
235     function ns_to_prefix($qname) {
236         $components = split(":", $qname);
237         $name = array_pop($components);
238
239         if(!empty($components)) {
240             $ns = join(":",$components);
241             foreach($this->ns_contexts as $context) {
242                 foreach($context as $mapping) {
243                     if($mapping[1] == $ns && strlen($mapping[0]) > 0) {
244                         return "$mapping[0]:$name";
245                     }
246                 }
247             }
248         }
249         return $name;
250     }
251
252     function xml_escape($string)
253     {
254              return str_replace(array('&','"',"'",'<','>'),
255                 array('&amp;','&quot;','&apos;','&lt;','&gt;'),
256                 $string );
257     }
258 }
259
260 class AtomServer {
261
262     var $ATOM_CONTENT_TYPE = 'application/atom+xml';
263     var $CATEGORIES_CONTENT_TYPE = 'application/atomcat+xml';
264     var $INTROSPECTION_CONTENT_TYPE = 'application/atomserv+xml';
265
266     var $ENTRIES_PATH = "posts";
267     var $CATEGORIES_PATH = "categories";
268     var $MEDIA_PATH = "attachments";
269     var $ENTRY_PATH = "post";
270     var $MEDIA_SINGLE_PATH = "attachment";
271
272     var $params = array();
273     var $script_name = "wp-app.php";
274     var $media_content_types = array('image/*','audio/*','video/*');
275     var $atom_content_types = array('application/atom+xml');
276
277     var $selectors = array();
278
279     // support for head
280     var $do_output = true;
281
282     function AtomServer() {
283
284         $this->script_name = array_pop(explode('/',$_SERVER['SCRIPT_NAME']));
285
286         $this->selectors = array(
287             '@/service@' =>
288                 array('GET' => 'get_service'),
289             '@/categories@' =>
290                 array('GET' => 'get_categories_xml'),
291             '@/post/(\d+)@' =>
292                 array('GET' => 'get_post',
293                         'PUT' => 'put_post',
294                         'DELETE' => 'delete_post'),
295             '@/posts/?([^/]+)?@' =>
296                 array('GET' => 'get_posts',
297                         'POST' => 'create_post'),
298             '@/attachments/?(\d+)?@' =>
299                 array('GET' => 'get_attachment',
300                         'POST' => 'create_attachment'),
301             '@/attachment/file/(\d+)@' =>
302                 array('GET' => 'get_file',
303                         'PUT' => 'put_file',
304                         'DELETE' => 'delete_file'),
305             '@/attachment/(\d+)@' =>
306                 array('GET' => 'get_attachment',
307                         'PUT' => 'put_attachment',
308                         'DELETE' => 'delete_attachment'),
309         );
310     }
311
312     function handle_request() {
313
314         $path = $_SERVER['PATH_INFO'];
315         $method = $_SERVER['REQUEST_METHOD'];
316
317         log_app('REQUEST',"$method $path\n================");
318
319         //$this->process_conditionals();
320
321         // exception case for HEAD (treat exactly as GET, but don't output)
322         if($method == 'HEAD') {
323             $this->do_output = false;
324             $method = 'GET';
325         }
326
327         // lame.
328         if(strlen($path) == 0 || $path == '/') {
329             $path = '/service';
330         }
331
332         // authenticate regardless of the operation and set the current
333         // user. each handler will decide if auth is required or not.
334         $this->authenticate();
335
336         // dispatch
337         foreach($this->selectors as $regex => $funcs) {
338             if(preg_match($regex, $path, $matches)) {
339                 if(isset($funcs[$method])) {
340                     array_shift($matches);
341                     call_user_func_array(array(&$this,$funcs[$method]), $matches);
342                     exit();
343                 } else {
344                     // only allow what we have handlers for...
345                     $this->not_allowed(array_keys($funcs));
346                 }
347             }
348         }
349
350         // oops, nothing found
351         $this->not_found();
352     }
353
354     function get_service() {
355         log_app('function','get_service()');
356         $entries_url = $this->get_entries_url();
357         $categories_url = $this->get_categories_url();
358         $media_url = $this->get_attachments_url();
359         $accepted_content_types = join(',',$this->media_content_types);
360         $introspection = <<<EOD
361 <service xmlns="http://purl.org/atom/app#" xmlns:atom="http://www.w3.org/2005/Atom">
362     <workspace title="WordPress Workspace">
363         <collection href="$entries_url" title="Posts">
364         <atom:title>WordPress Posts</atom:title>
365         <accept>entry</accept>
366         <categories href="$categories_url" />
367         </collection>
368         <collection href="$media_url" title="Media">
369         <atom:title>WordPress Media</atom:title>
370         <accept>$accepted_content_types</accept>
371         </collection>
372     </workspace>
373 </service>
374
375 EOD;
376
377         $this->output($introspection, $this->INTROSPECTION_CONTENT_TYPE);
378     }
379
380 function get_categories_xml() {
381     log_app('function','get_categories_xml()');
382     $home = get_bloginfo_rss('home');
383
384     $categories = "";
385     $cats = get_categories("hierarchical=0&hide_empty=0");
386     foreach ((array) $cats as $cat) {
387         $categories .= "    <category term=\"" . attribute_escape($cat->cat_name) .  "\" />\n";
388     }
389         $output = <<<EOD
390 <app:categories xmlns:app="http://purl.org/atom/app#"
391     xmlns="http://www.w3.org/2005/Atom"
392     fixed="yes" scheme="$home">
393     $categories
394 </app:categories>
395 EOD;
396     $this->output($output, $this->CATEGORIES_CONTENT_TYPE);
397 }
398
399     /*
400      * Create Post (No arguments)
401      */
402     function create_post() {
403         global $blog_id;
404         $this->get_accepted_content_type($this->atom_content_types);
405
406         $parser = new AtomParser();
407         if(!$parser->parse()) {
408             $this->client_error();
409         }
410
411         $entry = $parser->entry;
412
413         $publish = (isset($entry->draft) && trim($entry->draft) == 'yes') ? false : true;
414
415         $cap = ($publish) ? 'publish_posts' : 'edit_posts';
416
417         if(!current_user_can($cap))
418             $this->auth_required('Sorry, you do not have the right to edit/publish new posts.');
419
420         $blog_ID = (int ) $blog_id;
421         $post_status = ($publish) ? 'publish' : 'draft';
422         $post_author = (int) $user->ID;
423         $post_title = $entry->title;
424         $post_content = $entry->content;
425         $post_excerpt = $entry->summary;
426         $post_date = current_time('mysql');
427         $post_date_gmt = current_time('mysql', 1);
428
429         $post_data = compact('blog_ID', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt');
430
431         log_app('Inserting Post. Data:', print_r($post_data,true));
432
433         $postID = wp_insert_post($post_data);
434
435         if (!$postID) {
436             $this->internal_error('Sorry, your entry could not be posted. Something wrong happened.');
437         }
438
439         $output = $this->get_entry($postID);
440
441         log_app('function',"create_post($postID)");
442         $this->created($postID, $output);
443     }
444
445     function get_post($postID) {
446
447         global $entry;
448         $this->set_current_entry($postID);
449         $output = $this->get_entry($postID);
450         log_app('function',"get_post($postID)");
451         $this->output($output);
452
453     }
454
455     function put_post($postID) {
456
457         // checked for valid content-types (atom+xml)
458         // quick check and exit
459         $this->get_accepted_content_type($this->atom_content_types);
460
461         $parser = new AtomParser();
462         if(!$parser->parse()) {
463             $this->bad_request();
464         }
465
466         $parsed = $parser->entry;
467
468         // check for not found
469         global $entry;
470         $entry = $GLOBALS['entry'];
471         $this->set_current_entry($postID);
472         $this->escape($GLOBALS['entry']);
473
474         if(!current_user_can('edit_post', $entry['ID']))
475             $this->auth_required('Sorry, you do not have the right to edit this post.');
476
477         $publish = (isset($parsed->draft) && trim($parsed->draft) == 'yes') ? false : true;
478
479         extract($entry);
480
481         $post_title = $parsed->title;
482         $post_content = $parsed->content;
483         $post_excerpt = $parsed->summary;
484
485         // let's not go backwards and make something draft again.
486         if(!$publish && $post_status == 'draft') {
487             $post_status = ($publish) ? 'publish' : 'draft';
488         }
489
490         $postdata = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt');
491
492