home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Blogs / wordpress2.6.exe / wordpress2.6 / wp-app.php < prev    next >
Encoding:
PHP Script  |  2008-07-22  |  33.3 KB  |  1,186 lines

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