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