home *** CD-ROM | disk | FTP | other *** search
Wrap
<?php // $Id: blogapi.module,v 1.115.2.1 2008/02/07 20:11:02 goba Exp $ /** * @file * Enable users to post using applications that support XML-RPC blog APIs. */ /** * Implementation of hook_help(). */ function blogapi_help($path, $arg) { switch ($path) { case 'admin/help#blogapi': $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>'; $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>'; $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>'; $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>'; return $output; } } /** * Implementation of hook_perm(). */ function blogapi_perm() { return array('administer content with blog api'); } /** * Implementation of hook_xmlrpc(). */ function blogapi_xmlrpc() { return array( array( 'blogger.getUsersBlogs', 'blogapi_blogger_get_users_blogs', array('array', 'string', 'string', 'string'), t('Returns a list of blogs to which an author has posting privileges.')), array( 'blogger.getUserInfo', 'blogapi_blogger_get_user_info', array('struct', 'string', 'string', 'string'), t('Returns information about an author in the system.')), array( 'blogger.newPost', 'blogapi_blogger_new_post', array('string', 'string', 'string', 'string', 'string', 'string', 'boolean'), t('Creates a new post, and optionally publishes it.')), array( 'blogger.editPost', 'blogapi_blogger_edit_post', array('boolean', 'string', 'string', 'string', 'string', 'string', 'boolean'), t('Updates the information about an existing post.')), array( 'blogger.getPost', 'blogapi_blogger_get_post', array('struct', 'string', 'string', 'string', 'string'), t('Returns information about a specific post.')), array( 'blogger.deletePost', 'blogapi_blogger_delete_post', array('boolean', 'string', 'string', 'string', 'string', 'boolean'), t('Deletes a post.')), array( 'blogger.getRecentPosts', 'blogapi_blogger_get_recent_posts', array('array', 'string', 'string', 'string', 'string', 'int'), t('Returns a list of the most recent posts in the system.')), array( 'metaWeblog.newPost', 'blogapi_metaweblog_new_post', array('string', 'string', 'string', 'string', 'struct', 'boolean'), t('Creates a new post, and optionally publishes it.')), array( 'metaWeblog.editPost', 'blogapi_metaweblog_edit_post', array('boolean', 'string', 'string', 'string', 'struct', 'boolean'), t('Updates information about an existing post.')), array( 'metaWeblog.getPost', 'blogapi_metaweblog_get_post', array('struct', 'string', 'string', 'string'), t('Returns information about a specific post.')), array( 'metaWeblog.newMediaObject', 'blogapi_metaweblog_new_media_object', array('string', 'string', 'string', 'string', 'struct'), t('Uploads a file to your webserver.')), array( 'metaWeblog.getCategories', 'blogapi_metaweblog_get_category_list', array('struct', 'string', 'string', 'string'), t('Returns a list of all categories to which the post is assigned.')), array( 'metaWeblog.getRecentPosts', 'blogapi_metaweblog_get_recent_posts', array('array', 'string', 'string', 'string', 'int'), t('Returns a list of the most recent posts in the system.')), array( 'mt.getRecentPostTitles', 'blogapi_mt_get_recent_post_titles', array('array', 'string', 'string', 'string', 'int'), t('Returns a bandwidth-friendly list of the most recent posts in the system.')), array( 'mt.getCategoryList', 'blogapi_mt_get_category_list', array('array', 'string', 'string', 'string'), t('Returns a list of all categories defined in the blog.')), array( 'mt.getPostCategories', 'blogapi_mt_get_post_categories', array('array', 'string', 'string', 'string'), t('Returns a list of all categories to which the post is assigned.')), array( 'mt.setPostCategories', 'blogapi_mt_set_post_categories', array('boolean', 'string', 'string', 'string', 'array'), t('Sets the categories for a post.')), array( 'mt.supportedMethods', 'xmlrpc_server_list_methods', array('array'), t('Retrieve information about the XML-RPC methods supported by the server.')), array( 'mt.supportedTextFilters', 'blogapi_mt_supported_text_filters', array('array'), t('Retrieve information about the text formatting plugins supported by the server.')), array( 'mt.publishPost', 'blogap_mti_publish_post', array('boolean', 'string', 'string', 'string'), 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).'))); } /** * Blogging API callback. Finds the URL of a user's blog. */ function blogapi_blogger_get_users_blogs($appid, $username, $password) { $user = blogapi_validate_user($username, $password); if ($user->uid) { $types = _blogapi_get_node_types(); $structs = array(); foreach ($types as $type) { $structs[] = array('url' => url('blog/'. $user->uid, array('absolute' => TRUE)), 'blogid' => $type, 'blogName' => $user->name .": ". $type); } return $structs; } else { return blogapi_error($user); } } /** * Blogging API callback. Returns profile information about a user. */ function blogapi_blogger_get_user_info($appkey, $username, $password) { $user = blogapi_validate_user($username, $password); if ($user->uid) { $name = explode(' ', $user->realname ? $user->realname : $user->name, 2); return array( 'userid' => $user->uid, 'lastname' => $name[1], 'firstname' => $name[0], 'nickname' => $user->name, 'email' => $user->mail, 'url' => url('blog/'. $user->uid, array('absolute' => TRUE))); } else { return blogapi_error($user); } } /** * Blogging API callback. Inserts a new blog post as a node. */ function blogapi_blogger_new_post($appkey, $blogid, $username, $password, $content, $publish) { $user = blogapi_validate_user($username, $password); if (!$user->uid) { return blogapi_error($user); } if (($error = _blogapi_validate_blogid($blogid)) !== TRUE) { // Return an error if not configured type. return $error; } $edit = array(); $edit['type'] = $blogid; // get the node type defaults $node_type_default = variable_get('node_options_'. $edit['type'], array('status', 'promote')); $edit['uid'] = $user->uid; $edit['name'] = $user->name; $edit['promote'] = in_array('promote', $node_type_default); $edit['comment'] = variable_get('comment_'. $edit['type'], 2); $edit['revision'] = in_array('revision', $node_type_default); $edit['format'] = FILTER_FORMAT_DEFAULT; $edit['status'] = $publish; // check for bloggerAPI vs. metaWeblogAPI if (is_array($content)) { $edit['title'] = $content['title']; $edit['body'] = $content['description']; _blogapi_mt_extra($edit, $content); } else { $edit['title'] = blogapi_blogger_title($content); $edit['body'] = $content; } if (!node_access('create', $edit['type'])) { return blogapi_error(t('You do not have permission to create this type of post.')); } if (user_access('administer nodes') && !isset($edit['date'])) { $edit['date'] = format_date(time(), 'custom', 'Y-m-d H:i:s O'); } node_invoke_nodeapi($edit, 'blogapi new'); node_validate($edit); if ($errors = form_get_errors()) { return blogapi_error(implode("\n", $errors)); } $node = node_submit($edit); node_save($node); if ($node->nid) { watchdog('content', '@type: added %title using blog API.', array('@type' => $node->type, '%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), "node/$node->nid")); // blogger.newPost returns a string so we cast the nid to a string by putting it in double quotes: return "$node->nid"; } return blogapi_error(t('Error storing post.')); } /** * Blogging API callback. Modifies the specified blog node. */ function blogapi_blogger_edit_post($appkey, $postid, $username, $password, $content, $publish) { $user = blogapi_validate_user($username, $password); if (!$user->uid) { return blogapi_error($user); } $node = node_load($postid); if (!$node) { return blogapi_error(t('n/a')); } // Let the teaser be re-generated. unset($node->teaser); if (!node_access('update', $node)) { return blogapi_error(t('You do not have permission to update this post.')); } $node->status = $publish; // check for bloggerAPI vs. metaWeblogAPI if (is_array($content)) { $node->title = $content['title']; $node->body = $content['description']; _blogapi_mt_extra($node, $content); } else { $node->title = blogapi_blogger_title($content); $node->body = $content; } node_invoke_nodeapi($node, 'blogapi edit'); node_validate($node); if ($errors = form_get_errors()) { return blogapi_error(implode("\n", $errors)); } if (user_access('administer nodes') && !isset($edit['date'])) { $node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O'); } $node = node_submit($node); node_save($node); if ($node->nid) { watchdog('content', '@type: updated %title using Blog API.', array('@type' => $node->type, '%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), "node/$node->nid")); return TRUE; } return blogapi_error(t('Error storing post.')); } /** * Blogging API callback. Returns a specified blog node. */ function blogapi_blogger_get_post($appkey, $postid, $username, $password) { $user = blogapi_validate_user($username, $password); if (!$user->uid) { return blogapi_error($user); } $node = node_load($postid); return _blogapi_get_post($node, TRUE); } /** * Blogging API callback. Removes the specified blog node. */ function blogapi_blogger_delete_post($appkey, $postid, $username, $password, $publish) { $user = blogapi_validate_user($username, $password); if (!$user->uid) { return blogapi_error($user); } node_delete($postid); return TRUE; } /** * Blogging API callback. Returns the latest few postings in a user's blog. $bodies TRUE * <a href="http://movabletype.org/docs/mtmanual_programmatic.html#item_mt%2EgetRecentPostTitles"> * returns a bandwidth-friendly list</a>. */ function blogapi_blogger_get_recent_posts($appkey, $blogid, $username, $password, $number_of_posts, $bodies = TRUE) { // Remove unused appkey (from bloggerAPI). $user = blogapi_validate_user($username, $password); if (!$user->uid) { return blogapi_error($user); } if (($error = _blogapi_validate_blogid($blogid)) !== TRUE) { // Return an error if not configured type. return $error; } if ($bodies) { $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); } else { $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); } $blogs = array(); while ($blog = db_fetch_object($result)) { $blogs[] = _blogapi_get_post($blog, $bodies); } return $blogs; } function blogapi_metaweblog_new_post($blogid, $username, $password, $content, $publish) { return blogapi_blogger_new_post('0123456789ABCDEF', $blogid, $username, $password, $content, $publish); } function blogapi_metaweblog_edit_post($postid, $username, $password, $content, $publish) { return blogapi_blogger_edit_post('0123456789ABCDEF', $postid, $username, $password, $content, $publish); } function blogapi_metaweblog_get_post($postid, $username, $password) { return blogapi_blogger_get_post('01234567890ABCDEF', $postid, $username, $password); } /** * Blogging API callback. Inserts a file into Drupal. */ function blogapi_metaweblog_new_media_object($blogid, $username, $password, $file) { $user = blogapi_validate_user($username, $password); if (!$user->uid) { return blogapi_error($user); } $name = basename($file['name']); $data = $file['bits']; if (!$data) { return blogapi_error(t('No file sent.')); } if (!$file = file_save_data($data, $name)) { return blogapi_error(t('Error storing file.')); } // Return the successful result. return array('url' => file_create_url($file), 'struct'); } /** * Blogging API callback. Returns a list of the taxonomy terms that can be * associated with a blog node. */ function blogapi_metaweblog_get_category_list($blogid, $username, $password) { if (($error = _blogapi_validate_blogid($blogid)) !== TRUE) { // Return an error if not configured type. return $error; } $vocabularies = module_invoke('taxonomy', 'get_vocabularies', $blogid, 'vid'); $categories = array(); if ($vocabularies) { foreach ($vocabularies as $vocabulary) { $terms = module_invoke('taxonomy', 'get_tree', $vocabulary->vid, 0, -1); foreach ($terms as $term) { $term_name = $term->name; foreach (module_invoke('taxonomy', 'get_parents', $term->tid, 'tid') as $parent) { $term_name = $parent->name .'/'. $term_name; } $categories[] = array('categoryName' => $term_name, 'categoryId' => $term->tid); } } } return $categories; } function blogapi_metaweblog_get_recent_posts($blogid, $username, $password, $number_of_posts) { return blogapi_blogger_get_recent_posts('0123456789ABCDEF', $blogid, $username, $password, $number_of_posts, TRUE); } function blogapi_mt_get_recent_post_titles($blogid, $username, $password, $number_of_posts) { return blogapi_blogger_get_recent_posts('0123456789ABCDEF', $blogid, $username, $password, $number_of_posts, FALSE); } function blogapi_mt_get_category_list($blogid, $username, $password) { return blogapi_metaweblog_get_category_list($blogid, $username, $password); } /** * Blogging API callback. Returns a list of the taxonomy terms that are * assigned to a particular node. */ function blogapi_mt_get_post_categories($postid, $username, $password) { $user = blogapi_validate_user($username, $password); if (!$user->uid) { return blogapi_error($user); } $node = node_load($postid); $terms = module_invoke('taxonomy', 'node_get_terms', $node, 'tid'); $categories = array(); foreach ($terms as $term) { $term_name = $term->name; foreach (module_invoke('taxonomy', 'get_parents', $term->tid, 'tid') as $parent) { $term_name = $parent->name .'/'. $term_name; } $categories[] = array('categoryName' => $term_name, 'categoryId' => $term->tid, 'isPrimary' => TRUE); } return $categories; } /** * Blogging API callback. Assigns taxonomy terms to a particular node. */ function blogapi_mt_set_post_categories($postid, $username, $password, $categories) { $user = blogapi_validate_user($username, $password); if (!$user->uid) { return blogapi_error($user); } $node = node_load($postid); $node->taxonomy = array(); foreach ($categories as $category) { $node->taxonomy[] = $category['categoryId']; } node_save($node); return TRUE; } /** * Blogging API callback. Sends a list of available input formats. */ function blogapi_mt_supported_text_filters() { // NOTE: we're only using anonymous' formats because the MT spec // does not allow for per-user formats. $formats = filter_formats(); $filters = array(); foreach ($formats as $format) { $filter['key'] = $format->format; $filter['label'] = $format->name; $filters[] = $filter; } return $filters; } /** * Blogging API callback. Publishes the given node */ function blogap_mti_publish_post($postid, $username, $password) { $user = blogapi_validate_user($username, $password); if (!$user->uid) { return blogapi_error($user); } $node = node_load($postid); if (!$node) { return blogapi_error(t('Invalid post.')); } $node->status = 1; if (!node_access('update', $node)) { return blogapi_error(t('You do not have permission to update this post.')); } node_save($node); return TRUE; } /** * Prepare an error message for returning to the XMLRPC caller. */ function blogapi_error($message) { static $xmlrpcusererr; if (!is_array($message)) { $message = array($message); } $message = implode(' ', $message); return xmlrpc_error($xmlrpcusererr + 1, strip_tags($message)); } /** * Ensure that the given user has permission to edit a blog. */ function blogapi_validate_user($username, $password) { global $user; $user = user_authenticate(array('name' => $username, 'pass' => $password)); if ($user->uid) { if (user_access('administer content with blog api', $user)) { return $user; } else { return t('You do not have permission to edit this blog.'); } } else { return t('Wrong username or password.'); } } /** * For the blogger API, extract the node title from the contents field. */ function blogapi_blogger_title(&$contents) { if (eregi('<title>([^<]*)</title>', $contents, $title)) { $title = strip_tags($title[0]); $contents = ereg_replace('<title>[^<]*</title>', '', $contents); } else { list($title, $contents) = explode("\n", $contents, 2); } return $title; } function blogapi_admin_settings() { $node_types = array_map('check_plain', node_get_types('names')); $defaults = isset($node_types['blog']) ? array('blog' => 1) : array(); $form['blogapi_node_types'] = array( '#type' => 'checkboxes', '#title' => t('Enable for external blogging clients'), '#required' => TRUE, '#default_value' => variable_get('blogapi_node_types', $defaults), '#options' => $node_types, '#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.') ); return system_settings_form($form); } function blogapi_menu() { $items['blogapi/rsd'] = array( 'title' => 'RSD', 'page callback' => 'blogapi_rsd', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); $items['admin/settings/blogapi'] = array( 'title' => 'Blog API', 'description' => 'Configure the content types available to external blogging clients.', 'page callback' => 'drupal_get_form', 'page arguments' => array('blogapi_admin_settings'), 'access arguments' => array('administer site configuration'), 'type' => MENU_NORMAL_ITEM, ); return $items; } function blogapi_init() { if (drupal_is_front_page()) { drupal_add_link(array('rel' => 'EditURI', 'type' => 'application/rsd+xml', 'title' => t('RSD'), 'href' => url('blogapi/rsd', array('absolute' => TRUE)))); } } function blogapi_rsd() { global $base_url; $xmlrpc = $base_url .'/xmlrpc.php'; $base = url('', array('absolute' => TRUE)); $blogid = 1; # until we figure out how to handle multiple bloggers drupal_set_header('Content-Type: application/rsd+xml; charset=utf-8'); print <<<__RSD__ <?xml version="1.0"?> <rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd"> <service> <engineName>Drupal</engineName> <engineLink>http://drupal.org/</engineLink> <homePageLink>$base</homePageLink> <apis> <api name="MetaWeblog" preferred="false" apiLink="$xmlrpc" blogID="$blogid" /> <api name="Blogger" preferred="false" apiLink="$xmlrpc" blogID="$blogid" /> <api name="MovableType" preferred="true" apiLink="$xmlrpc" blogID="$blogid" /> </apis> </service> </rsd> __RSD__; } /** * Handles extra information sent by clients according to MovableType's spec. */ function _blogapi_mt_extra(&$node, $struct) { if (is_array($node)) { $was_array = TRUE; $node = (object)$node; } // mt_allow_comments if (array_key_exists('mt_allow_comments', $struct)) { switch ($struct['mt_allow_comments']) { case 0: $node->comment = COMMENT_NODE_DISABLED; break; case 1: $node->comment = COMMENT_NODE_READ_WRITE; break; case 2: $node->comment = COMMENT_NODE_READ_ONLY; break; } } // merge the 3 body sections (description, mt_excerpt, mt_text_more) into // one body if ($struct['mt_excerpt']) { $node->body = $struct['mt_excerpt'] .'<!--break-->'. $node->body; } if ($struct['mt_text_more']) { $node->body = $node->body .'<!--extended-->'. $struct['mt_text_more']; } // mt_convert_breaks if ($struct['mt_convert_breaks']) { $node->format = $struct['mt_convert_breaks']; } // dateCreated if ($struct['dateCreated']) { $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'); } if ($was_array) { $node = (array)$node; } } function _blogapi_get_post($node, $bodies = TRUE) { $xmlrpcval = array( 'userid' => $node->name, 'dateCreated' => xmlrpc_date($node->created), 'title' => $node->title, 'postid' => $node->nid, 'link' => url('node/'. $node->nid, array('absolute' => TRUE)), 'permaLink' => url('node/'. $node->nid, array('absolute' => TRUE)), ); if ($bodies) { if ($node->comment == 1) { $comment = 2; } else if ($node->comment == 2) { $comment = 1; } $xmlrpcval['content'] = "<title>$node->title</title>$node->body"; $xmlrpcval['description'] = $node->body; // Add MT specific fields $xmlrpcval['mt_allow_comments'] = (int) $comment; $xmlrpcval['mt_convert_breaks'] = $node->format; } return $xmlrpcval; } /** * Validate blog ID, which maps to a content type in Drupal. * * Only content types configured to work with Blog API are supported. * * @return * TRUE if the content type is supported and the user has permission * to post, or a blogapi_error() XML construct otherwise. */ function _blogapi_validate_blogid($blogid) { $types = _blogapi_get_node_types(); if (in_array($blogid, $types, TRUE)) { return TRUE; } 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))); } function _blogapi_get_node_types() { $available_types = array_keys(array_filter(variable_get('blogapi_node_types', array('blog' => 1)))); $types = array(); foreach (node_get_types() as $type => $name) { if (node_access('create', $type) && in_array($type, $available_types)) { $types[] = $type; } } return $types; }