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