drupal/modules/blogapi.module

517 lines
17 KiB
Plaintext
Raw Normal View History

<?php
// $Id$
/**
* Implementation of hook_help().
*/
function blogapi_help($section) {
switch ($section) {
case 'admin/help#blogapi':
return t('This module adds support for several XML-RPC based blogging APIs. Specifically, it currently implements the %bloggerAPI, %metaweblogAPI, and most of the %moveabletype extensions. This allows users to contribute to drupal using external GUI applications, which can often offer richer functionality that online forms based editing', array('%bloggerAPI' => '<a href="http://www.blogger.com/developers/api/1_docs/">Blogger API</a>', '%metaweblogAPI' => '<a href="http://www.xmlrpc.com/metaWeblogApi">MetaWeblog API</a>', '%moveabletype' => '<a href="http://www.movabletype.org/docs/mtmanual_programmatic.html">Moveable Type API</a>'));
case 'admin/modules#description':
return t('Enable users to post using applications that support XML-RPC blog APIs');
}
}
/**
* Implementation of hook_xmlrpc().
*/
function blogapi_xmlrpc() {
$methods = array('blogger.getUsersBlogs' => array('function' => 'blogapi_get_users_blogs'),
'blogger.getUserInfo' => array('function' => 'blogapi_get_user_info'),
'blogger.newPost' => array('function' => 'blogapi_new_post'),
'blogger.editPost' => array('function' => 'blogapi_edit_post'),
'blogger.deletePost' => array('function' => 'blogapi_delete_post'),
'blogger.getRecentPosts' => array('function' => 'blogapi_get_recent_posts'),
'metaWeblog.newPost' => array('function' => 'blogapi_new_post'),
'metaWeblog.editPost' => array('function' => 'blogapi_edit_post'),
'metaWeblog.getPost' => array('function' => 'blogapi_get_post'),
'metaWeblog.newMediaObject' => array('function' => 'blogapi_new_media_object'),
'metaWeblog.getCategories' => array('function' => 'blogapi_get_category_list'),
'metaWeblog.getRecentPosts' => array('function' => 'blogapi_get_recent_posts'),
'mt.getRecentPostTitles' => array('function' => 'blogapi_get_recent_post_titles'),
'mt.getCategoryList' => array('function' => 'blogapi_get_category_list'),
'mt.getPostCategories' => array('function' => 'blogapi_get_post_categories'),
'mt.setPostCategories' => array('function' => 'blogapi_set_post_categories')
);
return $methods;
}
/**
* Blogging API callback. Finds the URL of a user's blog.
*/
function blogapi_get_users_blogs($req_params) {
$params = blogapi_convert($req_params);
2003-12-22 11:28:07 +00:00
// Remove unused appkey from bloggerAPI.
if (count($params) == 6) {
$params = array_slice($params, 1);
}
$user = blogapi_validate_user($params[1], $params[2]);
if ($user->uid) {
$struct = new xmlrpcval(array('url' => new xmlrpcval(url('blog/' . $user->uid, NULL, NULL, true)),
'blogid' => new xmlrpcval($user->uid),
'blogName' => new xmlrpcval($user->name . "'s blog")),
'struct');
$resp = new xmlrpcval(array($struct), 'array');
return new xmlrpcresp($resp);
}
else {
return blogapi_error($user);
}
}
/**
* Blogging API callback. Returns profile information about a user.
*/
function blogapi_get_user_info($req_params) {
$params = blogapi_convert($req_params);
$user = blogapi_validate_user($params[1], $params[2]);
if ($user->uid) {
$name = explode(' ', $user->realname ? $user->realname : $user->name, 2);
$struct = new xmlrpcval(array('userid' => new xmlrpcval($user->uid, 'string'),
'lastname' => new xmlrpcval($name[1], 'string'),
'firstname' => new xmlrpcval($name[0], 'string'),
'nickname' => new xmlrpcval($user->name, 'string'),
'email' => new xmlrpcval($user->mail, 'string'),
'url' => new xmlrpcval(url('blog/view/' . $user->uid, NULL, NULL, true), 'string')),
'struct');
return new xmlrpcresp($struct);
}
else {
return blogapi_error($user);
}
}
/**
* Blogging API callback. Inserts a new blog post as a node.
*/
function blogapi_new_post($req_params) {
$params = blogapi_convert($req_params);
// Remove unused appkey from bloggerAPI.
if (count($params) == 6) {
$params = array_slice($params, 1);
}
$user = blogapi_validate_user($params[1], $params[2]);
if (!$user->uid) {
return blogapi_error($user);
}
$promote = variable_get('node_promote_blog', 0);
$comment = variable_get('node_comment_blog', 2);
$moderate = variable_get('node_moderate_blog', 0);
$revision = variable_get('node_revision_blog', 0);
// check for bloggerAPI vs. metaWeblogAPI
if (is_array($params[3])) {
$title = $params[3]['title'];
$body = $params[3]['description'];
}
else {
$title = blogapi_blogger_title($params[3]);
$body = $params[3];
}
if (!valid_input_data($title, $body)) {
return blogapi_error(t('Terminated request because of suspicious input data.'));
}
$node = node_validate(array('type' => 'blog',
'uid' => $user->uid,
'name' => $user->name,
'title' => $title,
'body' => $body,
'status' => $params[4],
'promote' => $promote,
'comment' => $comment,
'moderate' => $moderate,
The Input formats - filter patch has landed. I still need to make update instructions for modules and update the hook docs. Here's an overview of the changes: 1) Multiple Input formats: they are complete filter configurations (what filters to use, in what order and with which settings). Input formats are admin-definable, and usage of them is role-dependant. For example, you can set it up so that regular users can only use limited HTML, while admins can free HTML without any tag limitations. The input format can be chosen per content item (nodes, comments, blocks, ...) when you add/edit them. If only a single format is available, there is no choice, and nothing changes with before. The default install (and the upgrade) contains a basic set of formats which should satisfy the average user's needs. 2) Filters have toggles Because now you might want to enable a filter only on some input formats, an explicit toggle is provided by the filter system. Modules do not need to worry about it and filters that still have their own on/off switch should get rid of it. 3) Multiple filters per module This was necessary to accomodate the next change, and it's also a logical extension of the filter system. 4) Embedded PHP is now a filter Thanks to the multiple input formats, I was able to move the 'embedded PHP' feature from block.module, page.module and book.module into a simple filter which executes PHP code. This filter is part of filter.module, and by default there is an input format 'PHP', restricted to the administrator only, which contains this filter. This change means that block.module now passes custom block contents through the filter system. As well as from reducing code duplication and avoiding two type selectors for page/book nodes, you can now combine PHP code with other filters. 5) User-supplied PHP code now requires <?php ?> tags. This is required for teasers to work with PHP code. Because PHP evaluation is now just another step in the filter process, we can't do this. Also, because teasers are generated before filtering, this would result in errors when the teaser generation would cut off a piece of PHP code. Also, regular PHP syntax explicitly includes the <?php ?> tags for PHP files, so it makes sense to use the same convention for embedded PHP in Drupal. 6) Filter caching was added. Benchmarking shows that even for a simple setup (basic html filtering + legacy URL rewriting), filtercache can offer speedups. Unlike the old filtercache, this uses the normal cache table. 7) Filtertips were moved from help into a hook_filter_tips(). This was required to accomodate the fact that there are multiple filters per module, and that filter settings are format dependant. Shoehorning filter tips into _help was ugly and silly. The display of the filter tips is done through the input format selector, so filter_tips_short() no longer exists. 8) A more intelligent linebreak convertor was added, which doesn't stop working if you use block-level tags and which adds <p> tags.
2004-08-10 18:34:29 +00:00
'format' => FILTER_DEFAULT_FORMAT,
'revision' => $revision
));
if (form_get_errors()) {
return blogapi_error();
}
if (!node_access('create', $node)) {
return blogapi_error(message_access());
}
$nid = node_save($node);
if ($nid) {
watchdog('special', t('%node-type: added "%node-title" using blog API', array('%node-type' => t("$node->type"), '%node-title' => $node->title)), l(t('view'), "node/$nid"));
return new xmlrpcresp(new xmlrpcval($nid, 'string'));
}
return blogapi_error(t('error storing post'));
}
/**
* Blogging API callback. Modifies the specified blog node.
*/
function blogapi_edit_post($req_params) {
$params = blogapi_convert($req_params);
if (count($params) == 6) {
$params = array_slice($params, 1);
}
$user = blogapi_validate_user($params[1], $params[2]);
if (!$user->uid) {
return blogapi_error($user);
}
$node = node_load(array('nid' => $params[0]));
if (!$node) {
return blogapi_error(message_na());
}
if (!node_access('update', $node)) {
return blogapi_error(message_access());
}
// check for bloggerAPI vs. metaWeblogAPI
if (is_array($params[3])) {
$title = $params[3]['title'];
$body = $params[3]['description'];
}
else {
$title = blogapi_blogger_title($params[3]);
$body = $params[3];
}
if (!valid_input_data($title, $body)) {
return blogapi_error(t('Terminated request because of suspicious input data.'));
}
$node->title = $title;
$node->body = $body;
$node->status = $params[4];
$node = node_validate($node);
if (form_get_errors()) {
return blogapi_error();
}
$terms = module_invoke('taxonomy', 'node_get_terms', $node->nid, 'tid');
foreach ($terms as $term) {
$node->taxonomy[] = $term->tid;
}
$nid = node_save($node);
if ($nid) {
watchdog('special', t('%node-type: updated "%node-title" using blog API', array('%node-type' => t("$node->type"), '%node-title' => $node->title)), l(t('view'), "node/$nid"));
return new xmlrpcresp(new xmlrpcval(true, 'boolean'));
}
return blogapi_error(t('error storing node'));
}
/**
* Blogging API callback. Returns a specified blog node.
*/
function blogapi_get_post($req_params) {
$params = blogapi_convert($req_params);
$user = blogapi_validate_user($params[1], $params[2]);
if (!$user->uid) {
return blogapi_error($user);
}
$node = node_load(array('nid' => $params[0]));
$blog = new xmlrpcval(array('userid' => new xmlrpcval($node->name, 'string'),
'dateCreated' => new xmlrpcval(iso8601_encode($node->created), 'dateTime.iso8601'),
'title' => new xmlrpcval($node->title, 'string'),
'description' => new xmlrpcval($node->body, 'string'),
'postid' => new xmlrpcval($node->nid, 'string')),
'struct');
return new xmlrpcresp($blog);
}
/**
* Blogging API callback. Removes the specified blog node.
*/
function blogapi_delete_post($req_params) {
$params = blogapi_convert($req_params);
$user = blogapi_validate_user($params[2], $params[3]);
if (!$user->uid) {
return blogapi_error($user);
}
$ret = node_delete(array('nid' => $params[1], 'confirm' => 1));
return new xmlrpcresp(new xmlrpcval(true, 'boolean'));
}
/**
* Blogging API callback. Inserts a file into Drupal.
*/
function blogapi_new_media_object($req_params) {
$params = blogapi_convert($req_params);
$user = blogapi_validate_user($params[1], $params[2]);
if (!$user->uid) {
return blogapi_error($user);
}
$name = basename($params[3]['name']);
$data = $params[3]['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.
$result = new xmlrpcval(array('url' => new xmlrpcval(file_create_url($file), 'string')), 'struct');
return new xmlrpcresp($result);
}
/**
* Blogging API callback. Returns a list of the taxonomy terms that can be
* associated with a blog node.
*/
function blogapi_get_category_list($req_params) {
$vocabularies = module_invoke('taxonomy', 'get_vocabularies', 'blog', '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[] = new xmlrpcval(array('categoryName' => new xmlrpcval($term_name, 'string'),
'categoryId' => new xmlrpcval($term->tid, 'string')),
'struct');
}
}
}
return new xmlrpcresp(new xmlrpcval($categories, 'array'));
}
/**
* Blogging API callback. Returns a list of the taxonomy terms that are
* assigned to a particular node.
*/
function blogapi_get_post_categories($req_params) {
$params = blogapi_convert($req_params);
$user = blogapi_validate_user($params[1], $params[2]);
if (!$user->uid) {
return blogapi_error($user);
}
$terms = module_invoke('taxonomy', 'node_get_terms', $params[0], '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[] = new xmlrpcval(array('categoryName' => new xmlrpcval($term_name, 'string'),
'categoryId' => new xmlrpcval($term->tid, 'string'),
'isPrimary' => new xmlrpcval(true, 'boolean')),
'struct');
}
return new xmlrpcresp(new xmlrpcval($categories, 'array'));
}
/**
* Blogging API callback. Assigns taxonomy terms to a particular node.
*/
function blogapi_set_post_categories($req_params) {
$params = blogapi_convert($req_params);
$user = blogapi_validate_user($params[1], $params[2]);
if (!$user->uid) {
return blogapi_error($user);
}
$nid = $params[0];
$terms = array();
foreach ($params[3] as $category) {
$terms[] = $category['categoryId']->scalarval();
}
module_invoke('taxonomy', 'node_save', $nid, $terms);
return new xmlrpcresp(new xmlrpcval(true, 'boolean'));
}
/**
* 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_get_recent_posts($req_params, $bodies = TRUE) {
$params = blogapi_convert($req_params);
// Remove unused appkey (from bloggerAPI).
if (count($params) == 5) {
$params = array_slice($params, 1);
}
$user = blogapi_validate_user($params[1], $params[2]);
if (!$user->uid) {
return blogapi_error($user);
}
$result = db_query_range('SELECT n.nid, n.title,'. ($bodies ? ' n.body,' : '') ." n.created, u.name FROM {node} n, {users} u WHERE n.uid=u.uid AND n.type = 'blog' AND n.uid = %d ORDER BY n.created DESC", $user->uid, 0, $params[3]);
while ($blog = db_fetch_object($result)) {
$xmlrpcval = array (
'userid' => new xmlrpcval($blog->name, 'string'),
'dateCreated' => new xmlrpcval(iso8601_encode($blog->created), 'dateTime.iso8601'),
'title' => new xmlrpcval($blog->title, 'string'),
'postid' => new xmlrpcval($blog->nid, 'string')
);
if ($bodies) {
$xmlrpcval['content'] = new xmlrpcval("<title>$blog->title</title>$blog->body", 'string');
$xmlrpcval['description'] = new xmlrpcval($blog->body, 'string');
}
$blogs[] = new xmlrpcval($xmlrpcval, 'struct');
}
return new xmlrpcresp(new xmlrpcval($blogs, 'array'));
}
// see above
function blogapi_get_recent_post_titles($req_params) {
return blogapi_get_recent_posts($req_params, TRUE);
}
/**
* Process the parameters to an XMLRPC callback, and return them as an array.
*/
function blogapi_convert($params) {
$cparams = array();
$num_params= $params->getNumParams();
for ($i = 0; $i < $num_params; $i++) {
$sn = $params->getParam($i);
$cparams[] = $sn->getval();
}
return $cparams;
}
/**
* Prepare an error message for returning to the XMLRPC caller.
*/
function blogapi_error($message) {
global $xmlrpcusererr;
if (!is_array($message)) {
$message = array($message);
}
if ($errors = form_get_errors()) {
$message = $message + $errors;
}
$message = implode(' ', $message);
return new xmlrpcresp(0, $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_load(array('name' => $username, 'pass' => $password, 'status' => 1));
if ($user->uid) {
if (user_access('edit own blog')) {
return $user;
}
else {
return message_access();
}
}
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, $rest) = explode("\n", $contents, 2);
}
return $title;
}
function blogapi_settings() {
$output = form_select(t('XML-RPC Engine'), 'blogapi_engine', variable_get('blogapi_engine', 0), array(0 => 'Blogger', 1 => 'MetaWeblog', 2 => 'Movabletype'), t('RSD or Really-Simple-Discovery is a mechanism which allows external blogger tools to discover the APIs they can use to interact with Drupal. The common XML-RPC engines are Blogger, MetaWeblog and Movabletype. If you are not sure which is the correct setting, choose Blogger.'));
return $output;
}
function blogapi_menu() {
global $user;
$items = array();
if ($_GET['q'] == variable_get('site_frontpage', 'node')) {
drupal_set_html_head('<link rel="EditURI" type="application/rsd+xml" title="RSD" href="' . url('blogapi/rsd', NULL, NULL, TRUE) . '" />');
}
$items[] = array('path' => 'blogapi', 'title' => t('RSD'), 'callback' => 'blogapi_blogapi', 'access' => user_access('access_content'), 'type' => MENU_CALLBACK);
return $items;
}
function blogapi_blogapi() {
switch (arg(1)) {
case 'rsd':
blogapi_rsd();
break;
default:
drupal_not_found();
break;
}
}
function blogapi_rsd() {
global $base_url;
$xmlrpc = $base_url . FILE_SEPARATOR . 'xmlrpc.php';
$base = url('', NULL, NULL, TRUE);
$blogid = 1; # until we figure out how to handle multiple bloggers
$metaweblog = 'false'; $blogger = 'false'; $mt = 'false';
if (variable_get('blogapi_engine', 0) == 0) {
$blogger = 'true';
} else if (variable_get('blogapi_engine', 0) == 1) {
$metaweblog = 'true';
} else if (variable_get('blogapi_engine', 0) == 2) {
$mt = 'true';
}
print <<<__RSD__
<?xml version="1.0"?>
<rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">
<service>
<engineName>Drupal</engineName>
<engineLink>http://www.drupal.org/</engineLink>
<homePageLink>$base</homePageLink>
<apis>
<api name="MetaWeblog" preferred="$metaweblog" apiLink="$xmlrpc" blogID="$blogid" />
<api name="Blogger" preferred="$blogger" apiLink="$xmlrpc" blogID="$blogid" />
<api name="Movabletype" preferred="$mt" apiLink="$xmlrpc" blogID="$blogid" />
</apis>
</service>
</rsd>
__RSD__;
}
?>