blogapi.module 14.6 KB
Newer Older
Dries's avatar
 
Dries committed
1
2
3
<?php
// $Id$

4
5
6
/**
 * Implementation of hook_help().
 */
Dries's avatar
 
Dries committed
7
8
function blogapi_help($section) {
  switch ($section) {
9
10
    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>'));
Dries's avatar
 
Dries committed
11
    case 'admin/system/modules#description':
12
      return t('Enable users to post using applications that support XML-RPC blog APIs');
Dries's avatar
 
Dries committed
13
14
15
  }
}

16
17
18
/**
 * Implementation of hook_xmlrpc().
 */
Dries's avatar
 
Dries committed
19
20
function blogapi_xmlrpc() {
  $methods = array('blogger.getUsersBlogs' => array('function' => 'blogapi_get_users_blogs'),
21
22
23
                   'blogger.getUserInfo' => array('function' => 'blogapi_get_user_info'),
                   'blogger.newPost' => array('function' => 'blogapi_new_post'),
                   'blogger.editPost' => array('function' => 'blogapi_edit_post'),
Dries's avatar
 
Dries committed
24
                   'blogger.deletePost' => array('function' => 'blogapi_delete_post'),
Dries's avatar
   
Dries committed
25
                   'blogger.getRecentPosts' => array('function' => 'blogapi_get_recent_posts'),
Dries's avatar
 
Dries committed
26
27
28
29
                   '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'),
30
                   'metaWeblog.getCategories' => array('function' => 'blogapi_get_category_list'),
Dries's avatar
 
Dries committed
31
32
33
34
35
                   'metaWeblog.getRecentPosts' => array('function' => 'blogapi_get_recent_posts'),
                   'mt.getCategoryList' => array('function' => 'blogapi_get_category_list'),
                   'mt.getPostCategories' => array('function' => 'blogapi_get_post_categories'),
                   'mt.setPostCategories' => array('function' => 'blogapi_set_post_categories')
                   );
Dries's avatar
   
Dries committed
36

Dries's avatar
 
Dries committed
37
38
39
  return $methods;
}

40
41
42
/**
 * Blogging API callback. Finds the URL of a user's blog.
 */
Dries's avatar
 
Dries committed
43
44
function blogapi_get_users_blogs($req_params) {
  $params = blogapi_convert($req_params);
Dries's avatar
   
Dries committed
45
46
47
48
49
  // Remove unused appkey from bloggerAPI.
  if (count($params) == 6) {
    $params = array_slice($params, 1);
  }

Dries's avatar
 
Dries committed
50
51
  $user = blogapi_validate_user($params[1], $params[2]);
  if ($user->uid) {
52
    $struct = new xmlrpcval(array('url' => new xmlrpcval(url('blog/' . $user->uid, NULL, NULL, true)),
Dries's avatar
 
Dries committed
53
54
55
                                  'blogid' => new xmlrpcval($user->uid),
                                  'blogName' => new xmlrpcval($user->name . "'s blog")),
                            'struct');
56
    $resp = new xmlrpcval(array($struct), 'array');
Dries's avatar
 
Dries committed
57
58
59
    return new xmlrpcresp($resp);
  }
  else {
60
    return blogapi_error($user);
Dries's avatar
 
Dries committed
61
62
63
  }
}

64
65
66
/**
 * Blogging API callback. Returns profile information about a user.
 */
Dries's avatar
 
Dries committed
67
68
function blogapi_get_user_info($req_params) {
  $params = blogapi_convert($req_params);
69

Dries's avatar
 
Dries committed
70
  $user = blogapi_validate_user($params[1], $params[2]);
71

Dries's avatar
 
Dries committed
72
  if ($user->uid) {
73
    $name = explode(' ', $user->realname ? $user->realname : $user->name, 2);
Dries's avatar
 
Dries committed
74
    $struct = new xmlrpcval(array('userid' => new xmlrpcval($user->uid, 'string'),
75
76
                                  'lastname' => new xmlrpcval($name[1], 'string'),
                                  'firstname' => new xmlrpcval($name[0], 'string'),
Dries's avatar
 
Dries committed
77
78
                                  'nickname' => new xmlrpcval($user->name, 'string'),
                                  'email' => new xmlrpcval($user->mail, 'string'),
79
                                  'url' => new xmlrpcval(url('blog/view/' . $user->uid, NULL, NULL, true), 'string')),
Dries's avatar
 
Dries committed
80
81
82
83
                            'struct');
    return new xmlrpcresp($struct);
  }
  else {
84
    return blogapi_error($user);
Dries's avatar
 
Dries committed
85
86
87
  }
}

88
89
90
/**
 * Blogging API callback. Inserts a new blog post as a node.
 */
Dries's avatar
 
Dries committed
91
92
function blogapi_new_post($req_params) {
  $params = blogapi_convert($req_params);
93
94
95
96
97
98

  // Remove unused appkey from bloggerAPI.
  if (count($params) == 6) {
    $params = array_slice($params, 1);
  }

Dries's avatar
 
Dries committed
99
100
  $user = blogapi_validate_user($params[1], $params[2]);
  if (!$user->uid) {
101
    return blogapi_error($user);
Dries's avatar
 
Dries committed
102
103
  }

104
105
106
107
  $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);
Dries's avatar
 
Dries committed
108
109
110
111
112
113
114
115
116
117

  // 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];
  }
Dries's avatar
   
Dries committed
118

119
  if (!valid_input_data($title, $body)) {
120
    return blogapi_error(t('Terminated request because of suspicious input data.'));
121
122
  }

Dries's avatar
 
Dries committed
123
124
125
126
127
128
129
130
131
132
133
  $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,
                              'revision' => $revision
                              ), $error);
Dries's avatar
   
Dries committed
134

Dries's avatar
 
Dries committed
135
136
137
138
  if (count($error) > 0) {
    return blogapi_error($error);
  }

139
  if (!node_access('create', $node)) {
Dries's avatar
 
Dries committed
140
141
142
143
144
    return blogapi_error(message_access());
  }

  $nid = node_save($node);
  if ($nid) {
145
    watchdog('special', t('%node-type: added "%node-title" using blog API', array('%node-type' => t("$node->type"), '%node-title' => $node->title)), l(t('view post'), "node/view/$nid"));
Dries's avatar
 
Dries committed
146
147
148
149
150
151
    return new xmlrpcresp(new xmlrpcval($nid, 'string'));
  }

  return blogapi_error(t('error storing post'));
}

152
153
154
/**
 * Blogging API callback. Modifies the specified blog node.
 */
Dries's avatar
 
Dries committed
155
156
function blogapi_edit_post($req_params) {
  $params = blogapi_convert($req_params);
157
158
159
160
  if (count($params) == 6) {
    $params = array_slice($params, 1);
  }

Dries's avatar
 
Dries committed
161
  $user = blogapi_validate_user($params[1], $params[2]);
162

Dries's avatar
 
Dries committed
163
  if (!$user->uid) {
164
    return blogapi_error($user);
Dries's avatar
 
Dries committed
165
166
167
168
169
170
171
  }

  $node = node_load(array('nid' => $params[0]));
  if (!$node) {
    return blogapi_error(message_na());
  }

172
  if (!node_access('update', $node)) {
Dries's avatar
 
Dries committed
173
174
175
176
177
178
179
180
181
182
183
184
    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];
  }
Dries's avatar
   
Dries committed
185

186
  if (!valid_input_data($title, $body)) {
187
    return blogapi_error(t('Terminated request because of suspicious input data.'));
188
189
  }

Dries's avatar
 
Dries committed
190
191
192
193
  $node->title = $title;
  $node->body = $body;
  $node->status = $params[4];
  $node = node_validate($node, $error);
Dries's avatar
   
Dries committed
194

Dries's avatar
 
Dries committed
195
196
197
198
  if (count($error) > 0) {
    return blogapi_error($error);
  }

199
200
201
202
  $terms = module_invoke('taxonomy', 'node_get_terms', $node->nid, 'tid');
  foreach ($terms as $term) {
    $node->taxonomy[] = $term->tid;
  }
Dries's avatar
 
Dries committed
203
204
  $nid = node_save($node);
  if ($nid) {
205
    watchdog('special', t('%node-type: updated "%node-title" using blog API', array('%node-type' => t("$node->type"), '%node-title' => $node->title)), l(t('view post'), "node/view/$nid"));
206
    return new xmlrpcresp(new xmlrpcval(true, 'boolean'));
Dries's avatar
 
Dries committed
207
208
209
210
211
  }

  return blogapi_error(t('error storing node'));
}

212
213
214
/**
 * Blogging API callback. Returns a specified blog node.
 */
Dries's avatar
 
Dries committed
215
216
217
218
function blogapi_get_post($req_params) {
  $params = blogapi_convert($req_params);
  $user = blogapi_validate_user($params[1], $params[2]);
  if (!$user->uid) {
219
    return blogapi_error($user);
Dries's avatar
 
Dries committed
220
221
  }

Dries's avatar
   
Dries committed
222
  $node = node_load(array('nid' => $params[0]));
Dries's avatar
 
Dries committed
223
  $blog = new xmlrpcval(array('userid' => new xmlrpcval($node->name, 'string'),
224
                              'dateCreated' => new xmlrpcval(iso8601_encode($node->created), 'dateTime.iso8601'),
Dries's avatar
 
Dries committed
225
226
227
228
229
230
231
232
                              'title' => new xmlrpcval($node->title, 'string'),
                              'description' => new xmlrpcval($node->body, 'string'),
                              'postid' => new xmlrpcval($node->nid, 'string')),
                        'struct');

  return new xmlrpcresp($blog);
}

233
234
235
/**
 * Blogging API callback. Removes the specified blog node.
 */
Dries's avatar
 
Dries committed
236
237
function blogapi_delete_post($req_params) {
  $params = blogapi_convert($req_params);
Dries's avatar
   
Dries committed
238

Dries's avatar
 
Dries committed
239
240
  $user = blogapi_validate_user($params[2], $params[3]);
  if (!$user->uid) {
241
    return blogapi_error($user);
Dries's avatar
 
Dries committed
242
243
244
  }

  $ret = node_delete(array('nid' => $params[1], 'confirm' => 1));
245
  return new xmlrpcresp(new xmlrpcval(true, 'boolean'));
Dries's avatar
 
Dries committed
246
247
}

248
249
250
/**
 * Blogging API callback. Inserts a file into Drupal.
 */
Dries's avatar
 
Dries committed
251
function blogapi_new_media_object($req_params) {
252
253
254
255
256
257
258
259
260
261
262
263
264
  $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'));
  }
265

266
267
268
  if (!$file = file_save_data($data, $name)) {
    return blogapi_error(t('Error storing file'));
  }
269

270
271
272
  // Return the successful result.
  $result = new xmlrpcval(array('url' => new xmlrpcval(file_create_url($file), 'string')), 'struct');
  return new xmlrpcresp($result);
Dries's avatar
 
Dries committed
273
274
}

275
276
277
278
/**
 * Blogging API callback. Returns a list of the taxonomy terms that can be
 * associated with a blog node.
 */
Dries's avatar
 
Dries committed
279
function blogapi_get_category_list($req_params) {
280
  $vocabularies = module_invoke('taxonomy', 'get_vocabularies', 'blog', 'vid');
281
  $categories = array();
282
283
  if ($vocabularies) {
    foreach ($vocabularies as $vocabulary) {
284
      $terms = module_invoke('taxonomy', 'get_tree', $vocabulary->vid, 0, -1);
285
286
287
288
289
290
291
292
      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');
Dries's avatar
 
Dries committed
293
294
295
      }
    }
  }
296
  return new xmlrpcresp(new xmlrpcval($categories, 'array'));
Dries's avatar
 
Dries committed
297
298
}

299
300
301
302
/**
 * Blogging API callback. Returns a list of the taxonomy terms that are
 * assigned to a particular node.
 */
Dries's avatar
 
Dries committed
303
304
305
306
function blogapi_get_post_categories($req_params) {
  $params = blogapi_convert($req_params);
  $user = blogapi_validate_user($params[1], $params[2]);
  if (!$user->uid) {
307
    return blogapi_error($user);
Dries's avatar
 
Dries committed
308
309
  }

310
  $terms = module_invoke('taxonomy', 'node_get_terms', $params[0], 'tid');
Dries's avatar
 
Dries committed
311
  $categories = array();
312
  foreach ($terms as $term) {
Dries's avatar
 
Dries committed
313
    $term_name = $term->name;
314
    foreach (module_invoke('taxonomy', 'get_parents', $term->tid, 'tid') as $parent) {
Dries's avatar
 
Dries committed
315
316
317
318
319
320
321
      $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');
  }
322
  return new xmlrpcresp(new xmlrpcval($categories, 'array'));
Dries's avatar
 
Dries committed
323
324
}

325
326
327
/**
 * Blogging API callback. Assigns taxonomy terms to a particular node.
 */
Dries's avatar
 
Dries committed
328
329
330
331
function blogapi_set_post_categories($req_params) {
  $params = blogapi_convert($req_params);
  $user = blogapi_validate_user($params[1], $params[2]);
  if (!$user->uid) {
332
    return blogapi_error($user);
Dries's avatar
 
Dries committed
333
334
335
336
337
338
339
  }

  $nid = $params[0];
  $terms = array();
  foreach ($params[3] as $category) {
    $terms[] = $category['categoryId']->scalarval();
  }
340
  module_invoke('taxonomy', 'node_save', $nid, $terms);
Dries's avatar
 
Dries committed
341
342
343
  return new xmlrpcresp(new xmlrpcval(true, 'boolean'));
}

344
345
346
/**
 * Blogging API callback. Returns the latest few postings in a user's blog.
 */
Dries's avatar
 
Dries committed
347
348
function blogapi_get_recent_posts($req_params) {
  $params = blogapi_convert($req_params);
349
350
351
352
353

  // Remove unused appkey (from bloggerAPI).
  if (count($params) == 5) {
    $params = array_slice($params, 1);
  }
Dries's avatar
 
Dries committed
354
355
  $user = blogapi_validate_user($params[1], $params[2]);
  if (!$user->uid) {
356
    return blogapi_error($user);
Dries's avatar
 
Dries committed
357
358
  }

359
360
  $result = db_query_range("SELECT n.nid, n.title, 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)) {
Dries's avatar
 
Dries committed
361
    $blogs[] = new xmlrpcval(array('userid' => new xmlrpcval($blog->name, 'string'),
362
                                   'dateCreated' => new xmlrpcval(iso8601_encode($blog->created), 'dateTime.iso8601'),
363
                                   'content' => new xmlrpcval("<title>$blog->title</title>$blog->body", 'string'),
Dries's avatar
 
Dries committed
364
365
366
367
368
                                   'title' => new xmlrpcval($blog->title, 'string'),
                                   'description' => new xmlrpcval($blog->body, 'string'),
                                   'postid' => new xmlrpcval($blog->nid, 'string')),
                             'struct');
  }
369
  return new xmlrpcresp(new xmlrpcval($blogs, 'array'));
Dries's avatar
 
Dries committed
370
371
}

372
373
374
/**
 * Process the parameters to an XMLRPC callback, and return them as an array.
 */
Dries's avatar
 
Dries committed
375
376
377
378
379
380
381
382
383
384
385
386
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;
}

387
388
389
/**
 * Prepare an error message for returning to the XMLRPC caller.
 */
Dries's avatar
 
Dries committed
390
391
function blogapi_error($message) {
  global $xmlrpcusererr;
Dries's avatar
   
Dries committed
392

393
394
395
396
397
  if (is_array($message)) {
    $message = implode('', $message);
  }

  return new xmlrpcresp(0, $xmlrpcusererr + 1, strip_tags($message));
Dries's avatar
 
Dries committed
398
399
}

400
401
402
/**
 * Ensure that the given user has permission to edit a blog.
 */
Dries's avatar
 
Dries committed
403
404
function blogapi_validate_user($username, $password) {
  global $user;
Dries's avatar
   
Dries committed
405

Dries's avatar
 
Dries committed
406
407
  $user = user_load(array('name' => $username, 'pass' => $password, 'status' => 1));

408
409
410
411
412
413
414
415
416
417
  if ($user->uid) {
    if (user_access('maintain personal blog')) {
      return $user;
    }
    else {
      return message_access();
    }
  }
  else {
    return t('Wrong username or password.');
Dries's avatar
 
Dries committed
418
419
420
  }
}

421
422
423
/**
 * For the blogger API, extract the node title from the contents field.
 */
Dries's avatar
 
Dries committed
424
function blogapi_blogger_title(&$contents) {
425
  if (eregi('<title>([^<]*)</title>', $contents, $title)) {
426
    $title = strip_tags($title[0]);
427
    $contents = ereg_replace('<title>[^<]*</title>', '', $contents);
Dries's avatar
 
Dries committed
428
429
430
431
432
433
434
  }
  else {
    list($title, $rest) = explode("\n", $contents, 2);
  }
  return $title;
}
?>