home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / CMS / drupal-6.0.exe / drupal-6.0 / modules / blogapi / blogapi.module < prev   
Encoding:
Text File  |  2008-02-07  |  24.0 KB  |  729 lines

  1. <?php
  2. // $Id: blogapi.module,v 1.115.2.1 2008/02/07 20:11:02 goba Exp $
  3.  
  4. /**
  5.  * @file
  6.  * Enable users to post using applications that support XML-RPC blog APIs.
  7.  */
  8.  
  9. /**
  10.  * Implementation of hook_help().
  11.  */
  12. function blogapi_help($path, $arg) {
  13.   switch ($path) {
  14.     case 'admin/help#blogapi':
  15.       $output = '<p>'. t("The Blog API module allows your site's users to access and post to their blogs from external blogging clients. External blogging clients are available for a wide range of desktop operating systems, and generally provide a feature-rich graphical environment for creating and editing posts.") .'</p>';
  16.       $output .= '<p>'. t('<a href="@ecto-link">Ecto</a>, a blogging client available for both Mac OS X and Microsoft Windows, can be used with Blog API. Blog API also supports <a href="@blogger-api">Blogger API</a>, <a href="@metaweblog-api">MetaWeblog API</a>, and most of the <a href="@movabletype-api">Movable Type API</a>. Blogging clients and other services (e.g. <a href="@flickr">Flickr\'s</a> "post to blog") that support these APIs may also be compatible.', array('@ecto-link' => url('http://infinite-sushi.com/software/ecto/'), '@blogger-api' => url('http://www.blogger.com/developers/api/1_docs/'), '@metaweblog-api' => url('http://www.xmlrpc.com/metaWeblogApi'), '@movabletype-api' => url('http://www.movabletype.org/docs/mtmanual_programmatic.html'), '@flickr' => url('http://www.flickr.com'))) .'</p>';
  17.       $output .= '<p>'. t('Select the content types available to external clients on the <a href="@blogapi-settings">Blog API settings page</a>. If supported and available, each content type will be displayed as a separate "blog" by the external client.', array('@blogapi-settings' => url('admin/settings/blogapi'))) .'</p>';
  18.       $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@blogapi">Blog API module</a>.', array('@blogapi' => url('http://drupal.org/handbook/modules/blogapi/'))) .'</p>';
  19.       return $output;
  20.   }
  21. }
  22.  
  23. /**
  24.  * Implementation of hook_perm().
  25.  */
  26. function blogapi_perm() {
  27.   return array('administer content with blog api');
  28. }
  29.  
  30. /**
  31.  * Implementation of hook_xmlrpc().
  32.  */
  33. function blogapi_xmlrpc() {
  34.   return array(
  35.     array(
  36.       'blogger.getUsersBlogs',
  37.       'blogapi_blogger_get_users_blogs',
  38.       array('array', 'string', 'string', 'string'),
  39.       t('Returns a list of blogs to which an author has posting privileges.')),
  40.     array(
  41.       'blogger.getUserInfo',
  42.       'blogapi_blogger_get_user_info',
  43.       array('struct', 'string', 'string', 'string'),
  44.       t('Returns information about an author in the system.')),
  45.     array(
  46.       'blogger.newPost',
  47.       'blogapi_blogger_new_post',
  48.       array('string', 'string', 'string', 'string', 'string', 'string', 'boolean'),
  49.       t('Creates a new post, and optionally publishes it.')),
  50.     array(
  51.       'blogger.editPost',
  52.       'blogapi_blogger_edit_post',
  53.       array('boolean', 'string', 'string', 'string', 'string', 'string', 'boolean'),
  54.       t('Updates the information about an existing post.')),
  55.     array(
  56.       'blogger.getPost',
  57.       'blogapi_blogger_get_post',
  58.       array('struct', 'string', 'string', 'string', 'string'),
  59.       t('Returns information about a specific post.')),
  60.     array(
  61.       'blogger.deletePost',
  62.       'blogapi_blogger_delete_post',
  63.       array('boolean', 'string', 'string', 'string', 'string', 'boolean'),
  64.       t('Deletes a post.')),
  65.     array(
  66.       'blogger.getRecentPosts',
  67.       'blogapi_blogger_get_recent_posts',
  68.       array('array', 'string', 'string', 'string', 'string', 'int'),
  69.       t('Returns a list of the most recent posts in the system.')),
  70.     array(
  71.       'metaWeblog.newPost',
  72.       'blogapi_metaweblog_new_post',
  73.       array('string', 'string', 'string', 'string', 'struct', 'boolean'),
  74.       t('Creates a new post, and optionally publishes it.')),
  75.     array(
  76.       'metaWeblog.editPost',
  77.       'blogapi_metaweblog_edit_post',
  78.       array('boolean', 'string', 'string', 'string', 'struct', 'boolean'),
  79.       t('Updates information about an existing post.')),
  80.     array(
  81.       'metaWeblog.getPost',
  82.       'blogapi_metaweblog_get_post',
  83.       array('struct', 'string', 'string', 'string'),
  84.       t('Returns information about a specific post.')),
  85.     array(
  86.       'metaWeblog.newMediaObject',
  87.       'blogapi_metaweblog_new_media_object',
  88.       array('string', 'string', 'string', 'string', 'struct'),
  89.       t('Uploads a file to your webserver.')),
  90.     array(
  91.       'metaWeblog.getCategories',
  92.       'blogapi_metaweblog_get_category_list',
  93.       array('struct', 'string', 'string', 'string'),
  94.       t('Returns a list of all categories to which the post is assigned.')),
  95.     array(
  96.       'metaWeblog.getRecentPosts',
  97.       'blogapi_metaweblog_get_recent_posts',
  98.       array('array', 'string', 'string', 'string', 'int'),
  99.       t('Returns a list of the most recent posts in the system.')),
  100.     array(
  101.       'mt.getRecentPostTitles',
  102.       'blogapi_mt_get_recent_post_titles',
  103.       array('array', 'string', 'string', 'string', 'int'),
  104.       t('Returns a bandwidth-friendly list of the most recent posts in the system.')),
  105.     array(
  106.       'mt.getCategoryList',
  107.       'blogapi_mt_get_category_list',
  108.       array('array', 'string', 'string', 'string'),
  109.       t('Returns a list of all categories defined in the blog.')),
  110.     array(
  111.       'mt.getPostCategories',
  112.       'blogapi_mt_get_post_categories',
  113.       array('array', 'string', 'string', 'string'),
  114.       t('Returns a list of all categories to which the post is assigned.')),
  115.     array(
  116.       'mt.setPostCategories',
  117.       'blogapi_mt_set_post_categories',
  118.       array('boolean', 'string', 'string', 'string', 'array'),
  119.       t('Sets the categories for a post.')),
  120.     array(
  121.       'mt.supportedMethods',
  122.       'xmlrpc_server_list_methods',
  123.       array('array'),
  124.       t('Retrieve information about the XML-RPC methods supported by the server.')),
  125.     array(
  126.       'mt.supportedTextFilters',
  127.       'blogapi_mt_supported_text_filters',
  128.       array('array'),
  129.       t('Retrieve information about the text formatting plugins supported by the server.')),
  130.     array(
  131.       'mt.publishPost',
  132.       'blogap_mti_publish_post',
  133.       array('boolean', 'string', 'string', 'string'),
  134.       t('Publish (rebuild) all of the static files related to an entry from your blog. Equivalent to saving an entry in the system (but without the ping).')));
  135. }
  136.  
  137. /**
  138.  * Blogging API callback. Finds the URL of a user's blog.
  139.  */
  140.  
  141. function blogapi_blogger_get_users_blogs($appid, $username, $password) {
  142.  
  143.   $user = blogapi_validate_user($username, $password);
  144.   if ($user->uid) {
  145.     $types = _blogapi_get_node_types();
  146.     $structs = array();
  147.     foreach ($types as $type) {
  148.       $structs[] = array('url' => url('blog/'. $user->uid, array('absolute' => TRUE)), 'blogid' => $type, 'blogName' => $user->name .": ". $type);
  149.     }
  150.     return $structs;
  151.   }
  152.   else {
  153.     return blogapi_error($user);
  154.   }
  155. }
  156.  
  157. /**
  158.  * Blogging API callback. Returns profile information about a user.
  159.  */
  160. function blogapi_blogger_get_user_info($appkey, $username, $password) {
  161.   $user = blogapi_validate_user($username, $password);
  162.  
  163.   if ($user->uid) {
  164.     $name = explode(' ', $user->realname ? $user->realname : $user->name, 2);
  165.     return array(
  166.       'userid' => $user->uid,
  167.       'lastname' => $name[1],
  168.       'firstname' => $name[0],
  169.       'nickname' => $user->name,
  170.       'email' => $user->mail,
  171.       'url' => url('blog/'. $user->uid, array('absolute' => TRUE)));
  172.   }
  173.   else {
  174.     return blogapi_error($user);
  175.   }
  176. }
  177.  
  178. /**
  179.  * Blogging API callback. Inserts a new blog post as a node.
  180.  */
  181. function blogapi_blogger_new_post($appkey, $blogid, $username, $password, $content, $publish) {
  182.   $user = blogapi_validate_user($username, $password);
  183.   if (!$user->uid) {
  184.     return blogapi_error($user);
  185.   }
  186.  
  187.   if (($error = _blogapi_validate_blogid($blogid)) !== TRUE) {
  188.     // Return an error if not configured type.
  189.     return $error;
  190.   }
  191.  
  192.   $edit = array();
  193.   $edit['type'] = $blogid;
  194.   // get the node type defaults
  195.   $node_type_default = variable_get('node_options_'. $edit['type'], array('status', 'promote'));
  196.   $edit['uid'] = $user->uid;
  197.   $edit['name'] = $user->name;
  198.   $edit['promote'] = in_array('promote', $node_type_default);
  199.   $edit['comment'] = variable_get('comment_'. $edit['type'], 2);
  200.   $edit['revision'] = in_array('revision', $node_type_default);
  201.   $edit['format'] = FILTER_FORMAT_DEFAULT;
  202.   $edit['status'] = $publish;
  203.  
  204.   // check for bloggerAPI vs. metaWeblogAPI
  205.   if (is_array($content)) {
  206.     $edit['title'] = $content['title'];
  207.     $edit['body'] = $content['description'];
  208.     _blogapi_mt_extra($edit, $content);
  209.   }
  210.   else {
  211.     $edit['title'] = blogapi_blogger_title($content);
  212.     $edit['body'] = $content;
  213.   }
  214.  
  215.   if (!node_access('create', $edit['type'])) {
  216.     return blogapi_error(t('You do not have permission to create this type of post.'));
  217.   }
  218.  
  219.   if (user_access('administer nodes') && !isset($edit['date'])) {
  220.     $edit['date'] = format_date(time(), 'custom', 'Y-m-d H:i:s O');
  221.   }
  222.  
  223.   node_invoke_nodeapi($edit, 'blogapi new');
  224.  
  225.   node_validate($edit);
  226.   if ($errors = form_get_errors()) {
  227.     return blogapi_error(implode("\n", $errors));
  228.   }
  229.  
  230.   $node = node_submit($edit);
  231.   node_save($node);
  232.   if ($node->nid) {
  233.     watchdog('content', '@type: added %title using blog API.', array('@type' => $node->type, '%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), "node/$node->nid"));
  234.     // blogger.newPost returns a string so we cast the nid to a string by putting it in double quotes:
  235.     return "$node->nid";
  236.   }
  237.  
  238.   return blogapi_error(t('Error storing post.'));
  239. }
  240.  
  241. /**
  242.  * Blogging API callback. Modifies the specified blog node.
  243.  */
  244. function blogapi_blogger_edit_post($appkey, $postid, $username, $password, $content, $publish) {
  245.  
  246.   $user = blogapi_validate_user($username, $password);
  247.  
  248.   if (!$user->uid) {
  249.     return blogapi_error($user);
  250.   }
  251.  
  252.   $node = node_load($postid);
  253.   if (!$node) {
  254.     return blogapi_error(t('n/a'));
  255.   }
  256.   // Let the teaser be re-generated.
  257.   unset($node->teaser);
  258.  
  259.   if (!node_access('update', $node)) {
  260.     return blogapi_error(t('You do not have permission to update this post.'));
  261.   }
  262.  
  263.   $node->status = $publish;
  264.  
  265.   // check for bloggerAPI vs. metaWeblogAPI
  266.   if (is_array($content)) {
  267.     $node->title = $content['title'];
  268.     $node->body = $content['description'];
  269.     _blogapi_mt_extra($node, $content);
  270.   }
  271.   else {
  272.     $node->title = blogapi_blogger_title($content);
  273.     $node->body = $content;
  274.   }
  275.  
  276.   node_invoke_nodeapi($node, 'blogapi edit');
  277.  
  278.   node_validate($node);
  279.   if ($errors = form_get_errors()) {
  280.     return blogapi_error(implode("\n", $errors));
  281.   }
  282.  
  283.   if (user_access('administer nodes') && !isset($edit['date'])) {
  284.     $node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O');
  285.   }
  286.   $node = node_submit($node);
  287.   node_save($node);
  288.   if ($node->nid) {
  289.     watchdog('content', '@type: updated %title using Blog API.', array('@type' => $node->type, '%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), "node/$node->nid"));
  290.     return TRUE;
  291.   }
  292.  
  293.   return blogapi_error(t('Error storing post.'));
  294. }
  295.  
  296. /**
  297.  * Blogging API callback. Returns a specified blog node.
  298.  */
  299. function blogapi_blogger_get_post($appkey, $postid, $username, $password) {
  300.   $user = blogapi_validate_user($username, $password);
  301.   if (!$user->uid) {
  302.     return blogapi_error($user);
  303.   }
  304.  
  305.   $node = node_load($postid);
  306.  
  307.   return _blogapi_get_post($node, TRUE);
  308. }
  309.  
  310. /**
  311.  * Blogging API callback. Removes the specified blog node.
  312.  */
  313. function blogapi_blogger_delete_post($appkey, $postid, $username, $password, $publish) {
  314.   $user = blogapi_validate_user($username, $password);
  315.   if (!$user->uid) {
  316.     return blogapi_error($user);
  317.   }
  318.  
  319.   node_delete($postid);
  320.   return TRUE;
  321. }
  322.  
  323. /**
  324.  * Blogging API callback. Returns the latest few postings in a user's blog. $bodies TRUE
  325.  * <a href="http://movabletype.org/docs/mtmanual_programmatic.html#item_mt%2EgetRecentPostTitles">
  326.  * returns a bandwidth-friendly list</a>.
  327.  */
  328. function blogapi_blogger_get_recent_posts($appkey, $blogid, $username, $password, $number_of_posts, $bodies = TRUE) {
  329.   // Remove unused appkey (from bloggerAPI).
  330.   $user = blogapi_validate_user($username, $password);
  331.   if (!$user->uid) {
  332.     return blogapi_error($user);
  333.   }
  334.  
  335.   if (($error = _blogapi_validate_blogid($blogid)) !== TRUE) {
  336.     // Return an error if not configured type.
  337.     return $error;
  338.   }
  339.  
  340.   if ($bodies) {
  341.     $result = db_query_range("SELECT n.nid, n.title, r.body, r.format, n.comment, n.created, u.name FROM {node} n, {node_revisions} r, {users} u WHERE n.uid = u.uid AND n.vid = r.vid AND n.type = '%s' AND n.uid = %d ORDER BY n.created DESC",  $blogid, $user->uid, 0, $number_of_posts);
  342.   }
  343.   else {
  344.     $result = db_query_range("SELECT n.nid, n.title, n.created, u.name FROM {node} n, {users} u WHERE n.uid = u.uid AND n.type = '%s' AND n.uid = %d ORDER BY n.created DESC", $blogid, $user->uid, 0, $number_of_posts);
  345.   }
  346.   $blogs = array();
  347.   while ($blog = db_fetch_object($result)) {
  348.     $blogs[] = _blogapi_get_post($blog, $bodies);
  349.   }
  350.   return $blogs;
  351. }
  352.  
  353. function blogapi_metaweblog_new_post($blogid, $username, $password, $content, $publish) {
  354.   return blogapi_blogger_new_post('0123456789ABCDEF', $blogid, $username, $password, $content, $publish);
  355. }
  356.  
  357. function blogapi_metaweblog_edit_post($postid, $username, $password, $content, $publish) {
  358.   return blogapi_blogger_edit_post('0123456789ABCDEF', $postid, $username, $password, $content, $publish);
  359. }
  360.  
  361. function blogapi_metaweblog_get_post($postid, $username, $password) {
  362.   return blogapi_blogger_get_post('01234567890ABCDEF', $postid, $username, $password);
  363. }
  364.  
  365. /**
  366.  * Blogging API callback. Inserts a file into Drupal.
  367.  */
  368. function blogapi_metaweblog_new_media_object($blogid, $username, $password, $file) {
  369.   $user = blogapi_validate_user($username, $password);
  370.   if (!$user->uid) {
  371.     return blogapi_error($user);
  372.   }
  373.  
  374.   $name = basename($file['name']);
  375.   $data = $file['bits'];
  376.  
  377.   if (!$data) {
  378.     return blogapi_error(t('No file sent.'));
  379.   }
  380.  
  381.   if (!$file = file_save_data($data, $name)) {
  382.     return blogapi_error(t('Error storing file.'));
  383.   }
  384.  
  385.   // Return the successful result.
  386.   return array('url' => file_create_url($file), 'struct');
  387. }
  388. /**
  389.  * Blogging API callback. Returns a list of the taxonomy terms that can be
  390.  * associated with a blog node.
  391.  */
  392. function blogapi_metaweblog_get_category_list($blogid, $username, $password) {
  393.   if (($error = _blogapi_validate_blogid($blogid)) !== TRUE) {
  394.     // Return an error if not configured type.
  395.     return $error;
  396.   }
  397.  
  398.   $vocabularies = module_invoke('taxonomy', 'get_vocabularies', $blogid, 'vid');
  399.   $categories = array();
  400.   if ($vocabularies) {
  401.     foreach ($vocabularies as $vocabulary) {
  402.       $terms = module_invoke('taxonomy', 'get_tree', $vocabulary->vid, 0, -1);
  403.       foreach ($terms as $term) {
  404.         $term_name = $term->name;
  405.         foreach (module_invoke('taxonomy', 'get_parents', $term->tid, 'tid') as $parent) {
  406.           $term_name = $parent->name .'/'. $term_name;
  407.         }
  408.         $categories[] = array('categoryName' => $term_name, 'categoryId' => $term->tid);
  409.       }
  410.     }
  411.   }
  412.   return $categories;
  413. }
  414.  
  415. function blogapi_metaweblog_get_recent_posts($blogid, $username, $password, $number_of_posts) {
  416.   return blogapi_blogger_get_recent_posts('0123456789ABCDEF', $blogid, $username, $password, $number_of_posts, TRUE);
  417. }
  418.  
  419. function blogapi_mt_get_recent_post_titles($blogid, $username, $password, $number_of_posts) {
  420.   return blogapi_blogger_get_recent_posts('0123456789ABCDEF', $blogid, $username, $password, $number_of_posts, FALSE);
  421. }
  422.  
  423. function blogapi_mt_get_category_list($blogid, $username, $password) {
  424.   return blogapi_metaweblog_get_category_list($blogid, $username, $password);
  425. }
  426.  
  427. /**
  428.  * Blogging API callback. Returns a list of the taxonomy terms that are
  429.  * assigned to a particular node.
  430.  */
  431. function blogapi_mt_get_post_categories($postid, $username, $password) {
  432.   $user = blogapi_validate_user($username, $password);
  433.   if (!$user->uid) {
  434.     return blogapi_error($user);
  435.   }
  436.  
  437.   $node = node_load($postid);
  438.   $terms = module_invoke('taxonomy', 'node_get_terms', $node, 'tid');
  439.   $categories = array();
  440.   foreach ($terms as $term) {
  441.     $term_name = $term->name;
  442.     foreach (module_invoke('taxonomy', 'get_parents', $term->tid, 'tid') as $parent) {
  443.       $term_name = $parent->name .'/'. $term_name;
  444.     }
  445.     $categories[] = array('categoryName' => $term_name, 'categoryId' => $term->tid, 'isPrimary' => TRUE);
  446.   }
  447.  
  448.   return $categories;
  449. }
  450.  
  451. /**
  452.  * Blogging API callback. Assigns taxonomy terms to a particular node.
  453.  */
  454. function blogapi_mt_set_post_categories($postid, $username, $password, $categories) {
  455.   $user = blogapi_validate_user($username, $password);
  456.   if (!$user->uid) {
  457.     return blogapi_error($user);
  458.   }
  459.  
  460.   $node = node_load($postid);
  461.   $node->taxonomy = array();
  462.   foreach ($categories as $category) {
  463.     $node->taxonomy[] = $category['categoryId'];
  464.   }
  465.   node_save($node);
  466.   return TRUE;
  467. }
  468.  
  469. /**
  470.  * Blogging API callback. Sends a list of available input formats.
  471.  */
  472. function blogapi_mt_supported_text_filters() {
  473.   // NOTE: we're only using anonymous' formats because the MT spec
  474.   // does not allow for per-user formats.
  475.   $formats = filter_formats();
  476.  
  477.   $filters = array();
  478.   foreach ($formats as $format) {
  479.     $filter['key'] = $format->format;
  480.     $filter['label'] = $format->name;
  481.     $filters[] = $filter;
  482.   }
  483.  
  484.   return $filters;
  485. }
  486.  
  487. /**
  488.  * Blogging API callback. Publishes the given node
  489.  */
  490. function blogap_mti_publish_post($postid, $username, $password) {
  491.   $user = blogapi_validate_user($username, $password);
  492.   if (!$user->uid) {
  493.     return blogapi_error($user);
  494.   }
  495.   $node = node_load($postid);
  496.   if (!$node) {
  497.     return blogapi_error(t('Invalid post.'));
  498.   }
  499.  
  500.   $node->status = 1;
  501.   if (!node_access('update', $node)) {
  502.     return blogapi_error(t('You do not have permission to update this post.'));
  503.   }
  504.  
  505.   node_save($node);
  506.  
  507.   return TRUE;
  508. }
  509.  
  510. /**
  511.  * Prepare an error message for returning to the XMLRPC caller.
  512.  */
  513. function blogapi_error($message) {
  514.   static $xmlrpcusererr;
  515.   if (!is_array($message)) {
  516.     $message = array($message);
  517.   }
  518.  
  519.   $message = implode(' ', $message);
  520.  
  521.   return xmlrpc_error($xmlrpcusererr + 1, strip_tags($message));
  522. }
  523.  
  524. /**
  525.  * Ensure that the given user has permission to edit a blog.
  526.  */
  527. function blogapi_validate_user($username, $password) {
  528.   global $user;
  529.  
  530.   $user = user_authenticate(array('name' => $username, 'pass' => $password));
  531.  
  532.   if ($user->uid) {
  533.     if (user_access('administer content with blog api', $user)) {
  534.       return $user;
  535.     }
  536.     else {
  537.       return t('You do not have permission to edit this blog.');
  538.     }
  539.   }
  540.   else {
  541.     return t('Wrong username or password.');
  542.   }
  543. }
  544.  
  545. /**
  546.  * For the blogger API, extract the node title from the contents field.
  547.  */
  548. function blogapi_blogger_title(&$contents) {
  549.   if (eregi('<title>([^<]*)</title>', $contents, $title)) {
  550.     $title = strip_tags($title[0]);
  551.     $contents = ereg_replace('<title>[^<]*</title>', '', $contents);
  552.   }
  553.   else {
  554.     list($title, $contents) = explode("\n", $contents, 2);
  555.   }
  556.   return $title;
  557. }
  558.  
  559. function blogapi_admin_settings() {
  560.   $node_types = array_map('check_plain', node_get_types('names'));
  561.   $defaults = isset($node_types['blog']) ? array('blog' => 1) : array();
  562.   $form['blogapi_node_types'] = array(
  563.     '#type' => 'checkboxes',
  564.     '#title' => t('Enable for external blogging clients'),
  565.     '#required' => TRUE,
  566.     '#default_value' => variable_get('blogapi_node_types', $defaults),
  567.     '#options' => $node_types,
  568.     '#description' => t('Select the content types available to external blogging clients via Blog API. If supported, each enabled content type will be displayed as a separate "blog" by the external client.')
  569.   );
  570.  
  571.   return system_settings_form($form);
  572. }
  573.  
  574. function blogapi_menu() {
  575.   $items['blogapi/rsd'] = array(
  576.     'title' => 'RSD',
  577.     'page callback' => 'blogapi_rsd',
  578.     'access arguments' => array('access content'),
  579.     'type' => MENU_CALLBACK,
  580.   );
  581.   $items['admin/settings/blogapi'] = array(
  582.     'title' => 'Blog API',
  583.     'description' => 'Configure the content types available to external blogging clients.',
  584.     'page callback' => 'drupal_get_form',
  585.     'page arguments' => array('blogapi_admin_settings'),
  586.     'access arguments' => array('administer site configuration'),
  587.     'type' => MENU_NORMAL_ITEM,
  588.   );
  589.  
  590.   return $items;
  591. }
  592.  
  593. function blogapi_init() {
  594.   if (drupal_is_front_page()) {
  595.     drupal_add_link(array('rel' => 'EditURI',
  596.                           'type' => 'application/rsd+xml',
  597.                           'title' => t('RSD'),
  598.                           'href' => url('blogapi/rsd', array('absolute' => TRUE))));
  599.   }
  600. }
  601.  
  602. function blogapi_rsd() {
  603.   global $base_url;
  604.  
  605.   $xmlrpc = $base_url .'/xmlrpc.php';
  606.   $base = url('', array('absolute' => TRUE));
  607.   $blogid = 1; # until we figure out how to handle multiple bloggers
  608.  
  609.   drupal_set_header('Content-Type: application/rsd+xml; charset=utf-8');
  610.   print <<<__RSD__
  611. <?xml version="1.0"?>
  612. <rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">
  613.   <service>
  614.     <engineName>Drupal</engineName>
  615.     <engineLink>http://drupal.org/</engineLink>
  616.     <homePageLink>$base</homePageLink>
  617.     <apis>
  618.       <api name="MetaWeblog" preferred="false" apiLink="$xmlrpc" blogID="$blogid" />
  619.       <api name="Blogger" preferred="false" apiLink="$xmlrpc" blogID="$blogid" />
  620.       <api name="MovableType" preferred="true" apiLink="$xmlrpc" blogID="$blogid" />
  621.     </apis>
  622.   </service>
  623. </rsd>
  624. __RSD__;
  625. }
  626.  
  627. /**
  628.  * Handles extra information sent by clients according to MovableType's spec.
  629.  */
  630. function _blogapi_mt_extra(&$node, $struct) {
  631.   if (is_array($node)) {
  632.     $was_array = TRUE;
  633.     $node = (object)$node;
  634.   }
  635.  
  636.   // mt_allow_comments
  637.   if (array_key_exists('mt_allow_comments', $struct)) {
  638.     switch ($struct['mt_allow_comments']) {
  639.       case 0:
  640.         $node->comment = COMMENT_NODE_DISABLED;
  641.         break;
  642.       case 1:
  643.         $node->comment = COMMENT_NODE_READ_WRITE;
  644.         break;
  645.       case 2:
  646.         $node->comment = COMMENT_NODE_READ_ONLY;
  647.         break;
  648.     }
  649.   }
  650.  
  651.   // merge the 3 body sections (description, mt_excerpt, mt_text_more) into
  652.   // one body
  653.   if ($struct['mt_excerpt']) {
  654.     $node->body = $struct['mt_excerpt'] .'<!--break-->'. $node->body;
  655.   }
  656.   if ($struct['mt_text_more']) {
  657.     $node->body = $node->body .'<!--extended-->'. $struct['mt_text_more'];
  658.   }
  659.  
  660.   // mt_convert_breaks
  661.   if ($struct['mt_convert_breaks']) {
  662.     $node->format = $struct['mt_convert_breaks'];
  663.   }
  664.  
  665.   // dateCreated
  666.   if ($struct['dateCreated']) {
  667.     $node->date = format_date(mktime($struct['dateCreated']->hour, $struct['dateCreated']->minute, $struct['dateCreated']->second, $struct['dateCreated']->month, $struct['dateCreated']->day, $struct['dateCreated']->year), 'custom', 'Y-m-d H:i:s O');
  668.   }
  669.  
  670.   if ($was_array) {
  671.     $node = (array)$node;
  672.   }
  673. }
  674.  
  675. function _blogapi_get_post($node, $bodies = TRUE) {
  676.   $xmlrpcval = array(
  677.     'userid' => $node->name,
  678.     'dateCreated' => xmlrpc_date($node->created),
  679.     'title' => $node->title,
  680.     'postid' => $node->nid,
  681.     'link' => url('node/'. $node->nid, array('absolute' => TRUE)),
  682.     'permaLink' => url('node/'. $node->nid, array('absolute' => TRUE)),
  683.   );
  684.   if ($bodies) {
  685.     if ($node->comment == 1) {
  686.       $comment = 2;
  687.     }
  688.     else if ($node->comment == 2) {
  689.       $comment = 1;
  690.     }
  691.     $xmlrpcval['content'] = "<title>$node->title</title>$node->body";
  692.     $xmlrpcval['description'] = $node->body;
  693.     // Add MT specific fields
  694.     $xmlrpcval['mt_allow_comments'] = (int) $comment;
  695.     $xmlrpcval['mt_convert_breaks'] = $node->format;
  696.   }
  697.  
  698.   return $xmlrpcval;
  699. }
  700.  
  701. /**
  702.  * Validate blog ID, which maps to a content type in Drupal.
  703.  *
  704.  * Only content types configured to work with Blog API are supported.
  705.  *
  706.  * @return
  707.  *   TRUE if the content type is supported and the user has permission
  708.  *   to post, or a blogapi_error() XML construct otherwise.
  709.  */
  710. function _blogapi_validate_blogid($blogid) {
  711.   $types = _blogapi_get_node_types();
  712.   if (in_array($blogid, $types, TRUE)) {
  713.     return TRUE;
  714.   }
  715.   return blogapi_error(t("Blog API module is not configured to support the %type content type, or you don't have sufficient permissions to post this type of content.", array('%type' => $blogid)));
  716. }
  717.  
  718. function _blogapi_get_node_types() {
  719.   $available_types = array_keys(array_filter(variable_get('blogapi_node_types', array('blog' => 1))));
  720.   $types = array();
  721.   foreach (node_get_types() as $type => $name) {
  722.     if (node_access('create', $type) && in_array($type, $available_types)) {
  723.       $types[] = $type;
  724.     }
  725.   }
  726.  
  727.   return $types;
  728. }
  729.