Ticket #4899: class.app.patch
| File class.app.patch, 127.0 KB (added by , 19 years ago) |
|---|
-
wp-app.php
50 50 } 51 51 add_filter('posts_where', 'wa_posts_where_include_drafts_filter'); 52 52 53 class AtomServer { 53 require_once(ABSPATH . WPINC . '/class/AtomServer.php'); 54 54 55 var $ATOM_CONTENT_TYPE = 'application/atom+xml';56 var $CATEGORIES_CONTENT_TYPE = 'application/atomcat+xml';57 var $SERVICE_CONTENT_TYPE = 'application/atomsvc+xml';58 59 var $ATOM_NS = 'http://www.w3.org/2005/Atom';60 var $ATOMPUB_NS = 'http://www.w3.org/2007/app';61 62 var $ENTRIES_PATH = "posts";63 var $CATEGORIES_PATH = "categories";64 var $MEDIA_PATH = "attachments";65 var $ENTRY_PATH = "post";66 var $SERVICE_PATH = "service";67 var $MEDIA_SINGLE_PATH = "attachment";68 69 var $params = array();70 var $script_name = "wp-app.php";71 var $media_content_types = array('image/*','audio/*','video/*');72 var $atom_content_types = array('application/atom+xml');73 74 var $selectors = array();75 76 // support for head77 var $do_output = true;78 79 function AtomServer() {80 81 $this->script_name = array_pop(explode('/',$_SERVER['SCRIPT_NAME']));82 83 $this->selectors = array(84 '@/service$@' =>85 array('GET' => 'get_service'),86 '@/categories$@' =>87 array('GET' => 'get_categories_xml'),88 '@/post/(\d+)$@' =>89 array('GET' => 'get_post',90 'PUT' => 'put_post',91 'DELETE' => 'delete_post'),92 '@/posts/?(\d+)?$@' =>93 array('GET' => 'get_posts',94 'POST' => 'create_post'),95 '@/attachments/?(\d+)?$@' =>96 array('GET' => 'get_attachment',97 'POST' => 'create_attachment'),98 '@/attachment/file/(\d+)$@' =>99 array('GET' => 'get_file',100 'PUT' => 'put_file',101 'DELETE' => 'delete_file'),102 '@/attachment/(\d+)$@' =>103 array('GET' => 'get_attachment',104 'PUT' => 'put_attachment',105 'DELETE' => 'delete_attachment'),106 );107 }108 109 function handle_request() {110 global $always_authenticate;111 112 $path = $_SERVER['PATH_INFO'];113 $method = $_SERVER['REQUEST_METHOD'];114 115 log_app('REQUEST',"$method $path\n================");116 117 $this->process_conditionals();118 //$this->process_conditionals();119 120 // exception case for HEAD (treat exactly as GET, but don't output)121 if($method == 'HEAD') {122 $this->do_output = false;123 $method = 'GET';124 }125 126 // redirect to /service in case no path is found.127 if(strlen($path) == 0 || $path == '/') {128 $this->redirect($this->get_service_url());129 }130 131 // dispatch132 foreach($this->selectors as $regex => $funcs) {133 if(preg_match($regex, $path, $matches)) {134 if(isset($funcs[$method])) {135 136 // authenticate regardless of the operation and set the current137 // user. each handler will decide if auth is required or not.138 $this->authenticate();139 $u = wp_get_current_user();140 if(!isset($u) || $u->ID == 0) {141 if ($always_authenticate) {142 $this->auth_required('Credentials required.');143 }144 }145 146 array_shift($matches);147 call_user_func_array(array(&$this,$funcs[$method]), $matches);148 exit();149 } else {150 // only allow what we have handlers for...151 $this->not_allowed(array_keys($funcs));152 }153 }154 }155 156 // oops, nothing found157 $this->not_found();158 }159 160 function get_service() {161 log_app('function','get_service()');162 $entries_url = attribute_escape($this->get_entries_url());163 $categories_url = attribute_escape($this->get_categories_url());164 $media_url = attribute_escape($this->get_attachments_url());165 foreach ($this->media_content_types as $med) {166 $accepted_media_types = $accepted_media_types . "<accept>" . $med . "</accept>";167 }168 $atom_prefix="atom";169 $service_doc = <<<EOD170 <service xmlns="$this->ATOMPUB_NS" xmlns:$atom_prefix="$this->ATOM_NS">171 <workspace>172 <$atom_prefix:title>WordPress Workspace</$atom_prefix:title>173 <collection href="$entries_url">174 <$atom_prefix:title>WordPress Posts</$atom_prefix:title>175 <accept>$this->ATOM_CONTENT_TYPE;type=entry</accept>176 <categories href="$categories_url" />177 </collection>178 <collection href="$media_url">179 <$atom_prefix:title>WordPress Media</$atom_prefix:title>180 $accepted_media_types181 </collection>182 </workspace>183 </service>184 185 EOD;186 187 $this->output($service_doc, $this->SERVICE_CONTENT_TYPE);188 }189 190 function get_categories_xml() {191 192 log_app('function','get_categories_xml()');193 $home = attribute_escape(get_bloginfo_rss('home'));194 195 $categories = "";196 $cats = get_categories("hierarchical=0&hide_empty=0");197 foreach ((array) $cats as $cat) {198 $categories .= " <category term=\"" . attribute_escape($cat->cat_name) . "\" />\n";199 }200 $output = <<<EOD201 <app:categories xmlns:app="$this->ATOMPUB_NS"202 xmlns="$this->ATOM_NS"203 fixed="yes" scheme="$home">204 $categories205 </app:categories>206 EOD;207 $this->output($output, $this->CATEGORIES_CONTENT_TYPE);208 }209 210 /*211 * Create Post (No arguments)212 */213 function create_post() {214 global $blog_id, $wpdb;215 $this->get_accepted_content_type($this->atom_content_types);216 217 $parser = new AtomParser();218 if(!$parser->parse()) {219 $this->client_error();220 }221 222 $entry = array_pop($parser->feed->entries);223 224 log_app('Received entry:', print_r($entry,true));225 226 $catnames = array();227 foreach($entry->categories as $cat)228 array_push($catnames, $cat["term"]);229 230 $wp_cats = get_categories(array('hide_empty' => false));231 232 $post_category = array();233 234 foreach($wp_cats as $cat) {235 if(in_array($cat->cat_name, $catnames))236 array_push($post_category, $cat->cat_ID);237 }238 239 $publish = (isset($entry->draft) && trim($entry->draft) == 'yes') ? false : true;240 241 $cap = ($publish) ? 'publish_posts' : 'edit_posts';242 243 if(!current_user_can($cap))244 $this->auth_required(__('Sorry, you do not have the right to edit/publish new posts.'));245 246 $blog_ID = (int ) $blog_id;247 $post_status = ($publish) ? 'publish' : 'draft';248 $post_author = (int) $user->ID;249 $post_title = $entry->title[1];250 $post_content = $entry->content[1];251 $post_excerpt = $entry->summary[1];252 $pubtimes = $this->get_publish_time($entry);253 $post_date = $pubtimes[0];254 $post_date_gmt = $pubtimes[1];255 256 if ( isset( $_SERVER['HTTP_SLUG'] ) )257 $post_name = $_SERVER['HTTP_SLUG'];258 259 $post_data = compact('blog_ID', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'post_name');260 261 $this->escape($post_data);262 log_app('Inserting Post. Data:', print_r($post_data,true));263 264 $postID = wp_insert_post($post_data);265 266 if (!$postID) {267 $this->internal_error(__('Sorry, your entry could not be posted. Something wrong happened.'));268 }269 270 // getting warning here about unable to set headers271 // because something in the cache is printing to the buffer272 // could we clean up wp_set_post_categories or cache to not print273 // this could affect our ability to send back the right headers274 @wp_set_post_categories($postID, $post_category);275 276 $output = $this->get_entry($postID);277 278 log_app('function',"create_post($postID)");279 $this->created($postID, $output);280 }281 282 function get_post($postID) {283 284 global $entry;285 $this->set_current_entry($postID);286 $output = $this->get_entry($postID);287 log_app('function',"get_post($postID)");288 $this->output($output);289 290 }291 292 function put_post($postID) {293 global $wpdb;294 295 // checked for valid content-types (atom+xml)296 // quick check and exit297 $this->get_accepted_content_type($this->atom_content_types);298 299 $parser = new AtomParser();300 if(!$parser->parse()) {301 $this->bad_request();302 }303 304 $parsed = array_pop($parser->feed->entries);305 306 log_app('Received UPDATED entry:', print_r($parsed,true));307 308 // check for not found309 global $entry;310 $entry = $GLOBALS['entry'];311 $this->set_current_entry($postID);312 313 if(!current_user_can('edit_post', $entry['ID']))314 $this->auth_required(__('Sorry, you do not have the right to edit this post.'));315 316 $publish = (isset($parsed->draft) && trim($parsed->draft) == 'yes') ? false : true;317 318 extract($entry);319 320 $post_title = $parsed->title[1];321 $post_content = $parsed->content[1];322 $post_excerpt = $parsed->summary[1];323 $pubtimes = $this->get_publish_time($entry);324 $post_date = $pubtimes[0];325 $post_date_gmt = $pubtimes[1];326 327 // let's not go backwards and make something draft again.328 if(!$publish && $post_status == 'draft') {329 $post_status = ($publish) ? 'publish' : 'draft';330 } elseif($publish) {331 $post_status = 'publish';332 }333 334 $postdata = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'post_date', 'post_date_gmt');335 $this->escape($postdata);336 337 $result = wp_update_post($postdata);338 339 if (!$result) {340 $this->internal_error(__('For some strange yet very annoying reason, this post could not be edited.'));341 }342 343 log_app('function',"put_post($postID)");344 $this->ok();345 }346 347 function delete_post($postID) {348 349 // check for not found350 global $entry;351 $this->set_current_entry($postID);352 353 if(!current_user_can('edit_post', $postID)) {354 $this->auth_required(__('Sorry, you do not have the right to delete this post.'));355 }356 357 if ($entry['post_type'] == 'attachment') {358 $this->delete_attachment($postID);359 } else {360 $result = wp_delete_post($postID);361 362 if (!$result) {363 $this->internal_error(__('For some strange yet very annoying reason, this post could not be deleted.'));364 }365 366 log_app('function',"delete_post($postID)");367 $this->ok();368 }369 370 }371 372 function get_attachment($postID = NULL) {373 374 global $entry;375 if (!isset($postID)) {376 $this->get_attachments();377 } else {378 $this->set_current_entry($postID);379 $output = $this->get_entry($postID, 'attachment');380 log_app('function',"get_attachment($postID)");381 $this->output($output);382 }383 }384 385 function create_attachment() {386 global $wp, $wpdb, $wp_query, $blog_id;387 388 $type = $this->get_accepted_content_type();389 390 if(!current_user_can('upload_files'))391 $this->auth_required(__('You do not have permission to upload files.'));392 393 $fp = fopen("php://input", "rb");394 $bits = NULL;395 while(!feof($fp)) {396 $bits .= fread($fp, 4096);397 }398 fclose($fp);399 400 $slug = '';401 if ( isset( $_SERVER['HTTP_SLUG'] ) )402 $slug = sanitize_file_name( $_SERVER['HTTP_SLUG'] );403 elseif ( isset( $_SERVER['HTTP_TITLE'] ) )404 $slug = sanitize_file_name( $_SERVER['HTTP_TITLE'] );405 elseif ( empty( $slug ) ) // just make a random name406 $slug = substr( md5( uniqid( microtime() ) ), 0, 7);407 $ext = preg_replace( '|.*/([a-z]+)|', '$1', $_SERVER['CONTENT_TYPE'] );408 $slug = "$slug.$ext";409 $file = wp_upload_bits( $slug, NULL, $bits);410 411 log_app('wp_upload_bits returns:',print_r($file,true));412 413 $url = $file['url'];414 $file = $file['file'];415 $filename = basename($file);416 417 $header = apply_filters('wp_create_file_in_uploads', $file); // replicate418 419 // Construct the attachment array420 $attachment = array(421 'post_title' => $slug,422 'post_content' => $slug,423 'post_status' => 'attachment',424 'post_parent' => 0,425 'post_mime_type' => $type,426 'guid' => $url427 );428 429 // Save the data430 $postID = wp_insert_attachment($attachment, $file, $post);431 432 if (!$postID) {433 $this->internal_error(__('Sorry, your entry could not be posted. Something wrong happened.'));434 }435 436 $output = $this->get_entry($postID, 'attachment');437 438 $this->created($postID, $output, 'attachment');439 log_app('function',"create_attachment($postID)");440 }441 442 function put_attachment($postID) {443 global $wpdb;444 445 // checked for valid content-types (atom+xml)446 // quick check and exit447 $this->get_accepted_content_type($this->atom_content_types);448 449 $parser = new AtomParser();450 if(!$parser->parse()) {451 $this->bad_request();452 }453 454 $parsed = array_pop($parser->feed->entries);455 456 // check for not found457 global $entry;458 $this->set_current_entry($postID);459 460 if(!current_user_can('edit_post', $entry['ID']))461 $this->auth_required(__('Sorry, you do not have the right to edit this post.'));462 463 $publish = (isset($parsed->draft) && trim($parsed->draft) == 'yes') ? false : true;464 465 extract($entry);466 467 $post_title = $parsed->title[1];468 $post_content = $parsed->content[1];469 470 $postdata = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt');471 $this->escape($postdata);472 473 $result = wp_update_post($postdata);474 475 if (!$result) {476 $this->internal_error(__('For some strange yet very annoying reason, this post could not be edited.'));477 }478 479 log_app('function',"put_attachment($postID)");480 $this->ok();481 }482 483 function delete_attachment($postID) {484 log_app('function',"delete_attachment($postID). File '$location' deleted.");485 486 // check for not found487 global $entry;488 $this->set_current_entry($postID);489 490 if(!current_user_can('edit_post', $postID)) {491 $this->auth_required(__('Sorry, you do not have the right to delete this post.'));492 }493 494 $location = get_post_meta($entry['ID'], '_wp_attached_file', true);495 496 // delete file497 @unlink($location);498 499 // delete attachment500 $result = wp_delete_post($postID);501 502 if (!$result) {503 $this->internal_error(__('For some strange yet very annoying reason, this post could not be deleted.'));504 }505 506 log_app('function',"delete_attachment($postID). File '$location' deleted.");507 $this->ok();508 }509 510 function get_file($postID) {511 512 // check for not found513 global $entry;514 $this->set_current_entry($postID);515 516 // then whether user can edit the specific post517 if(!current_user_can('edit_post', $postID)) {518 $this->auth_required(__('Sorry, you do not have the right to edit this post.'));519 }520 521 $location = get_post_meta($entry['ID'], '_wp_attached_file', true);522 $filetype = wp_check_filetype($location);523 524 if(!isset($location) || 'attachment' != $entry['post_type'] || empty($filetype['ext']))525 $this->internal_error(__('Error ocurred while accessing post metadata for file location.'));526 527 status_header('200');528 header('Content-Type: ' . $entry['post_mime_type']);529 header('Connection: close');530 531 $fp = fopen($location, "rb");532 while(!feof($fp)) {533 echo fread($fp, 4096);534 }535 fclose($fp);536 537 log_app('function',"get_file($postID)");538 exit;539 }540 541 function put_file($postID) {542 543 $type = $this->get_accepted_content_type();544 545 // first check if user can upload546 if(!current_user_can('upload_files'))547 $this->auth_required(__('You do not have permission to upload files.'));548 549 // check for not found550 global $entry;551 $this->set_current_entry($postID);552 553 // then whether user can edit the specific post554 if(!current_user_can('edit_post', $postID)) {555 $this->auth_required(__('Sorry, you do not have the right to edit this post.'));556 }557 558 $location = get_post_meta($entry['ID'], '_wp_attached_file', true);559 $filetype = wp_check_filetype($location);560 561 if(!isset($location) || 'attachment' != $entry['post_type'] || empty($filetype['ext']))562 $this->internal_error(__('Error ocurred while accessing post metadata for file location.'));563 564 $fp = fopen("php://input", "rb");565 $localfp = fopen($location, "w+");566 while(!feof($fp)) {567 fwrite($localfp,fread($fp, 4096));568 }569 fclose($fp);570 fclose($localfp);571 572 $ID = $entry['ID'];573 $pubtimes = $this->get_publish_time($entry);574 $post_date = $pubtimes[0];575 $post_date_gmt = $pubtimes[1];576 577 $post_data = compact('ID', 'post_date', 'post_date_gmt');578 $result = wp_update_post($post_data);579 580 if (!$result) {581 $this->internal_error(__('Sorry, your entry could not be posted. Something wrong happened.'));582 }583 584 log_app('function',"put_file($postID)");585 $this->ok();586 }587 588 function get_entries_url($page = NULL) {589 if($GLOBALS['post_type'] == 'attachment') {590 $path = $this->MEDIA_PATH;591 } else {592 $path = $this->ENTRIES_PATH;593 }594 $url = get_bloginfo('url') . '/' . $this->script_name . '/' . $path;595 if(isset($page) && is_int($page)) {596 $url .= "/$page";597 }598 return $url;599 }600 601 function the_entries_url($page = NULL) {602 $url = $this->get_entries_url($page);603 echo $url;604 }605 606 function get_categories_url($page = NULL) {607 return get_bloginfo('url') . '/' . $this->script_name . '/' . $this->CATEGORIES_PATH;608 }609 610 function the_categories_url() {611 $url = $this->get_categories_url();612 echo $url;613 }614 615 function get_attachments_url($page = NULL) {616 $url = get_bloginfo('url') . '/' . $this->script_name . '/' . $this->MEDIA_PATH;617 if(isset($page) && is_int($page)) {618 $url .= "/$page";619 }620 return $url;621 }622 623 function the_attachments_url($page = NULL) {624 $url = $this->get_attachments_url($page);625 echo $url;626 }627 628 function get_service_url() {629 return get_bloginfo('url') . '/' . $this->script_name . '/' . $this->SERVICE_PATH;630 }631 632 function get_entry_url($postID = NULL) {633 if(!isset($postID)) {634 global $post;635 $postID = (int) $GLOBALS['post']->ID;636 }637 638 $url = get_bloginfo('url') . '/' . $this->script_name . '/' . $this->ENTRY_PATH . "/$postID";639 640 log_app('function',"get_entry_url() = $url");641 return $url;642 }643 644 function the_entry_url($postID = NULL) {645 $url = $this->get_entry_url($postID);646 echo $url;647 }648 649 function get_media_url($postID = NULL) {650 if(!isset($postID)) {651 global $post;652 $postID = (int) $GLOBALS['post']->ID;653 }654 655 $url = get_bloginfo('url') . '/' . $this->script_name . '/' . $this->MEDIA_SINGLE_PATH ."/file/$postID";656 657 log_app('function',"get_media_url() = $url");658 return $url;659 }660 661 function the_media_url($postID = NULL) {662 $url = $this->get_media_url($postID);663 echo $url;664 }665 666 function set_current_entry($postID) {667 global $entry;668 log_app('function',"set_current_entry($postID)");669 670 if(!isset($postID)) {671 // $this->bad_request();672 $this->not_found();673 }674 675 $entry = wp_get_single_post($postID,ARRAY_A);676 677 if(!isset($entry) || !isset($entry['ID']))678 $this->not_found();679 680 return;681 }682 683 function get_posts($page = 1, $post_type = 'post') {684 log_app('function',"get_posts($page, '$post_type')");685 $feed = $this->get_feed($page, $post_type);686 $this->output($feed);687 }688 689 function get_attachments($page = 1, $post_type = 'attachment') {690 log_app('function',"get_attachments($page, '$post_type')");691 $GLOBALS['post_type'] = $post_type;692 $feed = $this->get_feed($page, $post_type);693 $this->output($feed);694 }695 696 function get_feed($page = 1, $post_type = 'post') {697 global $post, $wp, $wp_query, $posts, $wpdb, $blog_id, $post_cache;698 log_app('function',"get_feed($page, '$post_type')");699 ob_start();700 701 if(!isset($page)) {702 $page = 1;703 }704 $page = (int) $page;705 706 $count = get_option('posts_per_rss');707 708 wp('what_to_show=posts&posts_per_page=' . $count . '&offset=' . ($count * ($page-1) ));709 710 $post = $GLOBALS['post'];711 $posts = $GLOBALS['posts'];712 $wp = $GLOBALS['wp'];713 $wp_query = $GLOBALS['wp_query'];714 $wpdb = $GLOBALS['wpdb'];715 $blog_id = (int) $GLOBALS['blog_id'];716 $post_cache = $GLOBALS['post_cache'];717 log_app('function',"query_posts(# " . print_r($wp_query, true) . "#)");718 719 log_app('function',"total_count(# $wp_query->max_num_pages #)");720 $last_page = $wp_query->max_num_pages;721 $next_page = (($page + 1) > $last_page) ? NULL : $page + 1;722 $prev_page = ($page - 1) < 1 ? NULL : $page - 1;723 $last_page = ((int)$last_page == 1 || (int)$last_page == 0) ? NULL : (int) $last_page;724 $self_page = $page > 1 ? $page : NULL;725 ?><feed xmlns="<?php echo $this->ATOM_NS ?>" xmlns:app="<?php echo $this->ATOMPUB_NS ?>" xml:lang="<?php echo get_option('rss_language'); ?>">726 <id><?php $this->the_entries_url() ?></id>727 <updated><?php echo mysql2date('Y-m-d\TH:i:s\Z', get_lastpostmodified('GMT')); ?></updated>728 <title type="text"><?php bloginfo_rss('name') ?></title>729 <subtitle type="text"><?php bloginfo_rss("description") ?></subtitle>730 <link rel="first" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url() ?>" />731 <?php if(isset($prev_page)): ?>732 <link rel="previous" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($prev_page) ?>" />733 <?php endif; ?>734 <?php if(isset($next_page)): ?>735 <link rel="next" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($next_page) ?>" />736 <?php endif; ?>737 <link rel="last" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($last_page) ?>" />738 <link rel="self" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($self_page) ?>" />739 <rights type="text">Copyright <?php echo mysql2date('Y', get_lastpostdate('blog')); ?></rights>740 <generator uri="http://wordpress.com/" version="1.0.5-dc">WordPress.com Atom API</generator>741 <?php if ( have_posts() ) {742 while ( have_posts() ) {743 the_post();744 $this->echo_entry();745 }746 }747 ?></feed>748 <?php749 $feed = ob_get_contents();750 ob_end_clean();751 return $feed;752 }753 754 function get_entry($postID, $post_type = 'post') {755 log_app('function',"get_entry($postID, '$post_type')");756 ob_start();757 global $posts, $post, $wp_query, $wp, $wpdb, $blog_id, $post_cache;758 switch($post_type) {759 case 'post':760 $varname = 'p';761 break;762 case 'attachment':763 $varname = 'attachment_id';764 break;765 }766 query_posts($varname . '=' . $postID);767 if ( have_posts() ) {768 while ( have_posts() ) {769 the_post();770 $this->echo_entry();771 log_app('$post',print_r($GLOBALS['post'],true));772 $entry = ob_get_contents();773 break;774 }775 }776 ob_end_clean();777 778 log_app('get_entry returning:',$entry);779 return $entry;780 }781 782 function echo_entry() { ?>783 <entry xmlns="<?php echo $this->ATOM_NS ?>"784 xmlns:app="<?php echo $this->ATOMPUB_NS ?>" xml:lang="<?php echo get_option('rss_language'); ?>">785 <id><?php the_guid($GLOBALS['post']->ID); ?></id>786 <?php list($content_type, $content) = $this->prep_content(get_the_title()); ?>787 <title type="<?php echo $content_type ?>"><?php echo $content ?></title>788 <updated><?php echo get_post_modified_time('Y-m-d\TH:i:s\Z', true); ?></updated>789 <published><?php echo get_post_time('Y-m-d\TH:i:s\Z', true); ?></published>790 <app:edited><?php echo get_post_modified_time('Y-m-d\TH:i:s\Z', true); ?></app:edited>791 <app:control>792 <app:draft><?php echo ($GLOBALS['post']->post_status == 'draft' ? 'yes' : 'no') ?></app:draft>793 </app:control>794 <author>795 <name><?php the_author()?></name>796 <email><?php the_author_email()?></email>797 <?php if (get_the_author_url() && get_the_author_url() != 'http://') { ?>798 <uri><?php the_author_url()?></uri>799 <?php } ?>800 </author>801 <?php if($GLOBALS['post']->post_type == 'attachment') { ?>802 <link rel="edit-media" href="<?php $this->the_media_url() ?>" />803 <content type="<?php echo $GLOBALS['post']->post_mime_type ?>" src="<?php the_guid(); ?>"/>804 <?php } else { ?>805 <link href="<?php the_permalink_rss() ?>" />806 <?php if ( strlen( $GLOBALS['post']->post_content ) ) :807 list($content_type, $content) = $this->prep_content(get_the_content()); ?>808 <content type="<?php echo $content_type ?>"><?php echo $content ?></content>809 <?php endif; ?>810 <?php } ?>811 <link rel="edit" href="<?php $this->the_entry_url() ?>" />812 <?php foreach(get_the_category() as $category) { ?>813 <category scheme="<?php bloginfo_rss('home') ?>" term="<?php echo $category->cat_name?>" />814 <?php } ?>815 <?php list($content_type, $content) = $this->prep_content(get_the_excerpt()); ?>816 <summary type="<?php echo $content_type ?>"><?php echo $content ?></summary>817 </entry>818 <?php }819 820 function prep_content($data) {821 if (strpos($data, '<') === false && strpos($data, '&') === false) {822 return array('text', $data);823 }824 825 $parser = xml_parser_create();826 xml_parse($parser, '<div>' . $data . '</div>', true);827 $code = xml_get_error_code($parser);828 xml_parser_free($parser);829 830 if (!$code) {831 if (strpos($data, '<') === false) {832 return array('text', $data);833 } else {834 $data = "<div xmlns='http://www.w3.org/1999/xhtml'>$data</div>";835 return array('xhtml', $data);836 }837 }838 839 if (strpos($data, ']]>') == false) {840 return array('html', "<![CDATA[$data]]>");841 } else {842 return array('html', htmlspecialchars($data));843 }844 }845 846 function ok() {847 log_app('Status','200: OK');848 header('Content-Type: text/plain');849 status_header('200');850 exit;851 }852 853 function no_content() {854 log_app('Status','204: No Content');855 header('Content-Type: text/plain');856 status_header('204');857 echo "Deleted.";858 exit;859 }860 861 function internal_error($msg = 'Internal Server Error') {862 log_app('Status','500: Server Error');863 header('Content-Type: text/plain');864 status_header('500');865 echo $msg;866 exit;867 }868 869 function bad_request() {870 log_app('Status','400: Bad Request');871 header('Content-Type: text/plain');872 status_header('400');873 exit;874 }875 876 function length_required() {877 log_app('Status','411: Length Required');878 header("HTTP/1.1 411 Length Required");879 header('Content-Type: text/plain');880 status_header('411');881 exit;882 }883 884 function invalid_media() {885 log_app('Status','415: Unsupported Media Type');886 header("HTTP/1.1 415 Unsupported Media Type");887 header('Content-Type: text/plain');888 exit;889 }890 891 function not_found() {892 log_app('Status','404: Not Found');893 header('Content-Type: text/plain');894 status_header('404');895 exit;896 }897 898 function not_allowed($allow) {899 log_app('Status','405: Not Allowed');900 header('Allow: ' . join(',', $allow));901 status_header('405');902 exit;903 }904 905 function redirect($url) {906 907 log_app('Status','302: Redirect');908 $escaped_url = attribute_escape($url);909 $content = <<<EOD910 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">911 <html>912 <head>913 <title>302 Found</title>914 </head>915 <body>916 <h1>Found</h1>917 <p>The document has moved <a href="$escaped_url">here</a>.</p>918 </body>919 </html>920 921 EOD;922 header('HTTP/1.1 302 Moved');923 header('Content-Type: text/html');924 header('Location: ' . $url);925 echo $content;926 exit;927 928 }929 930 931 function client_error($msg = 'Client Error') {932 log_app('Status','400: Client Error');933 header('Content-Type: text/plain');934 status_header('400');935 exit;936 }937 938 function created($post_ID, $content, $post_type = 'post') {939 log_app('created()::$post_ID',"$post_ID, $post_type");940 $edit = $this->get_entry_url($post_ID);941 switch($post_type) {942 case 'post':943 $ctloc = $this->get_entry_url($post_ID);944 break;945 case 'attachment':946 $edit = get_bloginfo('url') . '/' . $this->script_name . "/attachments/$post_ID";947 break;948 }949 header("Content-Type: $this->ATOM_CONTENT_TYPE");950 if(isset($ctloc))951 header('Content-Location: ' . $ctloc);952 header('Location: ' . $edit);953 status_header('201');954 echo $content;955 exit;956 }957 958 function auth_required($msg) {959 log_app('Status','401: Auth Required');960 nocache_headers();961 header('WWW-Authenticate: Basic realm="WordPress Atom Protocol"');962 header("HTTP/1.1 401 $msg");963 header('Status: ' . $msg);964 header('Content-Type: text/html');965 $content = <<<EOD966 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">967 <html>968 <head>969 <title>401 Unauthorized</title>970 </head>971 <body>972 <h1>401 Unauthorized</h1>973 <p>$msg</p>974 </body>975 </html>976 977 EOD;978 echo $content;979 exit;980 }981 982 function output($xml, $ctype = 'application/atom+xml') {983 status_header('200');984 $xml = '<?xml version="1.0" encoding="' . strtolower(get_option('blog_charset')) . '"?>'."\n".$xml;985 header('Connection: close');986 header('Content-Length: '. strlen($xml));987 header('Content-Type: ' . $ctype);988 header('Content-Disposition: attachment; filename=atom.xml');989 header('Date: '. date('r'));990 if($this->do_output)991 echo $xml;992 log_app('function', "output:\n$xml");993 exit;994 }995 996 function escape(&$array) {997 global $wpdb;998 999 foreach ($array as $k => $v) {1000 if (is_array($v)) {1001 $this->escape($array[$k]);1002 } else if (is_object($v)) {1003 //skip1004 } else {1005 $array[$k] = $wpdb->escape($v);1006 }1007 }1008 }1009 1010 /*1011 * Access credential through various methods and perform login1012 */1013 function authenticate() {1014 $login_data = array();1015 $already_md5 = false;1016 1017 log_app("authenticate()",print_r($_ENV, true));1018 1019 // if using mod_rewrite/ENV hack1020 // http://www.besthostratings.com/articles/http-auth-php-cgi.html1021 if(isset($_SERVER['HTTP_AUTHORIZATION'])) {1022 list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) =1023 explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));1024 }1025 1026 // If Basic Auth is working...1027 if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {1028 $login_data = array('login' => $_SERVER['PHP_AUTH_USER'], 'password' => $_SERVER['PHP_AUTH_PW']);1029 log_app("Basic Auth",$login_data['login']);1030 } else {1031 // else, do cookie-based authentication1032 if (function_exists('wp_get_cookie_login')) {1033 $login_data = wp_get_cookie_login();1034 $already_md5 = true;1035 }1036 }1037 1038 // call wp_login and set current user1039 if (!empty($login_data) && wp_login($login_data['login'], $login_data['password'], $already_md5)) {1040 $current_user = new WP_User(0, $login_data['login']);1041 wp_set_current_user($current_user->ID);1042 log_app("authenticate()",$login_data['login']);1043 }1044 }1045 1046 function get_accepted_content_type($types = NULL) {1047 1048 if(!isset($types)) {1049 $types = $this->media_content_types;1050 }1051 1052 if(!isset($_SERVER['CONTENT_LENGTH']) || !isset($_SERVER['CONTENT_TYPE'])) {1053 $this->length_required();1054 }1055 1056 $type = $_SERVER['CONTENT_TYPE'];1057 list($type,$subtype) = explode('/',$type);1058 list($subtype) = explode(";",$subtype); // strip MIME parameters1059 log_app("get_accepted_content_type", "type=$type, subtype=$subtype");1060 1061 foreach($types as $t) {1062 list($acceptedType,$acceptedSubtype) = explode('/',$t);1063 if($acceptedType == '*' || $acceptedType == $type) {1064 if($acceptedSubtype == '*' || $acceptedSubtype == $subtype)1065 return $type . "/" . $subtype;1066 }1067 }1068 1069 $this->invalid_media();1070 }1071 1072 function process_conditionals() {1073 1074 if(empty($this->params)) return;1075 if($_SERVER['REQUEST_METHOD'] == 'DELETE') return;1076 1077 switch($this->params[0]) {1078 case $this->ENTRY_PATH:1079 global $post;1080 $post = wp_get_single_post($this->params[1]);1081 $wp_last_modified = get_post_modified_time('D, d M Y H:i:s', true);1082 $post = NULL;1083 break;1084 case $this->ENTRIES_PATH:1085 $wp_last_modified = mysql2date('D, d M Y H:i:s', get_lastpostmodified('GMT'), 0).' GMT';1086 break;1087 default:1088 return;1089 }1090 $wp_etag = md5($wp_last_modified);1091 @header("Last-Modified: $wp_last_modified");1092 @header("ETag: $wp_etag");1093 1094 // Support for Conditional GET1095 if (isset($_SERVER['HTTP_IF_NONE_MATCH']))1096 $client_etag = stripslashes($_SERVER['HTTP_IF_NONE_MATCH']);1097 else1098 $client_etag = false;1099 1100 $client_last_modified = trim( $_SERVER['HTTP_IF_MODIFIED_SINCE']);1101 // If string is empty, return 0. If not, attempt to parse into a timestamp1102 $client_modified_timestamp = $client_last_modified ? strtotime($client_last_modified) : 0;1103 1104 // Make a timestamp for our most recent modification...1105 $wp_modified_timestamp = strtotime($wp_last_modified);1106 1107 if ( ($client_last_modified && $client_etag) ?1108 (($client_modified_timestamp >= $wp_modified_timestamp) && ($client_etag == $wp_etag)) :1109 (($client_modified_timestamp >= $wp_modified_timestamp) || ($client_etag == $wp_etag)) ) {1110 status_header( 304 );1111 exit;1112 }1113 }1114 1115 function rfc3339_str2time($str) {1116 1117 $match = false;1118 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))1119 return false;1120 1121 if($match[3] == 'Z')1122 $match[3] == '+0000';1123 1124 return strtotime($match[1] . " " . $match[2] . " " . $match[3]);1125 }1126 1127 function get_publish_time($entry) {1128 1129 $pubtime = $this->rfc3339_str2time($entry->published);1130 1131 if(!$pubtime) {1132 return array(current_time('mysql'),current_time('mysql',1));1133 } else {1134 return array(date("Y-m-d H:i:s", $pubtime), gmdate("Y-m-d H:i:s", $pubtime));1135 }1136 }1137 1138 }1139 1140 55 $server = new AtomServer(); 1141 56 $server->handle_request(); 1142 57 -
wp-includes/class/IXR.php
1 <?php 2 /* 3 IXR - The Inutio XML-RPC Library - (c) Incutio Ltd 2002-2005 4 Version 1.7 (beta) - Simon Willison, 23rd May 2005 5 Site: http://scripts.incutio.com/xmlrpc/ 6 Manual: http://scripts.incutio.com/xmlrpc/manual.php 7 Made available under the BSD License: http://www.opensource.org/licenses/bsd-license.php 8 */ 9 10 class IXR_Value { 11 var $data; 12 var $type; 13 function IXR_Value ($data, $type = false) { 14 $this->data = $data; 15 if (!$type) { 16 $type = $this->calculateType(); 17 } 18 $this->type = $type; 19 if ($type == 'struct') { 20 /* Turn all the values in the array in to new IXR_Value objects */ 21 foreach ($this->data as $key => $value) { 22 $this->data[$key] = new IXR_Value($value); 23 } 24 } 25 if ($type == 'array') { 26 for ($i = 0, $j = count($this->data); $i < $j; $i++) { 27 $this->data[$i] = new IXR_Value($this->data[$i]); 28 } 29 } 30 } 31 function calculateType() { 32 if ($this->data === true || $this->data === false) { 33 return 'boolean'; 34 } 35 if (is_integer($this->data)) { 36 return 'int'; 37 } 38 if (is_double($this->data)) { 39 return 'double'; 40 } 41 // Deal with IXR object types base64 and date 42 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) { 43 return 'date'; 44 } 45 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) { 46 return 'base64'; 47 } 48 // If it is a normal PHP object convert it in to a struct 49 if (is_object($this->data)) { 50 51 $this->data = get_object_vars($this->data); 52 return 'struct'; 53 } 54 if (!is_array($this->data)) { 55 return 'string'; 56 } 57 /* We have an array - is it an array or a struct ? */ 58 if ($this->isStruct($this->data)) { 59 return 'struct'; 60 } else { 61 return 'array'; 62 } 63 } 64 function getXml() { 65 /* Return XML for this value */ 66 switch ($this->type) { 67 case 'boolean': 68 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>'; 69 break; 70 case 'int': 71 return '<int>'.$this->data.'</int>'; 72 break; 73 case 'double': 74 return '<double>'.$this->data.'</double>'; 75 break; 76 case 'string': 77 return '<string>'.htmlspecialchars($this->data).'</string>'; 78 break; 79 case 'array': 80 $return = '<array><data>'."\n"; 81 foreach ($this->data as $item) { 82 $return .= ' <value>'.$item->getXml()."</value>\n"; 83 } 84 $return .= '</data></array>'; 85 return $return; 86 break; 87 case 'struct': 88 $return = '<struct>'."\n"; 89 foreach ($this->data as $name => $value) { 90 $name = htmlspecialchars($name); 91 $return .= " <member><name>$name</name><value>"; 92 $return .= $value->getXml()."</value></member>\n"; 93 } 94 $return .= '</struct>'; 95 return $return; 96 break; 97 case 'date': 98 case 'base64': 99 return $this->data->getXml(); 100 break; 101 } 102 return false; 103 } 104 function isStruct($array) { 105 /* Nasty function to check if an array is a struct or not */ 106 $expected = 0; 107 foreach ($array as $key => $value) { 108 if ((string)$key != (string)$expected) { 109 return true; 110 } 111 $expected++; 112 } 113 return false; 114 } 115 } 116 117 118 class IXR_Message { 119 var $message; 120 var $messageType; // methodCall / methodResponse / fault 121 var $faultCode; 122 var $faultString; 123 var $methodName; 124 var $params; 125 // Current variable stacks 126 var $_arraystructs = array(); // The stack used to keep track of the current array/struct 127 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array 128 var $_currentStructName = array(); // A stack as well 129 var $_param; 130 var $_value; 131 var $_currentTag; 132 var $_currentTagContents; 133 // The XML parser 134 var $_parser; 135 function IXR_Message ($message) { 136 $this->message = $message; 137 } 138 function parse() { 139 // first remove the XML declaration 140 $this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message); 141 if (trim($this->message) == '') { 142 return false; 143 } 144 $this->_parser = xml_parser_create(); 145 // Set XML parser to take the case of tags in to account 146 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); 147 // Set XML parser callback functions 148 xml_set_object($this->_parser, $this); 149 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close'); 150 xml_set_character_data_handler($this->_parser, 'cdata'); 151 if (!xml_parse($this->_parser, $this->message)) { 152 /* die(sprintf('XML error: %s at line %d', 153 xml_error_string(xml_get_error_code($this->_parser)), 154 xml_get_current_line_number($this->_parser))); */ 155 return false; 156 } 157 xml_parser_free($this->_parser); 158 // Grab the error messages, if any 159 if ($this->messageType == 'fault') { 160 $this->faultCode = $this->params[0]['faultCode']; 161 $this->faultString = $this->params[0]['faultString']; 162 } 163 return true; 164 } 165 function tag_open($parser, $tag, $attr) { 166 $this->_currentTagContents = ''; 167 $this->currentTag = $tag; 168 switch($tag) { 169 case 'methodCall': 170 case 'methodResponse': 171 case 'fault': 172 $this->messageType = $tag; 173 break; 174 /* Deal with stacks of arrays and structs */ 175 case 'data': // data is to all intents and puposes more interesting than array 176 $this->_arraystructstypes[] = 'array'; 177 $this->_arraystructs[] = array(); 178 break; 179 case 'struct': 180 $this->_arraystructstypes[] = 'struct'; 181 $this->_arraystructs[] = array(); 182 break; 183 } 184 } 185 function cdata($parser, $cdata) { 186 $this->_currentTagContents .= $cdata; 187 } 188 function tag_close($parser, $tag) { 189 $valueFlag = false; 190 switch($tag) { 191 case 'int': 192 case 'i4': 193 $value = (int) trim($this->_currentTagContents); 194 $valueFlag = true; 195 break; 196 case 'double': 197 $value = (double) trim($this->_currentTagContents); 198 $valueFlag = true; 199 break; 200 case 'string': 201 $value = $this->_currentTagContents; 202 $valueFlag = true; 203 break; 204 case 'dateTime.iso8601': 205 $value = new IXR_Date(trim($this->_currentTagContents)); 206 // $value = $iso->getTimestamp(); 207 $valueFlag = true; 208 break; 209 case 'value': 210 // "If no type is indicated, the type is string." 211 if (trim($this->_currentTagContents) != '') { 212 $value = (string)$this->_currentTagContents; 213 $valueFlag = true; 214 } 215 break; 216 case 'boolean': 217 $value = (boolean) trim($this->_currentTagContents); 218 $valueFlag = true; 219 break; 220 case 'base64': 221 $value = base64_decode( trim( $this->_currentTagContents ) ); 222 $valueFlag = true; 223 break; 224 /* Deal with stacks of arrays and structs */ 225 case 'data': 226 case 'struct': 227 $value = array_pop($this->_arraystructs); 228 array_pop($this->_arraystructstypes); 229 $valueFlag = true; 230 break; 231 case 'member': 232 array_pop($this->_currentStructName); 233 break; 234 case 'name': 235 $this->_currentStructName[] = trim($this->_currentTagContents); 236 break; 237 case 'methodName': 238 $this->methodName = trim($this->_currentTagContents); 239 break; 240 } 241 if ($valueFlag) { 242 if (count($this->_arraystructs) > 0) { 243 // Add value to struct or array 244 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') { 245 // Add to struct 246 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value; 247 } else { 248 // Add to array 249 $this->_arraystructs[count($this->_arraystructs)-1][] = $value; 250 } 251 } else { 252 // Just add as a paramater 253 $this->params[] = $value; 254 } 255 } 256 $this->_currentTagContents = ''; 257 } 258 } 259 260 261 class IXR_Server { 262 var $data; 263 var $callbacks = array(); 264 var $message; 265 var $capabilities; 266 function IXR_Server($callbacks = false, $data = false) { 267 $this->setCapabilities(); 268 if ($callbacks) { 269 $this->callbacks = $callbacks; 270 } 271 $this->setCallbacks(); 272 $this->serve($data); 273 } 274 function serve($data = false) { 275 if (!$data) { 276 global $HTTP_RAW_POST_DATA; 277 if (!$HTTP_RAW_POST_DATA) { 278 die('XML-RPC server accepts POST requests only.'); 279 } 280 $data = $HTTP_RAW_POST_DATA; 281 } 282 $this->message = new IXR_Message($data); 283 if (!$this->message->parse()) { 284 $this->error(-32700, 'parse error. not well formed'); 285 } 286 if ($this->message->messageType != 'methodCall') { 287 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); 288 } 289 $result = $this->call($this->message->methodName, $this->message->params); 290 // Is the result an error? 291 if (is_a($result, 'IXR_Error')) { 292 $this->error($result); 293 } 294 // Encode the result 295 $r = new IXR_Value($result); 296 $resultxml = $r->getXml(); 297 // Create the XML 298 $xml = <<<EOD 299 <methodResponse> 300 <params> 301 <param> 302 <value> 303 $resultxml 304 </value> 305 </param> 306 </params> 307 </methodResponse> 308 309 EOD; 310 // Send it 311 $this->output($xml); 312 } 313 function call($methodname, $args) { 314 if (!$this->hasMethod($methodname)) { 315 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.'); 316 } 317 $method = $this->callbacks[$methodname]; 318 // Perform the callback and send the response 319 if (count($args) == 1) { 320 // If only one paramater just send that instead of the whole array 321 $args = $args[0]; 322 } 323 // Are we dealing with a function or a method? 324 if (substr($method, 0, 5) == 'this:') { 325 // It's a class method - check it exists 326 $method = substr($method, 5); 327 if (!method_exists($this, $method)) { 328 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.'); 329 } 330 // Call the method 331 $result = $this->$method($args); 332 } else { 333 // It's a function - does it exist? 334 if (is_array($method)) { 335 if (!method_exists($method[0], $method[1])) { 336 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.'); 337 } 338 } else if (!function_exists($method)) { 339 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.'); 340 } 341 // Call the function 342 $result = call_user_func($method, $args); 343 } 344 return $result; 345 } 346 347 function error($error, $message = false) { 348 // Accepts either an error object or an error code and message 349 if ($message && !is_object($error)) { 350 $error = new IXR_Error($error, $message); 351 } 352 $this->output($error->getXml()); 353 } 354 function output($xml) { 355 $xml = '<?xml version="1.0"?>'."\n".$xml; 356 $length = strlen($xml); 357 header('Connection: close'); 358 header('Content-Length: '.$length); 359 header('Content-Type: text/xml'); 360 header('Date: '.date('r')); 361 echo $xml; 362 exit; 363 } 364 function hasMethod($method) { 365 return in_array($method, array_keys($this->callbacks)); 366 } 367 function setCapabilities() { 368 // Initialises capabilities array 369 $this->capabilities = array( 370 'xmlrpc' => array( 371 'specUrl' => 'http://www.xmlrpc.com/spec', 372 'specVersion' => 1 373 ), 374 'faults_interop' => array( 375 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', 376 'specVersion' => 20010516 377 ), 378 'system.multicall' => array( 379 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', 380 'specVersion' => 1 381 ), 382 ); 383 } 384 function getCapabilities($args) { 385 return $this->capabilities; 386 } 387 function setCallbacks() { 388 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities'; 389 $this->callbacks['system.listMethods'] = 'this:listMethods'; 390 $this->callbacks['system.multicall'] = 'this:multiCall'; 391 } 392 function listMethods($args) { 393 // Returns a list of methods - uses array_reverse to ensure user defined 394 // methods are listed before server defined methods 395 return array_reverse(array_keys($this->callbacks)); 396 } 397 function multiCall($methodcalls) { 398 // See http://www.xmlrpc.com/discuss/msgReader$1208 399 $return = array(); 400 foreach ($methodcalls as $call) { 401 $method = $call['methodName']; 402 $params = $call['params']; 403 if ($method == 'system.multicall') { 404 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden'); 405 } else { 406 $result = $this->call($method, $params); 407 } 408 if (is_a($result, 'IXR_Error')) { 409 $return[] = array( 410 'faultCode' => $result->code, 411 'faultString' => $result->message 412 ); 413 } else { 414 $return[] = array($result); 415 } 416 } 417 return $return; 418 } 419 } 420 421 class IXR_Request { 422 var $method; 423 var $args; 424 var $xml; 425 function IXR_Request($method, $args) { 426 $this->method = $method; 427 $this->args = $args; 428 $this->xml = <<<EOD 429 <?xml version="1.0"?> 430 <methodCall> 431 <methodName>{$this->method}</methodName> 432 <params> 433 434 EOD; 435 foreach ($this->args as $arg) { 436 $this->xml .= '<param><value>'; 437 $v = new IXR_Value($arg); 438 $this->xml .= $v->getXml(); 439 $this->xml .= "</value></param>\n"; 440 } 441 $this->xml .= '</params></methodCall>'; 442 } 443 function getLength() { 444 return strlen($this->xml); 445 } 446 function getXml() { 447 return $this->xml; 448 } 449 } 450 451 452 class IXR_Client { 453 var $server; 454 var $port; 455 var $path; 456 var $useragent; 457 var $response; 458 var $message = false; 459 var $debug = false; 460 var $timeout; 461 // Storage place for an error message 462 var $error = false; 463 function IXR_Client($server, $path = false, $port = 80, $timeout = false) { 464 if (!$path) { 465 // Assume we have been given a URL instead 466 $bits = parse_url($server); 467 $this->server = $bits['host']; 468 $this->port = isset($bits['port']) ? $bits['port'] : 80; 469 $this->path = isset($bits['path']) ? $bits['path'] : '/'; 470 // Make absolutely sure we have a path 471 if (!$this->path) { 472 $this->path = '/'; 473 } 474 } else { 475 $this->server = $server; 476 $this->path = $path; 477 $this->port = $port; 478 } 479 $this->useragent = 'Incutio XML-RPC'; 480 $this->timeout = $timeout; 481 } 482 function query() { 483 $args = func_get_args(); 484 $method = array_shift($args); 485 $request = new IXR_Request($method, $args); 486 $length = $request->getLength(); 487 $xml = $request->getXml(); 488 $r = "\r\n"; 489 $request = "POST {$this->path} HTTP/1.0$r"; 490 $request .= "Host: {$this->server}$r"; 491 $request .= "Content-Type: text/xml$r"; 492 $request .= "User-Agent: {$this->useragent}$r"; 493 $request .= "Content-length: {$length}$r$r"; 494 $request .= $xml; 495 // Now send the request 496 if ($this->debug) { 497 echo '<pre>'.htmlspecialchars($request)."\n</pre>\n\n"; 498 } 499 if ($this->timeout) { 500 $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout); 501 } else { 502 $fp = @fsockopen($this->server, $this->port, $errno, $errstr); 503 } 504 if (!$fp) { 505 $this->error = new IXR_Error(-32300, "transport error - could not open socket: $errno $errstr"); 506 return false; 507 } 508 fputs($fp, $request); 509 $contents = ''; 510 $gotFirstLine = false; 511 $gettingHeaders = true; 512 while (!feof($fp)) { 513 $line = fgets($fp, 4096); 514 if (!$gotFirstLine) { 515 // Check line for '200' 516 if (strstr($line, '200') === false) { 517 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200'); 518 return false; 519 } 520 $gotFirstLine = true; 521 } 522 if (trim($line) == '') { 523 $gettingHeaders = false; 524 } 525 if (!$gettingHeaders) { 526 $contents .= trim($line)."\n"; 527 } 528 } 529 if ($this->debug) { 530 echo '<pre>'.htmlspecialchars($contents)."\n</pre>\n\n"; 531 } 532 // Now parse what we've got back 533 $this->message = new IXR_Message($contents); 534 if (!$this->message->parse()) { 535 // XML error 536 $this->error = new IXR_Error(-32700, 'parse error. not well formed'); 537 return false; 538 } 539 // Is the message a fault? 540 if ($this->message->messageType == 'fault') { 541 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString); 542 return false; 543 } 544 // Message must be OK 545 return true; 546 } 547 function getResponse() { 548 // methodResponses can only have one param - return that 549 return $this->message->params[0]; 550 } 551 function isError() { 552 return (is_object($this->error)); 553 } 554 function getErrorCode() { 555 return $this->error->code; 556 } 557 function getErrorMessage() { 558 return $this->error->message; 559 } 560 } 561 562 563 class IXR_Error { 564 var $code; 565 var $message; 566 function IXR_Error($code, $message) { 567 $this->code = $code; 568 $this->message = $message; 569 } 570 function getXml() { 571 $xml = <<<EOD 572 <methodResponse> 573 <fault> 574 <value> 575 <struct> 576 <member> 577 <name>faultCode</name> 578 <value><int>{$this->code}</int></value> 579 </member> 580 <member> 581 <name>faultString</name> 582 <value><string>{$this->message}</string></value> 583 </member> 584 </struct> 585 </value> 586 </fault> 587 </methodResponse> 588 589 EOD; 590 return $xml; 591 } 592 } 593 594 595 class IXR_Date { 596 var $year; 597 var $month; 598 var $day; 599 var $hour; 600 var $minute; 601 var $second; 602 function IXR_Date($time) { 603 // $time can be a PHP timestamp or an ISO one 604 if (is_numeric($time)) { 605 $this->parseTimestamp($time); 606 } else { 607 $this->parseIso($time); 608 } 609 } 610 function parseTimestamp($timestamp) { 611 $this->year = date('Y', $timestamp); 612 $this->month = date('m', $timestamp); 613 $this->day = date('d', $timestamp); 614 $this->hour = date('H', $timestamp); 615 $this->minute = date('i', $timestamp); 616 $this->second = date('s', $timestamp); 617 } 618 function parseIso($iso) { 619 $this->year = substr($iso, 0, 4); 620 $this->month = substr($iso, 4, 2); 621 $this->day = substr($iso, 6, 2); 622 $this->hour = substr($iso, 9, 2); 623 $this->minute = substr($iso, 12, 2); 624 $this->second = substr($iso, 15, 2); 625 $this->timezone = substr($iso, 17); 626 } 627 function getIso() { 628 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone; 629 } 630 function getXml() { 631 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>'; 632 } 633 function getTimestamp() { 634 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year); 635 } 636 } 637 638 639 class IXR_Base64 { 640 var $data; 641 function IXR_Base64($data) { 642 $this->data = $data; 643 } 644 function getXml() { 645 return '<base64>'.base64_encode($this->data).'</base64>'; 646 } 647 } 648 649 650 class IXR_IntrospectionServer extends IXR_Server { 651 var $signatures; 652 var $help; 653 function IXR_IntrospectionServer() { 654 $this->setCallbacks(); 655 $this->setCapabilities(); 656 $this->capabilities['introspection'] = array( 657 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html', 658 'specVersion' => 1 659 ); 660 $this->addCallback( 661 'system.methodSignature', 662 'this:methodSignature', 663 array('array', 'string'), 664 'Returns an array describing the return type and required parameters of a method' 665 ); 666 $this->addCallback( 667 'system.getCapabilities', 668 'this:getCapabilities', 669 array('struct'), 670 'Returns a struct describing the XML-RPC specifications supported by this server' 671 ); 672 $this->addCallback( 673 'system.listMethods', 674 'this:listMethods', 675 array('array'), 676 'Returns an array of available methods on this server' 677 ); 678 $this->addCallback( 679 'system.methodHelp', 680 'this:methodHelp', 681 array('string', 'string'), 682 'Returns a documentation string for the specified method' 683 ); 684 } 685 function addCallback($method, $callback, $args, $help) { 686 $this->callbacks[$method] = $callback; 687 $this->signatures[$method] = $args; 688 $this->help[$method] = $help; 689 } 690 function call($methodname, $args) { 691 // Make sure it's in an array 692 if ($args && !is_array($args)) { 693 $args = array($args); 694 } 695 // Over-rides default call method, adds signature check 696 if (!$this->hasMethod($methodname)) { 697 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.'); 698 } 699 $method = $this->callbacks[$methodname]; 700 $signature = $this->signatures[$methodname]; 701 $returnType = array_shift($signature); 702 // Check the number of arguments 703 if (count($args) != count($signature)) { 704 return new IXR_Error(-32602, 'server error. wrong number of method parameters'); 705 } 706 // Check the argument types 707 $ok = true; 708 $argsbackup = $args; 709 for ($i = 0, $j = count($args); $i < $j; $i++) { 710 $arg = array_shift($args); 711 $type = array_shift($signature); 712 switch ($type) { 713 case 'int': 714 case 'i4': 715 if (is_array($arg) || !is_int($arg)) { 716 $ok = false; 717 } 718 break; 719 case 'base64': 720 case 'string': 721 if (!is_string($arg)) { 722 $ok = false; 723 } 724 break; 725 case 'boolean': 726 if ($arg !== false && $arg !== true) { 727 $ok = false; 728 } 729 break; 730 case 'float': 731 case 'double': 732 if (!is_float($arg)) { 733 $ok = false; 734 } 735 break; 736 case 'date': 737 case 'dateTime.iso8601': 738 if (!is_a($arg, 'IXR_Date')) { 739 $ok = false; 740 } 741 break; 742 } 743 if (!$ok) { 744 return new IXR_Error(-32602, 'server error. invalid method parameters'); 745 } 746 } 747 // It passed the test - run the "real" method call 748 return parent::call($methodname, $argsbackup); 749 } 750 function methodSignature($method) { 751 if (!$this->hasMethod($method)) { 752 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.'); 753 } 754 // We should be returning an array of types 755 $types = $this->signatures[$method]; 756 $return = array(); 757 foreach ($types as $type) { 758 switch ($type) { 759 case 'string': 760 $return[] = 'string'; 761 break; 762 case 'int': 763 case 'i4': 764 $return[] = 42; 765 break; 766 case 'double': 767 $return[] = 3.1415; 768 break; 769 case 'dateTime.iso8601': 770 $return[] = new IXR_Date(time()); 771 break; 772 case 'boolean': 773 $return[] = true; 774 break; 775 case 'base64': 776 $return[] = new IXR_Base64('base64'); 777 break; 778 case 'array': 779 $return[] = array('array'); 780 break; 781 case 'struct': 782 $return[] = array('struct' => 'struct'); 783 break; 784 } 785 } 786 return $return; 787 } 788 function methodHelp($method) { 789 return $this->help[$method]; 790 } 791 } 792 793 794 class IXR_ClientMulticall extends IXR_Client { 795 var $calls = array(); 796 function IXR_ClientMulticall($server, $path = false, $port = 80) { 797 parent::IXR_Client($server, $path, $port); 798 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)'; 799 } 800 function addCall() { 801 $args = func_get_args(); 802 $methodName = array_shift($args); 803 $struct = array( 804 'methodName' => $methodName, 805 'params' => $args 806 ); 807 $this->calls[] = $struct; 808 } 809 function query() { 810 // Prepare multicall, then call the parent::query() method 811 return parent::query('system.multicall', $this->calls); 812 } 813 } 814 No newline at end of file -
wp-includes/class/AtomServer.php
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 class AtomServer { 11 12 var $ATOM_CONTENT_TYPE = 'application/atom+xml'; 13 var $CATEGORIES_CONTENT_TYPE = 'application/atomcat+xml'; 14 var $SERVICE_CONTENT_TYPE = 'application/atomsvc+xml'; 15 16 var $ATOM_NS = 'http://www.w3.org/2005/Atom'; 17 var $ATOMPUB_NS = 'http://www.w3.org/2007/app'; 18 19 var $ENTRIES_PATH = "posts"; 20 var $CATEGORIES_PATH = "categories"; 21 var $MEDIA_PATH = "attachments"; 22 var $ENTRY_PATH = "post"; 23 var $SERVICE_PATH = "service"; 24 var $MEDIA_SINGLE_PATH = "attachment"; 25 26 var $params = array(); 27 var $script_name = "wp-app.php"; 28 var $media_content_types = array('image/*','audio/*','video/*'); 29 var $atom_content_types = array('application/atom+xml'); 30 31 var $selectors = array(); 32 33 // support for head 34 var $do_output = true; 35 36 function AtomServer() { 37 38 $this->script_name = array_pop(explode('/',$_SERVER['SCRIPT_NAME'])); 39 40 $this->selectors = array( 41 '@/service$@' => 42 array('GET' => 'get_service'), 43 '@/categories$@' => 44 array('GET' => 'get_categories_xml'), 45 '@/post/(\d+)$@' => 46 array('GET' => 'get_post', 47 'PUT' => 'put_post', 48 'DELETE' => 'delete_post'), 49 '@/posts/?(\d+)?$@' => 50 array('GET' => 'get_posts', 51 'POST' => 'create_post'), 52 '@/attachments/?(\d+)?$@' => 53 array('GET' => 'get_attachment', 54 'POST' => 'create_attachment'), 55 '@/attachment/file/(\d+)$@' => 56 array('GET' => 'get_file', 57 'PUT' => 'put_file', 58 'DELETE' => 'delete_file'), 59 '@/attachment/(\d+)$@' => 60 array('GET' => 'get_attachment', 61 'PUT' => 'put_attachment', 62 'DELETE' => 'delete_attachment'), 63 ); 64 } 65 66 function handle_request() { 67 global $always_authenticate; 68 69 $path = $_SERVER['PATH_INFO']; 70 $method = $_SERVER['REQUEST_METHOD']; 71 72 log_app('REQUEST',"$method $path\n================"); 73 74 $this->process_conditionals(); 75 //$this->process_conditionals(); 76 77 // exception case for HEAD (treat exactly as GET, but don't output) 78 if($method == 'HEAD') { 79 $this->do_output = false; 80 $method = 'GET'; 81 } 82 83 // redirect to /service in case no path is found. 84 if(strlen($path) == 0 || $path == '/') { 85 $this->redirect($this->get_service_url()); 86 } 87 88 // dispatch 89 foreach($this->selectors as $regex => $funcs) { 90 if(preg_match($regex, $path, $matches)) { 91 if(isset($funcs[$method])) { 92 93 // authenticate regardless of the operation and set the current 94 // user. each handler will decide if auth is required or not. 95 $this->authenticate(); 96 $u = wp_get_current_user(); 97 if(!isset($u) || $u->ID == 0) { 98 if ($always_authenticate) { 99 $this->auth_required('Credentials required.'); 100 } 101 } 102 103 array_shift($matches); 104 call_user_func_array(array(&$this,$funcs[$method]), $matches); 105 exit(); 106 } else { 107 // only allow what we have handlers for... 108 $this->not_allowed(array_keys($funcs)); 109 } 110 } 111 } 112 113 // oops, nothing found 114 $this->not_found(); 115 } 116 117 function get_service() { 118 log_app('function','get_service()'); 119 $entries_url = attribute_escape($this->get_entries_url()); 120 $categories_url = attribute_escape($this->get_categories_url()); 121 $media_url = attribute_escape($this->get_attachments_url()); 122 foreach ($this->media_content_types as $med) { 123 $accepted_media_types = $accepted_media_types . "<accept>" . $med . "</accept>"; 124 } 125 $atom_prefix="atom"; 126 $service_doc = <<<EOD 127 <service xmlns="$this->ATOMPUB_NS" xmlns:$atom_prefix="$this->ATOM_NS"> 128 <workspace> 129 <$atom_prefix:title>WordPress Workspace</$atom_prefix:title> 130 <collection href="$entries_url"> 131 <$atom_prefix:title>WordPress Posts</$atom_prefix:title> 132 <accept>$this->ATOM_CONTENT_TYPE;type=entry</accept> 133 <categories href="$categories_url" /> 134 </collection> 135 <collection href="$media_url"> 136 <$atom_prefix:title>WordPress Media</$atom_prefix:title> 137 $accepted_media_types 138 </collection> 139 </workspace> 140 </service> 141 142 EOD; 143 144 $this->output($service_doc, $this->SERVICE_CONTENT_TYPE); 145 } 146 147 function get_categories_xml() { 148 149 log_app('function','get_categories_xml()'); 150 $home = attribute_escape(get_bloginfo_rss('home')); 151 152 $categories = ""; 153 $cats = get_categories("hierarchical=0&hide_empty=0"); 154 foreach ((array) $cats as $cat) { 155 $categories .= " <category term=\"" . attribute_escape($cat->cat_name) . "\" />\n"; 156 } 157 $output = <<<EOD 158 <app:categories xmlns:app="$this->ATOMPUB_NS" 159 xmlns="$this->ATOM_NS" 160 fixed="yes" scheme="$home"> 161 $categories 162 </app:categories> 163 EOD; 164 $this->output($output, $this->CATEGORIES_CONTENT_TYPE); 165 } 166 167 /* 168 * Create Post (No arguments) 169 */ 170 function create_post() { 171 global $blog_id, $wpdb; 172 $this->get_accepted_content_type($this->atom_content_types); 173 174 $parser = new AtomParser(); 175 if(!$parser->parse()) { 176 $this->client_error(); 177 } 178 179 $entry = array_pop($parser->feed->entries); 180 181 log_app('Received entry:', print_r($entry,true)); 182 183 $catnames = array(); 184 foreach($entry->categories as $cat) 185 array_push($catnames, $cat["term"]); 186 187 $wp_cats = get_categories(array('hide_empty' => false)); 188 189 $post_category = array(); 190 191 foreach($wp_cats as $cat) { 192 if(in_array($cat->cat_name, $catnames)) 193 array_push($post_category, $cat->cat_ID); 194 } 195 196 $publish = (isset($entry->draft) && trim($entry->draft) == 'yes') ? false : true; 197 198 $cap = ($publish) ? 'publish_posts' : 'edit_posts'; 199 200 if(!current_user_can($cap)) 201 $this->auth_required(__('Sorry, you do not have the right to edit/publish new posts.')); 202 203 $blog_ID = (int ) $blog_id; 204 $post_status = ($publish) ? 'publish' : 'draft'; 205 $post_author = (int) $user->ID; 206 $post_title = $entry->title[1]; 207 $post_content = $entry->content[1]; 208 $post_excerpt = $entry->summary[1]; 209 $pubtimes = $this->get_publish_time($entry); 210 $post_date = $pubtimes[0]; 211 $post_date_gmt = $pubtimes[1]; 212 213 if ( isset( $_SERVER['HTTP_SLUG'] ) ) 214 $post_name = $_SERVER['HTTP_SLUG']; 215 216 $post_data = compact('blog_ID', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'post_name'); 217 218 $this->escape($post_data); 219 log_app('Inserting Post. Data:', print_r($post_data,true)); 220 221 $postID = wp_insert_post($post_data); 222 223 if (!$postID) { 224 $this->internal_error(__('Sorry, your entry could not be posted. Something wrong happened.')); 225 } 226 227 // getting warning here about unable to set headers 228 // because something in the cache is printing to the buffer 229 // could we clean up wp_set_post_categories or cache to not print 230 // this could affect our ability to send back the right headers 231 @wp_set_post_categories($postID, $post_category); 232 233 $output = $this->get_entry($postID); 234 235 log_app('function',"create_post($postID)"); 236 $this->created($postID, $output); 237 } 238 239 function get_post($postID) { 240 241 global $entry; 242 $this->set_current_entry($postID); 243 $output = $this->get_entry($postID); 244 log_app('function',"get_post($postID)"); 245 $this->output($output); 246 247 } 248 249 function put_post($postID) { 250 global $wpdb; 251 252 // checked for valid content-types (atom+xml) 253 // quick check and exit 254 $this->get_accepted_content_type($this->atom_content_types); 255 256 $parser = new AtomParser(); 257 if(!$parser->parse()) { 258 $this->bad_request(); 259 } 260 261 $parsed = array_pop($parser->feed->entries); 262 263 log_app('Received UPDATED entry:', print_r($parsed,true)); 264 265 // check for not found 266 global $entry; 267 $entry = $GLOBALS['entry']; 268 $this->set_current_entry($postID); 269 270 if(!current_user_can('edit_post', $entry['ID'])) 271 $this->auth_required(__('Sorry, you do not have the right to edit this post.')); 272 273 $publish = (isset($parsed->draft) && trim($parsed->draft) == 'yes') ? false : true; 274 275 extract($entry); 276 277 $post_title = $parsed->title[1]; 278 $post_content = $parsed->content[1]; 279 $post_excerpt = $parsed->summary[1]; 280 $pubtimes = $this->get_publish_time($entry); 281 $post_date = $pubtimes[0]; 282 $post_date_gmt = $pubtimes[1]; 283 284 // let's not go backwards and make something draft again. 285 if(!$publish && $post_status == 'draft') { 286 $post_status = ($publish) ? 'publish' : 'draft'; 287 } elseif($publish) { 288 $post_status = 'publish'; 289 } 290 291 $postdata = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'post_date', 'post_date_gmt'); 292 $this->escape($postdata); 293 294 $result = wp_update_post($postdata); 295 296 if (!$result) { 297 $this->internal_error(__('For some strange yet very annoying reason, this post could not be edited.')); 298 } 299 300 log_app('function',"put_post($postID)"); 301 $this->ok(); 302 } 303 304 function delete_post($postID) { 305 306 // check for not found 307 global $entry; 308 $this->set_current_entry($postID); 309 310 if(!current_user_can('edit_post', $postID)) { 311 $this->auth_required(__('Sorry, you do not have the right to delete this post.')); 312 } 313 314 if ($entry['post_type'] == 'attachment') { 315 $this->delete_attachment($postID); 316 } else { 317 $result = wp_delete_post($postID); 318 319 if (!$result) { 320 $this->internal_error(__('For some strange yet very annoying reason, this post could not be deleted.')); 321 } 322 323 log_app('function',"delete_post($postID)"); 324 $this->ok(); 325 } 326 327 } 328 329 function get_attachment($postID = NULL) { 330 331 global $entry; 332 if (!isset($postID)) { 333 $this->get_attachments(); 334 } else { 335 $this->set_current_entry($postID); 336 $output = $this->get_entry($postID, 'attachment'); 337 log_app('function',"get_attachment($postID)"); 338 $this->output($output); 339 } 340 } 341 342 function create_attachment() { 343 global $wp, $wpdb, $wp_query, $blog_id; 344 345 $type = $this->get_accepted_content_type(); 346 347 if(!current_user_can('upload_files')) 348 $this->auth_required(__('You do not have permission to upload files.')); 349 350 $fp = fopen("php://input", "rb"); 351 $bits = NULL; 352 while(!feof($fp)) { 353 $bits .= fread($fp, 4096); 354 } 355 fclose($fp); 356 357 $slug = ''; 358 if ( isset( $_SERVER['HTTP_SLUG'] ) ) 359 $slug = sanitize_file_name( $_SERVER['HTTP_SLUG'] ); 360 elseif ( isset( $_SERVER['HTTP_TITLE'] ) ) 361 $slug = sanitize_file_name( $_SERVER['HTTP_TITLE'] ); 362 elseif ( empty( $slug ) ) // just make a random name 363 $slug = substr( md5( uniqid( microtime() ) ), 0, 7); 364 $ext = preg_replace( '|.*/([a-z]+)|', '$1', $_SERVER['CONTENT_TYPE'] ); 365 $slug = "$slug.$ext"; 366 $file = wp_upload_bits( $slug, NULL, $bits); 367 368 log_app('wp_upload_bits returns:',print_r($file,true)); 369 370 $url = $file['url']; 371 $file = $file['file']; 372 $filename = basename($file); 373 374 $header = apply_filters('wp_create_file_in_uploads', $file); // replicate 375 376 // Construct the attachment array 377 $attachment = array( 378 'post_title' => $slug, 379 'post_content' => $slug, 380 'post_status' => 'attachment', 381 'post_parent' => 0, 382 'post_mime_type' => $type, 383 'guid' => $url 384 ); 385 386 // Save the data 387 $postID = wp_insert_attachment($attachment, $file, $post); 388 389 if (!$postID) { 390 $this->internal_error(__('Sorry, your entry could not be posted. Something wrong happened.')); 391 } 392 393 $output = $this->get_entry($postID, 'attachment'); 394 395 $this->created($postID, $output, 'attachment'); 396 log_app('function',"create_attachment($postID)"); 397 } 398 399 function put_attachment($postID) { 400 global $wpdb; 401 402 // checked for valid content-types (atom+xml) 403 // quick check and exit 404 $this->get_accepted_content_type($this->atom_content_types); 405 406 $parser = new AtomParser(); 407 if(!$parser->parse()) { 408 $this->bad_request(); 409 } 410 411 $parsed = array_pop($parser->feed->entries); 412 413 // check for not found 414 global $entry; 415 $this->set_current_entry($postID); 416 417 if(!current_user_can('edit_post', $entry['ID'])) 418 $this->auth_required(__('Sorry, you do not have the right to edit this post.')); 419 420 $publish = (isset($parsed->draft) && trim($parsed->draft) == 'yes') ? false : true; 421 422 extract($entry); 423 424 $post_title = $parsed->title[1]; 425 $post_content = $parsed->content[1]; 426 427 $postdata = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt'); 428 $this->escape($postdata); 429 430 $result = wp_update_post($postdata); 431 432 if (!$result) { 433 $this->internal_error(__('For some strange yet very annoying reason, this post could not be edited.')); 434 } 435 436 log_app('function',"put_attachment($postID)"); 437 $this->ok(); 438 } 439 440 function delete_attachment($postID) { 441 log_app('function',"delete_attachment($postID). File '$location' deleted."); 442 443 // check for not found 444 global $entry; 445 $this->set_current_entry($postID); 446 447 if(!current_user_can('edit_post', $postID)) { 448 $this->auth_required(__('Sorry, you do not have the right to delete this post.')); 449 } 450 451 $location = get_post_meta($entry['ID'], '_wp_attached_file', true); 452 453 // delete file 454 @unlink($location); 455 456 // delete attachment 457 $result = wp_delete_post($postID); 458 459 if (!$result) { 460 $this->internal_error(__('For some strange yet very annoying reason, this post could not be deleted.')); 461 } 462 463 log_app('function',"delete_attachment($postID). File '$location' deleted."); 464 $this->ok(); 465 } 466 467 function get_file($postID) { 468 469 // check for not found 470 global $entry; 471 $this->set_current_entry($postID); 472 473 // then whether user can edit the specific post 474 if(!current_user_can('edit_post', $postID)) { 475 $this->auth_required(__('Sorry, you do not have the right to edit this post.')); 476 } 477 478 $location = get_post_meta($entry['ID'], '_wp_attached_file', true); 479 $filetype = wp_check_filetype($location); 480 481 if(!isset($location) || 'attachment' != $entry['post_type'] || empty($filetype['ext'])) 482 $this->internal_error(__('Error ocurred while accessing post metadata for file location.')); 483 484 status_header('200'); 485 header('Content-Type: ' . $entry['post_mime_type']); 486 header('Connection: close'); 487 488 $fp = fopen($location, "rb"); 489 while(!feof($fp)) { 490 echo fread($fp, 4096); 491 } 492 fclose($fp); 493 494 log_app('function',"get_file($postID)"); 495 exit; 496 } 497 498 function put_file($postID) { 499 500 $type = $this->get_accepted_content_type(); 501 502 // first check if user can upload 503 if(!current_user_can('upload_files')) 504 $this->auth_required(__('You do not have permission to upload files.')); 505 506 // check for not found 507 global $entry; 508 $this->set_current_entry($postID); 509 510 // then whether user can edit the specific post 511 if(!current_user_can('edit_post', $postID)) { 512 $this->auth_required(__('Sorry, you do not have the right to edit this post.')); 513 } 514 515 $location = get_post_meta($entry['ID'], '_wp_attached_file', true); 516 $filetype = wp_check_filetype($location); 517 518 if(!isset($location) || 'attachment' != $entry['post_type'] || empty($filetype['ext'])) 519 $this->internal_error(__('Error ocurred while accessing post metadata for file location.')); 520 521 $fp = fopen("php://input", "rb"); 522 $localfp = fopen($location, "w+"); 523 while(!feof($fp)) { 524 fwrite($localfp,fread($fp, 4096)); 525 } 526 fclose($fp); 527 fclose($localfp); 528 529 $ID = $entry['ID']; 530 $pubtimes = $this->get_publish_time($entry); 531 $post_date = $pubtimes[0]; 532 $post_date_gmt = $pubtimes[1]; 533 534 $post_data = compact('ID', 'post_date', 'post_date_gmt'); 535 $result = wp_update_post($post_data); 536 537 if (!$result) { 538 $this->internal_error(__('Sorry, your entry could not be posted. Something wrong happened.')); 539 } 540 541 log_app('function',"put_file($postID)"); 542 $this->ok(); 543 } 544 545 function get_entries_url($page = NULL) { 546 if($GLOBALS['post_type'] == 'attachment') { 547 $path = $this->MEDIA_PATH; 548 } else { 549 $path = $this->ENTRIES_PATH; 550 } 551 $url = get_bloginfo('url') . '/' . $this->script_name . '/' . $path; 552 if(isset($page) && is_int($page)) { 553 $url .= "/$page"; 554 } 555 return $url; 556 } 557 558 function the_entries_url($page = NULL) { 559 $url = $this->get_entries_url($page); 560 echo $url; 561 } 562 563 function get_categories_url($page = NULL) { 564 return get_bloginfo('url') . '/' . $this->script_name . '/' . $this->CATEGORIES_PATH; 565 } 566 567 function the_categories_url() { 568 $url = $this->get_categories_url(); 569 echo $url; 570 } 571 572 function get_attachments_url($page = NULL) { 573 $url = get_bloginfo('url') . '/' . $this->script_name . '/' . $this->MEDIA_PATH; 574 if(isset($page) && is_int($page)) { 575 $url .= "/$page"; 576 } 577 return $url; 578 } 579 580 function the_attachments_url($page = NULL) { 581 $url = $this->get_attachments_url($page); 582 echo $url; 583 } 584 585 function get_service_url() { 586 return get_bloginfo('url') . '/' . $this->script_name . '/' . $this->SERVICE_PATH; 587 } 588 589 function get_entry_url($postID = NULL) { 590 if(!isset($postID)) { 591 global $post; 592 $postID = (int) $GLOBALS['post']->ID; 593 } 594 595 $url = get_bloginfo('url') . '/' . $this->script_name . '/' . $this->ENTRY_PATH . "/$postID"; 596 597 log_app('function',"get_entry_url() = $url"); 598 return $url; 599 } 600 601 function the_entry_url($postID = NULL) { 602 $url = $this->get_entry_url($postID); 603 echo $url; 604 } 605 606 function get_media_url($postID = NULL) { 607 if(!isset($postID)) { 608 global $post; 609 $postID = (int) $GLOBALS['post']->ID; 610 } 611 612 $url = get_bloginfo('url') . '/' . $this->script_name . '/' . $this->MEDIA_SINGLE_PATH ."/file/$postID"; 613 614 log_app('function',"get_media_url() = $url"); 615 return $url; 616 } 617 618 function the_media_url($postID = NULL) { 619 $url = $this->get_media_url($postID); 620 echo $url; 621 } 622 623 function set_current_entry($postID) { 624 global $entry; 625 log_app('function',"set_current_entry($postID)"); 626 627 if(!isset($postID)) { 628 // $this->bad_request(); 629 $this->not_found(); 630 } 631 632 $entry = wp_get_single_post($postID,ARRAY_A); 633 634 if(!isset($entry) || !isset($entry['ID'])) 635 $this->not_found(); 636 637 return; 638 } 639 640 function get_posts($page = 1, $post_type = 'post') { 641 log_app('function',"get_posts($page, '$post_type')"); 642 $feed = $this->get_feed($page, $post_type); 643 $this->output($feed); 644 } 645 646 function get_attachments($page = 1, $post_type = 'attachment') { 647 log_app('function',"get_attachments($page, '$post_type')"); 648 $GLOBALS['post_type'] = $post_type; 649 $feed = $this->get_feed($page, $post_type); 650 $this->output($feed); 651 } 652 653 function get_feed($page = 1, $post_type = 'post') { 654 global $post, $wp, $wp_query, $posts, $wpdb, $blog_id, $post_cache; 655 log_app('function',"get_feed($page, '$post_type')"); 656 ob_start(); 657 658 if(!isset($page)) { 659 $page = 1; 660 } 661 $page = (int) $page; 662 663 $count = get_option('posts_per_rss'); 664 665 wp('what_to_show=posts&posts_per_page=' . $count . '&offset=' . ($count * ($page-1) )); 666 667 $post = $GLOBALS['post']; 668 $posts = $GLOBALS['posts']; 669 $wp = $GLOBALS['wp']; 670 $wp_query = $GLOBALS['wp_query']; 671 $wpdb = $GLOBALS['wpdb']; 672 $blog_id = (int) $GLOBALS['blog_id']; 673 $post_cache = $GLOBALS['post_cache']; 674 log_app('function',"query_posts(# " . print_r($wp_query, true) . "#)"); 675 676 log_app('function',"total_count(# $wp_query->max_num_pages #)"); 677 $last_page = $wp_query->max_num_pages; 678 $next_page = (($page + 1) > $last_page) ? NULL : $page + 1; 679 $prev_page = ($page - 1) < 1 ? NULL : $page - 1; 680 $last_page = ((int)$last_page == 1 || (int)$last_page == 0) ? NULL : (int) $last_page; 681 $self_page = $page > 1 ? $page : NULL; 682 ?><feed xmlns="<?php echo $this->ATOM_NS ?>" xmlns:app="<?php echo $this->ATOMPUB_NS ?>" xml:lang="<?php echo get_option('rss_language'); ?>"> 683 <id><?php $this->the_entries_url() ?></id> 684 <updated><?php echo mysql2date('Y-m-d\TH:i:s\Z', get_lastpostmodified('GMT')); ?></updated> 685 <title type="text"><?php bloginfo_rss('name') ?></title> 686 <subtitle type="text"><?php bloginfo_rss("description") ?></subtitle> 687 <link rel="first" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url() ?>" /> 688 <?php if(isset($prev_page)): ?> 689 <link rel="previous" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($prev_page) ?>" /> 690 <?php endif; ?> 691 <?php if(isset($next_page)): ?> 692 <link rel="next" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($next_page) ?>" /> 693 <?php endif; ?> 694 <link rel="last" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($last_page) ?>" /> 695 <link rel="self" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($self_page) ?>" /> 696 <rights type="text">Copyright <?php echo mysql2date('Y', get_lastpostdate('blog')); ?></rights> 697 <generator uri="http://wordpress.com/" version="1.0.5-dc">WordPress.com Atom API</generator> 698 <?php if ( have_posts() ) { 699 while ( have_posts() ) { 700 the_post(); 701 $this->echo_entry(); 702 } 703 } 704 ?></feed> 705 <?php 706 $feed = ob_get_contents(); 707 ob_end_clean(); 708 return $feed; 709 } 710 711 function get_entry($postID, $post_type = 'post') { 712 log_app('function',"get_entry($postID, '$post_type')"); 713 ob_start(); 714 global $posts, $post, $wp_query, $wp, $wpdb, $blog_id, $post_cache; 715 switch($post_type) { 716 case 'post': 717 $varname = 'p'; 718 break; 719 case 'attachment': 720 $varname = 'attachment_id'; 721 break; 722 } 723 query_posts($varname . '=' . $postID); 724 if ( have_posts() ) { 725 while ( have_posts() ) { 726 the_post(); 727 $this->echo_entry(); 728 log_app('$post',print_r($GLOBALS['post'],true)); 729 $entry = ob_get_contents(); 730 break; 731 } 732 } 733 ob_end_clean(); 734 735 log_app('get_entry returning:',$entry); 736 return $entry; 737 } 738 739 function echo_entry() { ?> 740 <entry xmlns="<?php echo $this->ATOM_NS ?>" 741 xmlns:app="<?php echo $this->ATOMPUB_NS ?>" xml:lang="<?php echo get_option('rss_language'); ?>"> 742 <id><?php the_guid($GLOBALS['post']->ID); ?></id> 743 <?php list($content_type, $content) = $this->prep_content(get_the_title()); ?> 744 <title type="<?php echo $content_type ?>"><?php echo $content ?></title> 745 <updated><?php echo get_post_modified_time('Y-m-d\TH:i:s\Z', true); ?></updated> 746 <published><?php echo get_post_time('Y-m-d\TH:i:s\Z', true); ?></published> 747 <app:edited><?php echo get_post_modified_time('Y-m-d\TH:i:s\Z', true); ?></app:edited> 748 <app:control> 749 <app:draft><?php echo ($GLOBALS['post']->post_status == 'draft' ? 'yes' : 'no') ?></app:draft> 750 </app:control> 751 <author> 752 <name><?php the_author()?></name> 753 <email><?php the_author_email()?></email> 754 <?php if (get_the_author_url() && get_the_author_url() != 'http://') { ?> 755 <uri><?php the_author_url()?></uri> 756 <?php } ?> 757 </author> 758 <?php if($GLOBALS['post']->post_type == 'attachment') { ?> 759 <link rel="edit-media" href="<?php $this->the_media_url() ?>" /> 760 <content type="<?php echo $GLOBALS['post']->post_mime_type ?>" src="<?php the_guid(); ?>"/> 761 <?php } else { ?> 762 <link href="<?php the_permalink_rss() ?>" /> 763 <?php if ( strlen( $GLOBALS['post']->post_content ) ) : 764 list($content_type, $content) = $this->prep_content(get_the_content()); ?> 765 <content type="<?php echo $content_type ?>"><?php echo $content ?></content> 766 <?php endif; ?> 767 <?php } ?> 768 <link rel="edit" href="<?php $this->the_entry_url() ?>" /> 769 <?php foreach(get_the_category() as $category) { ?> 770 <category scheme="<?php bloginfo_rss('home') ?>" term="<?php echo $category->cat_name?>" /> 771 <?php } ?> 772 <?php list($content_type, $content) = $this->prep_content(get_the_excerpt()); ?> 773 <summary type="<?php echo $content_type ?>"><?php echo $content ?></summary> 774 </entry> 775 <?php } 776 777 function prep_content($data) { 778 if (strpos($data, '<') === false && strpos($data, '&') === false) { 779 return array('text', $data); 780 } 781 782 $parser = xml_parser_create(); 783 xml_parse($parser, '<div>' . $data . '</div>', true); 784 $code = xml_get_error_code($parser); 785 xml_parser_free($parser); 786 787 if (!$code) { 788 if (strpos($data, '<') === false) { 789 return array('text', $data); 790 } else { 791 $data = "<div xmlns='http://www.w3.org/1999/xhtml'>$data</div>"; 792 return array('xhtml', $data); 793 } 794 } 795 796 if (strpos($data, ']]>') == false) { 797 return array('html', "<![CDATA[$data]]>"); 798 } else { 799 return array('html', htmlspecialchars($data)); 800 } 801 } 802 803 function ok() { 804 log_app('Status','200: OK'); 805 header('Content-Type: text/plain'); 806 status_header('200'); 807 exit; 808 } 809 810 function no_content() { 811 log_app('Status','204: No Content'); 812 header('Content-Type: text/plain'); 813 status_header('204'); 814 echo "Deleted."; 815 exit; 816 } 817 818 function internal_error($msg = 'Internal Server Error') { 819 log_app('Status','500: Server Error'); 820 header('Content-Type: text/plain'); 821 status_header('500'); 822 echo $msg; 823 exit; 824 } 825 826 function bad_request() { 827 log_app('Status','400: Bad Request'); 828 header('Content-Type: text/plain'); 829 status_header('400'); 830 exit; 831 } 832 833 function length_required() { 834 log_app('Status','411: Length Required'); 835 header("HTTP/1.1 411 Length Required"); 836 header('Content-Type: text/plain'); 837 status_header('411'); 838 exit; 839 } 840 841 function invalid_media() { 842 log_app('Status','415: Unsupported Media Type'); 843 header("HTTP/1.1 415 Unsupported Media Type"); 844 header('Content-Type: text/plain'); 845 exit; 846 } 847 848 function not_found() { 849 log_app('Status','404: Not Found'); 850 header('Content-Type: text/plain'); 851 status_header('404'); 852 exit; 853 } 854 855 function not_allowed($allow) { 856 log_app('Status','405: Not Allowed'); 857 header('Allow: ' . join(',', $allow)); 858 status_header('405'); 859 exit; 860 } 861 862 function redirect($url) { 863 864 log_app('Status','302: Redirect'); 865 $escaped_url = attribute_escape($url); 866 $content = <<<EOD 867 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> 868 <html> 869 <head> 870 <title>302 Found</title> 871 </head> 872 <body> 873 <h1>Found</h1> 874 <p>The document has moved <a href="$escaped_url">here</a>.</p> 875 </body> 876 </html> 877 878 EOD; 879 header('HTTP/1.1 302 Moved'); 880 header('Content-Type: text/html'); 881 header('Location: ' . $url); 882 echo $content; 883 exit; 884 885 } 886 887 888 function client_error($msg = 'Client Error') { 889 log_app('Status','400: Client Error'); 890 header('Content-Type: text/plain'); 891 status_header('400'); 892 exit; 893 } 894 895 function created($post_ID, $content, $post_type = 'post') { 896 log_app('created()::$post_ID',"$post_ID, $post_type"); 897 $edit = $this->get_entry_url($post_ID); 898 switch($post_type) { 899 case 'post': 900 $ctloc = $this->get_entry_url($post_ID); 901 break; 902 case 'attachment': 903 $edit = get_bloginfo('url') . '/' . $this->script_name . "/attachments/$post_ID"; 904 break; 905 } 906 header("Content-Type: $this->ATOM_CONTENT_TYPE"); 907 if(isset($ctloc)) 908 header('Content-Location: ' . $ctloc); 909 header('Location: ' . $edit); 910 status_header('201'); 911 echo $content; 912 exit; 913 } 914 915 function auth_required($msg) { 916 log_app('Status','401: Auth Required'); 917 nocache_headers(); 918 header('WWW-Authenticate: Basic realm="WordPress Atom Protocol"'); 919 header("HTTP/1.1 401 $msg"); 920 header('Status: ' . $msg); 921 header('Content-Type: text/html'); 922 $content = <<<EOD 923 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> 924 <html> 925 <head> 926 <title>401 Unauthorized</title> 927 </head> 928 <body> 929 <h1>401 Unauthorized</h1> 930 <p>$msg</p> 931 </body> 932 </html> 933 934 EOD; 935 echo $content; 936 exit; 937 } 938 939 function output($xml, $ctype = 'application/atom+xml') { 940 status_header('200'); 941 $xml = '<?xml version="1.0" encoding="' . strtolower(get_option('blog_charset')) . '"?>'."\n".$xml; 942 header('Connection: close'); 943 header('Content-Length: '. strlen($xml)); 944 header('Content-Type: ' . $ctype); 945 header('Content-Disposition: attachment; filename=atom.xml'); 946 header('Date: '. date('r')); 947 if($this->do_output) 948 echo $xml; 949 log_app('function', "output:\n$xml"); 950 exit; 951 } 952 953 function escape(&$array) { 954 global $wpdb; 955 956 foreach ($array as $k => $v) { 957 if (is_array($v)) { 958 $this->escape($array[$k]); 959 } else if (is_object($v)) { 960 //skip 961 } else { 962 $array[$k] = $wpdb->escape($v); 963 } 964 } 965 } 966 967 /* 968 * Access credential through various methods and perform login 969 */ 970 function authenticate() { 971 $login_data = array(); 972 $already_md5 = false; 973 974 log_app("authenticate()",print_r($_ENV, true)); 975 976 // if using mod_rewrite/ENV hack 977 // http://www.besthostratings.com/articles/http-auth-php-cgi.html 978 if(isset($_SERVER['HTTP_AUTHORIZATION'])) { 979 list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = 980 explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6))); 981 } 982 983 // If Basic Auth is working... 984 if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) { 985 $login_data = array('login' => $_SERVER['PHP_AUTH_USER'], 'password' => $_SERVER['PHP_AUTH_PW']); 986 log_app("Basic Auth",$login_data['login']); 987 } else { 988 // else, do cookie-based authentication 989 if (function_exists('wp_get_cookie_login')) { 990 $login_data = wp_get_cookie_login(); 991 $already_md5 = true; 992 } 993 } 994 995 // call wp_login and set current user 996 if (!empty($login_data) && wp_login($login_data['login'], $login_data['password'], $already_md5)) { 997 $current_user = new WP_User(0, $login_data['login']); 998 wp_set_current_user($current_user->ID); 999 log_app("authenticate()",$login_data['login']); 1000 } 1001 } 1002 1003 function get_accepted_content_type($types = NULL) { 1004 1005 if(!isset($types)) { 1006 $types = $this->media_content_types; 1007 } 1008 1009 if(!isset($_SERVER['CONTENT_LENGTH']) || !isset($_SERVER['CONTENT_TYPE'])) { 1010 $this->length_required(); 1011 } 1012 1013 $type = $_SERVER['CONTENT_TYPE']; 1014 list($type,$subtype) = explode('/',$type); 1015 list($subtype) = explode(";",$subtype); // strip MIME parameters 1016 log_app("get_accepted_content_type", "type=$type, subtype=$subtype"); 1017 1018 foreach($types as $t) { 1019 list($acceptedType,$acceptedSubtype) = explode('/',$t); 1020 if($acceptedType == '*' || $acceptedType == $type) { 1021 if($acceptedSubtype == '*' || $acceptedSubtype == $subtype) 1022 return $type . "/" . $subtype; 1023 } 1024 } 1025 1026 $this->invalid_media(); 1027 } 1028 1029 function process_conditionals() { 1030 1031 if(empty($this->params)) return; 1032 if($_SERVER['REQUEST_METHOD'] == 'DELETE') return; 1033 1034 switch($this->params[0]) { 1035 case $this->ENTRY_PATH: 1036 global $post; 1037 $post = wp_get_single_post($this->params[1]); 1038 $wp_last_modified = get_post_modified_time('D, d M Y H:i:s', true); 1039 $post = NULL; 1040 break; 1041 case $this->ENTRIES_PATH: 1042 $wp_last_modified = mysql2date('D, d M Y H:i:s', get_lastpostmodified('GMT'), 0).' GMT'; 1043 break; 1044 default: 1045 return; 1046 } 1047 $wp_etag = md5($wp_last_modified); 1048 @header("Last-Modified: $wp_last_modified"); 1049 @header("ETag: $wp_etag"); 1050 1051 // Support for Conditional GET 1052 if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) 1053 $client_etag = stripslashes($_SERVER['HTTP_IF_NONE_MATCH']); 1054 else 1055 $client_etag = false; 1056 1057 $client_last_modified = trim( $_SERVER['HTTP_IF_MODIFIED_SINCE']); 1058 // If string is empty, return 0. If not, attempt to parse into a timestamp 1059 $client_modified_timestamp = $client_last_modified ? strtotime($client_last_modified) : 0; 1060 1061 // Make a timestamp for our most recent modification... 1062 $wp_modified_timestamp = strtotime($wp_last_modified); 1063 1064 if ( ($client_last_modified && $client_etag) ? 1065 (($client_modified_timestamp >= $wp_modified_timestamp) && ($client_etag == $wp_etag)) : 1066 (($client_modified_timestamp >= $wp_modified_timestamp) || ($client_etag == $wp_etag)) ) { 1067 status_header( 304 ); 1068 exit; 1069 } 1070 } 1071 1072 function rfc3339_str2time($str) { 1073 1074 $match = false; 1075 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)) 1076 return false; 1077 1078 if($match[3] == 'Z') 1079 $match[3] == '+0000'; 1080 1081 return strtotime($match[1] . " " . $match[2] . " " . $match[3]); 1082 } 1083 1084 function get_publish_time($entry) { 1085 1086 $pubtime = $this->rfc3339_str2time($entry->published); 1087 1088 if(!$pubtime) { 1089 return array(current_time('mysql'),current_time('mysql',1)); 1090 } else { 1091 return array(date("Y-m-d H:i:s", $pubtime), gmdate("Y-m-d H:i:s", $pubtime)); 1092 } 1093 } 1094 1095 } 1096 No newline at end of file -
wp-includes/class/AtomServer.php
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 class AtomServer { 11 12 var $ATOM_CONTENT_TYPE = 'application/atom+xml'; 13 var $CATEGORIES_CONTENT_TYPE = 'application/atomcat+xml'; 14 var $SERVICE_CONTENT_TYPE = 'application/atomsvc+xml'; 15 16 var $ATOM_NS = 'http://www.w3.org/2005/Atom'; 17 var $ATOMPUB_NS = 'http://www.w3.org/2007/app'; 18 19 var $ENTRIES_PATH = "posts"; 20 var $CATEGORIES_PATH = "categories"; 21 var $MEDIA_PATH = "attachments"; 22 var $ENTRY_PATH = "post"; 23 var $SERVICE_PATH = "service"; 24 var $MEDIA_SINGLE_PATH = "attachment"; 25 26 var $params = array(); 27 var $script_name = "wp-app.php"; 28 var $media_content_types = array('image/*','audio/*','video/*'); 29 var $atom_content_types = array('application/atom+xml'); 30 31 var $selectors = array(); 32 33 // support for head 34 var $do_output = true; 35 36 function AtomServer() { 37 38 $this->script_name = array_pop(explode('/',$_SERVER['SCRIPT_NAME'])); 39 40 $this->selectors = array( 41 '@/service$@' => 42 array('GET' => 'get_service'), 43 '@/categories$@' => 44 array('GET' => 'get_categories_xml'), 45 '@/post/(\d+)$@' => 46 array('GET' => 'get_post', 47 'PUT' => 'put_post', 48 'DELETE' => 'delete_post'), 49 '@/posts/?(\d+)?$@' => 50 array('GET' => 'get_posts', 51 'POST' => 'create_post'), 52 '@/attachments/?(\d+)?$@' => 53 array('GET' => 'get_attachment', 54 'POST' => 'create_attachment'), 55 '@/attachment/file/(\d+)$@' => 56 array('GET' => 'get_file', 57 'PUT' => 'put_file', 58 'DELETE' => 'delete_file'), 59 '@/attachment/(\d+)$@' => 60 array('GET' => 'get_attachment', 61 'PUT' => 'put_attachment', 62 'DELETE' => 'delete_attachment'), 63 ); 64 } 65 66 function handle_request() { 67 global $always_authenticate; 68 69 $path = $_SERVER['PATH_INFO']; 70 $method = $_SERVER['REQUEST_METHOD']; 71 72 log_app('REQUEST',"$method $path\n================"); 73 74 $this->process_conditionals(); 75 //$this->process_conditionals(); 76 77 // exception case for HEAD (treat exactly as GET, but don't output) 78 if($method == 'HEAD') { 79 $this->do_output = false; 80 $method = 'GET'; 81 } 82 83 // redirect to /service in case no path is found. 84 if(strlen($path) == 0 || $path == '/') { 85 $this->redirect($this->get_service_url()); 86 } 87 88 // dispatch 89 foreach($this->selectors as $regex => $funcs) { 90 if(preg_match($regex, $path, $matches)) { 91 if(isset($funcs[$method])) { 92 93 // authenticate regardless of the operation and set the current 94 // user. each handler will decide if auth is required or not. 95 $this->authenticate(); 96 $u = wp_get_current_user(); 97 if(!isset($u) || $u->ID == 0) { 98 if ($always_authenticate) { 99 $this->auth_required('Credentials required.'); 100 } 101 } 102 103 array_shift($matches); 104 call_user_func_array(array(&$this,$funcs[$method]), $matches); 105 exit(); 106 } else { 107 // only allow what we have handlers for... 108 $this->not_allowed(array_keys($funcs)); 109 } 110 } 111 } 112 113 // oops, nothing found 114 $this->not_found(); 115 } 116 117 function get_service() { 118 log_app('function','get_service()'); 119 $entries_url = attribute_escape($this->get_entries_url()); 120 $categories_url = attribute_escape($this->get_categories_url()); 121 $media_url = attribute_escape($this->get_attachments_url()); 122 foreach ($this->media_content_types as $med) { 123 $accepted_media_types = $accepted_media_types . "<accept>" . $med . "</accept>"; 124 } 125 $atom_prefix="atom"; 126 $service_doc = <<<EOD 127 <service xmlns="$this->ATOMPUB_NS" xmlns:$atom_prefix="$this->ATOM_NS"> 128 <workspace> 129 <$atom_prefix:title>WordPress Workspace</$atom_prefix:title> 130 <collection href="$entries_url"> 131 <$atom_prefix:title>WordPress Posts</$atom_prefix:title> 132 <accept>$this->ATOM_CONTENT_TYPE;type=entry</accept> 133 <categories href="$categories_url" /> 134 </collection> 135 <collection href="$media_url"> 136 <$atom_prefix:title>WordPress Media</$atom_prefix:title> 137 $accepted_media_types 138 </collection> 139 </workspace> 140 </service> 141 142 EOD; 143 144 $this->output($service_doc, $this->SERVICE_CONTENT_TYPE); 145 } 146 147 function get_categories_xml() { 148 149 log_app('function','get_categories_xml()'); 150 $home = attribute_escape(get_bloginfo_rss('home')); 151 152 $categories = ""; 153 $cats = get_categories("hierarchical=0&hide_empty=0"); 154 foreach ((array) $cats as $cat) { 155 $categories .= " <category term=\"" . attribute_escape($cat->cat_name) . "\" />\n"; 156 } 157 $output = <<<EOD 158 <app:categories xmlns:app="$this->ATOMPUB_NS" 159 xmlns="$this->ATOM_NS" 160 fixed="yes" scheme="$home"> 161 $categories 162 </app:categories> 163 EOD; 164 $this->output($output, $this->CATEGORIES_CONTENT_TYPE); 165 } 166 167 /* 168 * Create Post (No arguments) 169 */ 170 function create_post() { 171 global $blog_id, $wpdb; 172 $this->get_accepted_content_type($this->atom_content_types); 173 174 $parser = new AtomParser(); 175 if(!$parser->parse()) { 176 $this->client_error(); 177 } 178 179 $entry = array_pop($parser->feed->entries); 180 181 log_app('Received entry:', print_r($entry,true)); 182 183 $catnames = array(); 184 foreach($entry->categories as $cat) 185 array_push($catnames, $cat["term"]); 186 187 $wp_cats = get_categories(array('hide_empty' => false)); 188 189 $post_category = array(); 190 191 foreach($wp_cats as $cat) { 192 if(in_array($cat->cat_name, $catnames)) 193 array_push($post_category, $cat->cat_ID); 194 } 195 196 $publish = (isset($entry->draft) && trim($entry->draft) == 'yes') ? false : true; 197 198 $cap = ($publish) ? 'publish_posts' : 'edit_posts'; 199 200 if(!current_user_can($cap)) 201 $this->auth_required(__('Sorry, you do not have the right to edit/publish new posts.')); 202 203 $blog_ID = (int ) $blog_id; 204 $post_status = ($publish) ? 'publish' : 'draft'; 205 $post_author = (int) $user->ID; 206 $post_title = $entry->title[1]; 207 $post_content = $entry->content[1]; 208 $post_excerpt = $entry->summary[1]; 209 $pubtimes = $this->get_publish_time($entry); 210 $post_date = $pubtimes[0]; 211 $post_date_gmt = $pubtimes[1]; 212 213 if ( isset( $_SERVER['HTTP_SLUG'] ) ) 214 $post_name = $_SERVER['HTTP_SLUG']; 215 216 $post_data = compact('blog_ID', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'post_name'); 217 218 $this->escape($post_data); 219 log_app('Inserting Post. Data:', print_r($post_data,true)); 220 221 $postID = wp_insert_post($post_data); 222 223 if (!$postID) { 224 $this->internal_error(__('Sorry, your entry could not be posted. Something wrong happened.')); 225 } 226 227 // getting warning here about unable to set headers 228 // because something in the cache is printing to the buffer 229 // could we clean up wp_set_post_categories or cache to not print 230 // this could affect our ability to send back the right headers 231 @wp_set_post_categories($postID, $post_category); 232 233 $output = $this->get_entry($postID); 234 235 log_app('function',"create_post($postID)"); 236 $this->created($postID, $output); 237 } 238 239 function get_post($postID) { 240 241 global $entry; 242 $this->set_current_entry($postID); 243 $output = $this->get_entry($postID); 244 log_app('function',"get_post($postID)"); 245 $this->output($output); 246 247 } 248 249 function put_post($postID) { 250 global $wpdb; 251 252 // checked for valid content-types (atom+xml) 253 // quick check and exit 254 $this->get_accepted_content_type($this->atom_content_types); 255 256 $parser = new AtomParser(); 257 if(!$parser->parse()) { 258 $this->bad_request(); 259 } 260 261 $parsed = array_pop($parser->feed->entries); 262 263 log_app('Received UPDATED entry:', print_r($parsed,true)); 264 265 // check for not found 266 global $entry; 267 $entry = $GLOBALS['entry']; 268 $this->set_current_entry($postID); 269 270 if(!current_user_can('edit_post', $entry['ID'])) 271 $this->auth_required(__('Sorry, you do not have the right to edit this post.')); 272 273 $publish = (isset($parsed->draft) && trim($parsed->draft) == 'yes') ? false : true; 274 275 extract($entry); 276 277 $post_title = $parsed->title[1]; 278 $post_content = $parsed->content[1]; 279 $post_excerpt = $parsed->summary[1]; 280 $pubtimes = $this->get_publish_time($entry); 281 $post_date = $pubtimes[0]; 282 $post_date_gmt = $pubtimes[1]; 283 284 // let's not go backwards and make something draft again. 285 if(!$publish && $post_status == 'draft') { 286 $post_status = ($publish) ? 'publish' : 'draft'; 287 } elseif($publish) { 288 $post_status = 'publish'; 289 } 290 291 $postdata = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt', 'post_date', 'post_date_gmt'); 292 $this->escape($postdata); 293 294 $result = wp_update_post($postdata); 295 296 if (!$result) { 297 $this->internal_error(__('For some strange yet very annoying reason, this post could not be edited.')); 298 } 299 300 log_app('function',"put_post($postID)"); 301 $this->ok(); 302 } 303 304 function delete_post($postID) { 305 306 // check for not found 307 global $entry; 308 $this->set_current_entry($postID); 309 310 if(!current_user_can('edit_post', $postID)) { 311 $this->auth_required(__('Sorry, you do not have the right to delete this post.')); 312 } 313 314 if ($entry['post_type'] == 'attachment') { 315 $this->delete_attachment($postID); 316 } else { 317 $result = wp_delete_post($postID); 318 319 if (!$result) { 320 $this->internal_error(__('For some strange yet very annoying reason, this post could not be deleted.')); 321 } 322 323 log_app('function',"delete_post($postID)"); 324 $this->ok(); 325 } 326 327 } 328 329 function get_attachment($postID = NULL) { 330 331 global $entry; 332 if (!isset($postID)) { 333 $this->get_attachments(); 334 } else { 335 $this->set_current_entry($postID); 336 $output = $this->get_entry($postID, 'attachment'); 337 log_app('function',"get_attachment($postID)"); 338 $this->output($output); 339 } 340 } 341 342 function create_attachment() { 343 global $wp, $wpdb, $wp_query, $blog_id; 344 345 $type = $this->get_accepted_content_type(); 346 347 if(!current_user_can('upload_files')) 348 $this->auth_required(__('You do not have permission to upload files.')); 349 350 $fp = fopen("php://input", "rb"); 351 $bits = NULL; 352 while(!feof($fp)) { 353 $bits .= fread($fp, 4096); 354 } 355 fclose($fp); 356 357 $slug = ''; 358 if ( isset( $_SERVER['HTTP_SLUG'] ) ) 359 $slug = sanitize_file_name( $_SERVER['HTTP_SLUG'] ); 360 elseif ( isset( $_SERVER['HTTP_TITLE'] ) ) 361 $slug = sanitize_file_name( $_SERVER['HTTP_TITLE'] ); 362 elseif ( empty( $slug ) ) // just make a random name 363 $slug = substr( md5( uniqid( microtime() ) ), 0, 7); 364 $ext = preg_replace( '|.*/([a-z]+)|', '$1', $_SERVER['CONTENT_TYPE'] ); 365 $slug = "$slug.$ext"; 366 $file = wp_upload_bits( $slug, NULL, $bits); 367 368 log_app('wp_upload_bits returns:',print_r($file,true)); 369 370 $url = $file['url']; 371 $file = $file['file']; 372 $filename = basename($file); 373 374 $header = apply_filters('wp_create_file_in_uploads', $file); // replicate 375 376 // Construct the attachment array 377 $attachment = array( 378 'post_title' => $slug, 379 'post_content' => $slug, 380 'post_status' => 'attachment', 381 'post_parent' => 0, 382 'post_mime_type' => $type, 383 'guid' => $url 384 ); 385 386 // Save the data 387 $postID = wp_insert_attachment($attachment, $file, $post); 388 389 if (!$postID) { 390 $this->internal_error(__('Sorry, your entry could not be posted. Something wrong happened.')); 391 } 392 393 $output = $this->get_entry($postID, 'attachment'); 394 395 $this->created($postID, $output, 'attachment'); 396 log_app('function',"create_attachment($postID)"); 397 } 398 399 function put_attachment($postID) { 400 global $wpdb; 401 402 // checked for valid content-types (atom+xml) 403 // quick check and exit 404 $this->get_accepted_content_type($this->atom_content_types); 405 406 $parser = new AtomParser(); 407 if(!$parser->parse()) { 408 $this->bad_request(); 409 } 410 411 $parsed = array_pop($parser->feed->entries); 412 413 // check for not found 414 global $entry; 415 $this->set_current_entry($postID); 416 417 if(!current_user_can('edit_post', $entry['ID'])) 418 $this->auth_required(__('Sorry, you do not have the right to edit this post.')); 419 420 $publish = (isset($parsed->draft) && trim($parsed->draft) == 'yes') ? false : true; 421 422 extract($entry); 423 424 $post_title = $parsed->title[1]; 425 $post_content = $parsed->content[1]; 426 427 $postdata = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt'); 428 $this->escape($postdata); 429 430 $result = wp_update_post($postdata); 431 432 if (!$result) { 433 $this->internal_error(__('For some strange yet very annoying reason, this post could not be edited.')); 434 } 435 436 log_app('function',"put_attachment($postID)"); 437 $this->ok(); 438 } 439 440 function delete_attachment($postID) { 441 log_app('function',"delete_attachment($postID). File '$location' deleted."); 442 443 // check for not found 444 global $entry; 445 $this->set_current_entry($postID); 446 447 if(!current_user_can('edit_post', $postID)) { 448 $this->auth_required(__('Sorry, you do not have the right to delete this post.')); 449 } 450 451 $location = get_post_meta($entry['ID'], '_wp_attached_file', true); 452 453 // delete file 454 @unlink($location); 455 456 // delete attachment 457 $result = wp_delete_post($postID); 458 459 if (!$result) { 460 $this->internal_error(__('For some strange yet very annoying reason, this post could not be deleted.')); 461 } 462 463 log_app('function',"delete_attachment($postID). File '$location' deleted."); 464 $this->ok(); 465 } 466 467 function get_file($postID) { 468 469 // check for not found 470 global $entry; 471 $this->set_current_entry($postID); 472 473 // then whether user can edit the specific post 474 if(!current_user_can('edit_post', $postID)) { 475 $this->auth_required(__('Sorry, you do not have the right to edit this post.')); 476 } 477 478 $location = get_post_meta($entry['ID'], '_wp_attached_file', true); 479 $filetype = wp_check_filetype($location); 480 481 if(!isset($location) || 'attachment' != $entry['post_type'] || empty($filetype['ext'])) 482 $this->internal_error(__('Error ocurred while accessing post metadata for file location.')); 483 484 status_header('200'); 485 header('Content-Type: ' . $entry['post_mime_type']); 486 header('Connection: close'); 487 488 $fp = fopen($location, "rb"); 489 while(!feof($fp)) { 490 echo fread($fp, 4096); 491 } 492 fclose($fp); 493 494 log_app('function',"get_file($postID)"); 495 exit; 496 } 497 498 function put_file($postID) { 499 500 $type = $this->get_accepted_content_type(); 501 502 // first check if user can upload 503 if(!current_user_can('upload_files')) 504 $this->auth_required(__('You do not have permission to upload files.')); 505 506 // check for not found 507 global $entry; 508 $this->set_current_entry($postID); 509 510 // then whether user can edit the specific post 511 if(!current_user_can('edit_post', $postID)) { 512 $this->auth_required(__('Sorry, you do not have the right to edit this post.')); 513 } 514 515 $location = get_post_meta($entry['ID'], '_wp_attached_file', true); 516 $filetype = wp_check_filetype($location); 517 518 if(!isset($location) || 'attachment' != $entry['post_type'] || empty($filetype['ext'])) 519 $this->internal_error(__('Error ocurred while accessing post metadata for file location.')); 520 521 $fp = fopen("php://input", "rb"); 522 $localfp = fopen($location, "w+"); 523 while(!feof($fp)) { 524 fwrite($localfp,fread($fp, 4096)); 525 } 526 fclose($fp); 527 fclose($localfp); 528 529 $ID = $entry['ID']; 530 $pubtimes = $this->get_publish_time($entry); 531 $post_date = $pubtimes[0]; 532 $post_date_gmt = $pubtimes[1]; 533 534 $post_data = compact('ID', 'post_date', 'post_date_gmt'); 535 $result = wp_update_post($post_data); 536 537 if (!$result) { 538 $this->internal_error(__('Sorry, your entry could not be posted. Something wrong happened.')); 539 } 540 541 log_app('function',"put_file($postID)"); 542 $this->ok(); 543 } 544 545 function get_entries_url($page = NULL) { 546 if($GLOBALS['post_type'] == 'attachment') { 547 $path = $this->MEDIA_PATH; 548 } else { 549 $path = $this->ENTRIES_PATH; 550 } 551 $url = get_bloginfo('url') . '/' . $this->script_name . '/' . $path; 552 if(isset($page) && is_int($page)) { 553 $url .= "/$page"; 554 } 555 return $url; 556 } 557 558 function the_entries_url($page = NULL) { 559 $url = $this->get_entries_url($page); 560 echo $url; 561 } 562 563 function get_categories_url($page = NULL) { 564 return get_bloginfo('url') . '/' . $this->script_name . '/' . $this->CATEGORIES_PATH; 565 } 566 567 function the_categories_url() { 568 $url = $this->get_categories_url(); 569 echo $url; 570 } 571 572 function get_attachments_url($page = NULL) { 573 $url = get_bloginfo('url') . '/' . $this->script_name . '/' . $this->MEDIA_PATH; 574 if(isset($page) && is_int($page)) { 575 $url .= "/$page"; 576 } 577 return $url; 578 } 579 580 function the_attachments_url($page = NULL) { 581 $url = $this->get_attachments_url($page); 582 echo $url; 583 } 584 585 function get_service_url() { 586 return get_bloginfo('url') . '/' . $this->script_name . '/' . $this->SERVICE_PATH; 587 } 588 589 function get_entry_url($postID = NULL) { 590 if(!isset($postID)) { 591 global $post; 592 $postID = (int) $GLOBALS['post']->ID; 593 } 594 595 $url = get_bloginfo('url') . '/' . $this->script_name . '/' . $this->ENTRY_PATH . "/$postID"; 596 597 log_app('function',"get_entry_url() = $url"); 598 return $url; 599 } 600 601 function the_entry_url($postID = NULL) { 602 $url = $this->get_entry_url($postID); 603 echo $url; 604 } 605 606 function get_media_url($postID = NULL) { 607 if(!isset($postID)) { 608 global $post; 609 $postID = (int) $GLOBALS['post']->ID; 610 } 611 612 $url = get_bloginfo('url') . '/' . $this->script_name . '/' . $this->MEDIA_SINGLE_PATH ."/file/$postID"; 613 614 log_app('function',"get_media_url() = $url"); 615 return $url; 616 } 617 618 function the_media_url($postID = NULL) { 619 $url = $this->get_media_url($postID); 620 echo $url; 621 } 622 623 function set_current_entry($postID) { 624 global $entry; 625 log_app('function',"set_current_entry($postID)"); 626 627 if(!isset($postID)) { 628 // $this->bad_request(); 629 $this->not_found(); 630 } 631 632 $entry = wp_get_single_post($postID,ARRAY_A); 633 634 if(!isset($entry) || !isset($entry['ID'])) 635 $this->not_found(); 636 637 return; 638 } 639 640 function get_posts($page = 1, $post_type = 'post') { 641 log_app('function',"get_posts($page, '$post_type')"); 642 $feed = $this->get_feed($page, $post_type); 643 $this->output($feed); 644 } 645 646 function get_attachments($page = 1, $post_type = 'attachment') { 647 log_app('function',"get_attachments($page, '$post_type')"); 648 $GLOBALS['post_type'] = $post_type; 649 $feed = $this->get_feed($page, $post_type); 650 $this->output($feed); 651 } 652 653 function get_feed($page = 1, $post_type = 'post') { 654 global $post, $wp, $wp_query, $posts, $wpdb, $blog_id, $post_cache; 655 log_app('function',"get_feed($page, '$post_type')"); 656 ob_start(); 657 658 if(!isset($page)) { 659 $page = 1; 660 } 661 $page = (int) $page; 662 663 $count = get_option('posts_per_rss'); 664 665 wp('what_to_show=posts&posts_per_page=' . $count . '&offset=' . ($count * ($page-1) )); 666 667 $post = $GLOBALS['post']; 668 $posts = $GLOBALS['posts']; 669 $wp = $GLOBALS['wp']; 670 $wp_query = $GLOBALS['wp_query']; 671 $wpdb = $GLOBALS['wpdb']; 672 $blog_id = (int) $GLOBALS['blog_id']; 673 $post_cache = $GLOBALS['post_cache']; 674 log_app('function',"query_posts(# " . print_r($wp_query, true) . "#)"); 675 676 log_app('function',"total_count(# $wp_query->max_num_pages #)"); 677 $last_page = $wp_query->max_num_pages; 678 $next_page = (($page + 1) > $last_page) ? NULL : $page + 1; 679 $prev_page = ($page - 1) < 1 ? NULL : $page - 1; 680 $last_page = ((int)$last_page == 1 || (int)$last_page == 0) ? NULL : (int) $last_page; 681 $self_page = $page > 1 ? $page : NULL; 682 ?><feed xmlns="<?php echo $this->ATOM_NS ?>" xmlns:app="<?php echo $this->ATOMPUB_NS ?>" xml:lang="<?php echo get_option('rss_language'); ?>"> 683 <id><?php $this->the_entries_url() ?></id> 684 <updated><?php echo mysql2date('Y-m-d\TH:i:s\Z', get_lastpostmodified('GMT')); ?></updated> 685 <title type="text"><?php bloginfo_rss('name') ?></title> 686 <subtitle type="text"><?php bloginfo_rss("description") ?></subtitle> 687 <link rel="first" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url() ?>" /> 688 <?php if(isset($prev_page)): ?> 689 <link rel="previous" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($prev_page) ?>" /> 690 <?php endif; ?> 691 <?php if(isset($next_page)): ?> 692 <link rel="next" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($next_page) ?>" /> 693 <?php endif; ?> 694 <link rel="last" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($last_page) ?>" /> 695 <link rel="self" type="<?php echo $this->ATOM_CONTENT_TYPE ?>" href="<?php $this->the_entries_url($self_page) ?>" /> 696 <rights type="text">Copyright <?php echo mysql2date('Y', get_lastpostdate('blog')); ?></rights> 697 <generator uri="http://wordpress.com/" version="1.0.5-dc">WordPress.com Atom API</generator> 698 <?php if ( have_posts() ) { 699 while ( have_posts() ) { 700 the_post(); 701 $this->echo_entry(); 702 } 703 } 704 ?></feed> 705 <?php 706 $feed = ob_get_contents(); 707 ob_end_clean(); 708 return $feed; 709 } 710 711 function get_entry($postID, $post_type = 'post') { 712 log_app('function',"get_entry($postID, '$post_type')"); 713 ob_start(); 714 global $posts, $post, $wp_query, $wp, $wpdb, $blog_id, $post_cache; 715 switch($post_type) { 716 case 'post': 717 $varname = 'p'; 718 break; 719 case 'attachment': 720 $varname = 'attachment_id'; 721 break; 722 } 723 query_posts($varname . '=' . $postID); 724 if ( have_posts() ) { 725 while ( have_posts() ) { 726 the_post(); 727 $this->echo_entry(); 728 log_app('$post',print_r($GLOBALS['post'],true)); 729 $entry = ob_get_contents(); 730 break; 731 } 732 } 733 ob_end_clean(); 734 735 log_app('get_entry returning:',$entry); 736 return $entry; 737 } 738 739 function echo_entry() { ?> 740 <entry xmlns="<?php echo $this->ATOM_NS ?>" 741 xmlns:app="<?php echo $this->ATOMPUB_NS ?>" xml:lang="<?php echo get_option('rss_language'); ?>"> 742 <id><?php the_guid($GLOBALS['post']->ID); ?></id> 743 <?php list($content_type, $content) = $this->prep_content(get_the_title()); ?> 744 <title type="<?php echo $content_type ?>"><?php echo $content ?></title> 745 <updated><?php echo get_post_modified_time('Y-m-d\TH:i:s\Z', true); ?></updated> 746 <published><?php echo get_post_time('Y-m-d\TH:i:s\Z', true); ?></published> 747 <app:edited><?php echo get_post_modified_time('Y-m-d\TH:i:s\Z', true); ?></app:edited> 748 <app:control> 749 <app:draft><?php echo ($GLOBALS['post']->post_status == 'draft' ? 'yes' : 'no') ?></app:draft> 750 </app:control> 751 <author> 752 <name><?php the_author()?></name> 753 <email><?php the_author_email()?></email> 754 <?php if (get_the_author_url() && get_the_author_url() != 'http://') { ?> 755 <uri><?php the_author_url()?></uri> 756 <?php } ?> 757 </author> 758 <?php if($GLOBALS['post']->post_type == 'attachment') { ?> 759 <link rel="edit-media" href="<?php $this->the_media_url() ?>" /> 760 <content type="<?php echo $GLOBALS['post']->post_mime_type ?>" src="<?php the_guid(); ?>"/> 761 <?php } else { ?> 762 <link href="<?php the_permalink_rss() ?>" /> 763 <?php if ( strlen( $GLOBALS['post']->post_content ) ) : 764 list($content_type, $content) = $this->prep_content(get_the_content()); ?> 765 <content type="<?php echo $content_type ?>"><?php echo $content ?></content> 766 <?php endif; ?> 767 <?php } ?> 768 <link rel="edit" href="<?php $this->the_entry_url() ?>" /> 769 <?php foreach(get_the_category() as $category) { ?> 770 <category scheme="<?php bloginfo_rss('home') ?>" term="<?php echo $category->cat_name?>" /> 771 <?php } ?> 772 <?php list($content_type, $content) = $this->prep_content(get_the_excerpt()); ?> 773 <summary type="<?php echo $content_type ?>"><?php echo $content ?></summary> 774 </entry> 775 <?php } 776 777 function prep_content($data) { 778 if (strpos($data, '<') === false && strpos($data, '&') === false) { 779 return array('text', $data); 780 } 781 782 $parser = xml_parser_create(); 783 xml_parse($parser, '<div>' . $data . '</div>', true); 784 $code = xml_get_error_code($parser); 785 xml_parser_free($parser); 786 787 if (!$code) { 788 if (strpos($data, '<') === false) { 789 return array('text', $data); 790 } else { 791 $data = "<div xmlns='http://www.w3.org/1999/xhtml'>$data</div>"; 792 return array('xhtml', $data); 793 } 794 } 795 796 if (strpos($data, ']]>') == false) { 797 return array('html', "<![CDATA[$data]]>"); 798 } else { 799 return array('html', htmlspecialchars($data)); 800 } 801 } 802 803 function ok() { 804 log_app('Status','200: OK'); 805 header('Content-Type: text/plain'); 806 status_header('200'); 807 exit; 808 } 809 810 function no_content() { 811 log_app('Status','204: No Content'); 812 header('Content-Type: text/plain'); 813 status_header('204'); 814 echo "Deleted."; 815 exit; 816 } 817 818 function internal_error($msg = 'Internal Server Error') { 819 log_app('Status','500: Server Error'); 820 header('Content-Type: text/plain'); 821 status_header('500'); 822 echo $msg; 823 exit; 824 } 825 826 function bad_request() { 827 log_app('Status','400: Bad Request'); 828 header('Content-Type: text/plain'); 829 status_header('400'); 830 exit; 831 } 832 833 function length_required() { 834 log_app('Status','411: Length Required'); 835 header("HTTP/1.1 411 Length Required"); 836 header('Content-Type: text/plain'); 837 status_header('411'); 838 exit; 839 } 840 841 function invalid_media() { 842 log_app('Status','415: Unsupported Media Type'); 843 header("HTTP/1.1 415 Unsupported Media Type"); 844 header('Content-Type: text/plain'); 845 exit; 846 } 847 848 function not_found() { 849 log_app('Status','404: Not Found'); 850 header('Content-Type: text/plain'); 851 status_header('404'); 852 exit; 853 } 854 855 function not_allowed($allow) { 856 log_app('Status','405: Not Allowed'); 857 header('Allow: ' . join(',', $allow)); 858 status_header('405'); 859 exit; 860 } 861 862 function redirect($url) { 863 864 log_app('Status','302: Redirect'); 865 $escaped_url = attribute_escape($url); 866 $content = <<<EOD 867 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> 868 <html> 869 <head> 870 <title>302 Found</title> 871 </head> 872 <body> 873 <h1>Found</h1> 874 <p>The document has moved <a href="$escaped_url">here</a>.</p> 875 </body> 876 </html> 877 878 EOD; 879 header('HTTP/1.1 302 Moved'); 880 header('Content-Type: text/html'); 881 header('Location: ' . $url); 882 echo $content; 883 exit; 884 885 } 886 887 888 function client_error($msg = 'Client Error') { 889 log_app('Status','400: Client Error'); 890 header('Content-Type: text/plain'); 891 status_header('400'); 892 exit; 893 } 894 895 function created($post_ID, $content, $post_type = 'post') { 896 log_app('created()::$post_ID',"$post_ID, $post_type"); 897 $edit = $this->get_entry_url($post_ID); 898 switch($post_type) { 899 case 'post': 900 $ctloc = $this->get_entry_url($post_ID); 901 break; 902 case 'attachment': 903 $edit = get_bloginfo('url') . '/' . $this->script_name . "/attachments/$post_ID"; 904 break; 905 } 906 header("Content-Type: $this->ATOM_CONTENT_TYPE"); 907 if(isset($ctloc)) 908 header('Content-Location: ' . $ctloc); 909 header('Location: ' . $edit); 910 status_header('201'); 911 echo $content; 912 exit; 913 } 914 915 function auth_required($msg) { 916 log_app('Status','401: Auth Required'); 917 nocache_headers(); 918 header('WWW-Authenticate: Basic realm="WordPress Atom Protocol"'); 919 header("HTTP/1.1 401 $msg"); 920 header('Status: ' . $msg); 921 header('Content-Type: text/html'); 922 $content = <<<EOD 923 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> 924 <html> 925 <head> 926 <title>401 Unauthorized</title> 927 </head> 928 <body> 929 <h1>401 Unauthorized</h1> 930 <p>$msg</p> 931 </body> 932 </html> 933 934 EOD; 935 echo $content; 936 exit; 937 } 938 939 function output($xml, $ctype = 'application/atom+xml') { 940 status_header('200'); 941 $xml = '<?xml version="1.0" encoding="' . strtolower(get_option('blog_charset')) . '"?>'."\n".$xml; 942 header('Connection: close'); 943 header('Content-Length: '. strlen($xml)); 944 header('Content-Type: ' . $ctype); 945 header('Content-Disposition: attachment; filename=atom.xml'); 946 header('Date: '. date('r')); 947 if($this->do_output) 948 echo $xml; 949 log_app('function', "output:\n$xml"); 950 exit; 951 } 952 953 function escape(&$array) { 954 global $wpdb; 955 956 foreach ($array as $k => $v) { 957 if (is_array($v)) { 958 $this->escape($array[$k]); 959 } else if (is_object($v)) { 960 //skip 961 } else { 962 $array[$k] = $wpdb->escape($v); 963 } 964 } 965 } 966 967 /* 968 * Access credential through various methods and perform login 969 */ 970 function authenticate() { 971 $login_data = array(); 972 $already_md5 = false; 973 974 log_app("authenticate()",print_r($_ENV, true)); 975 976 // if using mod_rewrite/ENV hack 977 // http://www.besthostratings.com/articles/http-auth-php-cgi.html 978 if(isset($_SERVER['HTTP_AUTHORIZATION'])) { 979 list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = 980 explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6))); 981 } 982 983 // If Basic Auth is working... 984 if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) { 985 $login_data = array('login' => $_SERVER['PHP_AUTH_USER'], 'password' => $_SERVER['PHP_AUTH_PW']); 986 log_app("Basic Auth",$login_data['login']); 987 } else { 988 // else, do cookie-based authentication 989 if (function_exists('wp_get_cookie_login')) { 990 $login_data = wp_get_cookie_login(); 991 $already_md5 = true; 992 } 993 } 994 995 // call wp_login and set current user 996 if (!empty($login_data) && wp_login($login_data['login'], $login_data['password'], $already_md5)) { 997 $current_user = new WP_User(0, $login_data['login']); 998 wp_set_current_user($current_user->ID); 999 log_app("authenticate()",$login_data['login']); 1000 } 1001 } 1002 1003 function get_accepted_content_type($types = NULL) { 1004 1005 if(!isset($types)) { 1006 $types = $this->media_content_types; 1007 } 1008 1009 if(!isset($_SERVER['CONTENT_LENGTH']) || !isset($_SERVER['CONTENT_TYPE'])) { 1010 $this->length_required(); 1011 } 1012 1013 $type = $_SERVER['CONTENT_TYPE']; 1014 list($type,$subtype) = explode('/',$type); 1015 list($subtype) = explode(";",$subtype); // strip MIME parameters 1016 log_app("get_accepted_content_type", "type=$type, subtype=$subtype"); 1017 1018 foreach($types as $t) { 1019 list($acceptedType,$acceptedSubtype) = explode('/',$t); 1020 if($acceptedType == '*' || $acceptedType == $type) { 1021 if($acceptedSubtype == '*' || $acceptedSubtype == $subtype) 1022 return $type . "/" . $subtype; 1023 } 1024 } 1025 1026 $this->invalid_media(); 1027 } 1028 1029 function process_conditionals() { 1030 1031 if(empty($this->params)) return; 1032 if($_SERVER['REQUEST_METHOD'] == 'DELETE') return; 1033 1034 switch($this->params[0]) { 1035 case $this->ENTRY_PATH: 1036 global $post; 1037 $post = wp_get_single_post($this->params[1]); 1038 $wp_last_modified = get_post_modified_time('D, d M Y H:i:s', true); 1039 $post = NULL; 1040 break; 1041 case $this->ENTRIES_PATH: 1042 $wp_last_modified = mysql2date('D, d M Y H:i:s', get_lastpostmodified('GMT'), 0).' GMT'; 1043 break; 1044 default: 1045 return; 1046 } 1047 $wp_etag = md5($wp_last_modified); 1048 @header("Last-Modified: $wp_last_modified"); 1049 @header("ETag: $wp_etag"); 1050 1051 // Support for Conditional GET 1052 if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) 1053 $client_etag = stripslashes($_SERVER['HTTP_IF_NONE_MATCH']); 1054 else 1055 $client_etag = false; 1056 1057 $client_last_modified = trim( $_SERVER['HTTP_IF_MODIFIED_SINCE']); 1058 // If string is empty, return 0. If not, attempt to parse into a timestamp 1059 $client_modified_timestamp = $client_last_modified ? strtotime($client_last_modified) : 0; 1060 1061 // Make a timestamp for our most recent modification... 1062 $wp_modified_timestamp = strtotime($wp_last_modified); 1063 1064 if ( ($client_last_modified && $client_etag) ? 1065 (($client_modified_timestamp >= $wp_modified_timestamp) && ($client_etag == $wp_etag)) : 1066 (($client_modified_timestamp >= $wp_modified_timestamp) || ($client_etag == $wp_etag)) ) { 1067 status_header( 304 ); 1068 exit; 1069 } 1070 } 1071 1072 function rfc3339_str2time($str) { 1073 1074 $match = false; 1075 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)) 1076 return false; 1077 1078 if($match[3] == 'Z') 1079 $match[3] == '+0000'; 1080 1081 return strtotime($match[1] . " " . $match[2] . " " . $match[3]); 1082 } 1083 1084 function get_publish_time($entry) { 1085 1086 $pubtime = $this->rfc3339_str2time($entry->published); 1087 1088 if(!$pubtime) { 1089 return array(current_time('mysql'),current_time('mysql',1)); 1090 } else { 1091 return array(date("Y-m-d H:i:s", $pubtime), gmdate("Y-m-d H:i:s", $pubtime)); 1092 } 1093 } 1094 1095 } 1096 No newline at end of file