| [ Index ] |
PHP Cross Reference of Drupal 6 (gatewave) |
[Summary view] [Print] [Text view]
1 <?php 2 // $Id: blogapi.module,v 1.115.2.5 2008/10/08 20:12:17 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 'blogapi_mt_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 $valid = blogapi_status_error_check($edit, $publish); 226 if ($valid !== TRUE) { 227 return $valid; 228 } 229 230 node_validate($edit); 231 if ($errors = form_get_errors()) { 232 return blogapi_error(implode("\n", $errors)); 233 } 234 235 $node = node_submit($edit); 236 node_save($node); 237 if ($node->nid) { 238 watchdog('content', '@type: added %title using blog API.', array('@type' => $node->type, '%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), "node/$node->nid")); 239 // blogger.newPost returns a string so we cast the nid to a string by putting it in double quotes: 240 return "$node->nid"; 241 } 242 243 return blogapi_error(t('Error storing post.')); 244 } 245 246 /** 247 * Blogging API callback. Modifies the specified blog node. 248 */ 249 function blogapi_blogger_edit_post($appkey, $postid, $username, $password, $content, $publish) { 250 251 $user = blogapi_validate_user($username, $password); 252 253 if (!$user->uid) { 254 return blogapi_error($user); 255 } 256 257 $node = node_load($postid); 258 if (!$node) { 259 return blogapi_error(t('n/a')); 260 } 261 // Let the teaser be re-generated. 262 unset($node->teaser); 263 264 if (!node_access('update', $node)) { 265 return blogapi_error(t('You do not have permission to update this post.')); 266 } 267 // Save the original status for validation of permissions. 268 $original_status = $node->status; 269 $node->status = $publish; 270 271 // check for bloggerAPI vs. metaWeblogAPI 272 if (is_array($content)) { 273 $node->title = $content['title']; 274 $node->body = $content['description']; 275 _blogapi_mt_extra($node, $content); 276 } 277 else { 278 $node->title = blogapi_blogger_title($content); 279 $node->body = $content; 280 } 281 282 node_invoke_nodeapi($node, 'blogapi edit'); 283 284 $valid = blogapi_status_error_check($node, $original_status); 285 if ($valid !== TRUE) { 286 return $valid; 287 } 288 289 node_validate($node); 290 if ($errors = form_get_errors()) { 291 return blogapi_error(implode("\n", $errors)); 292 } 293 294 if (user_access('administer nodes') && !isset($edit['date'])) { 295 $node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O'); 296 } 297 $node = node_submit($node); 298 node_save($node); 299 if ($node->nid) { 300 watchdog('content', '@type: updated %title using Blog API.', array('@type' => $node->type, '%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), "node/$node->nid")); 301 return TRUE; 302 } 303 304 return blogapi_error(t('Error storing post.')); 305 } 306 307 /** 308 * Blogging API callback. Returns a specified blog node. 309 */ 310 function blogapi_blogger_get_post($appkey, $postid, $username, $password) { 311 $user = blogapi_validate_user($username, $password); 312 if (!$user->uid) { 313 return blogapi_error($user); 314 } 315 316 $node = node_load($postid); 317 318 return _blogapi_get_post($node, TRUE); 319 } 320 321 /** 322 * Check that the user has permission to save the node with the chosen status. 323 * 324 * @return 325 * TRUE if no error, or the blogapi_error(). 326 */ 327 function blogapi_status_error_check($node, $original_status) { 328 329 $node = (object) $node; 330 331 $node_type_default = variable_get('node_options_'. $node->type, array('status', 'promote')); 332 333 // If we don't have the 'administer nodes' permission and the status is 334 // changing or for a new node the status is not the content type's default, 335 // then return an error. 336 if (!user_access('administer nodes') && (($node->status != $original_status) || (empty($node->nid) && $node->status != in_array('status', $node_type_default)))) { 337 if ($node->status) { 338 return blogapi_error(t('You do not have permission to publish this type of post. Please save it as a draft instead.')); 339 } 340 else { 341 return blogapi_error(t('You do not have permission to save this post as a draft. Please publish it instead.')); 342 } 343 } 344 return TRUE; 345 } 346 347 348 /** 349 * Blogging API callback. Removes the specified blog node. 350 */ 351 function blogapi_blogger_delete_post($appkey, $postid, $username, $password, $publish) { 352 $user = blogapi_validate_user($username, $password); 353 if (!$user->uid) { 354 return blogapi_error($user); 355 } 356 357 node_delete($postid); 358 return TRUE; 359 } 360 361 /** 362 * Blogging API callback. Returns the latest few postings in a user's blog. $bodies TRUE 363 * <a href="http://movabletype.org/docs/mtmanual_programmatic.html#item_mt%2EgetRecentPostTitles"> 364 * returns a bandwidth-friendly list</a>. 365 */ 366 function blogapi_blogger_get_recent_posts($appkey, $blogid, $username, $password, $number_of_posts, $bodies = TRUE) { 367 // Remove unused appkey (from bloggerAPI). 368 $user = blogapi_validate_user($username, $password); 369 if (!$user->uid) { 370 return blogapi_error($user); 371 } 372 373 if (($error = _blogapi_validate_blogid($blogid)) !== TRUE) { 374 // Return an error if not configured type. 375 return $error; 376 } 377 378 if ($bodies) { 379 $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); 380 } 381 else { 382 $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); 383 } 384 $blogs = array(); 385 while ($blog = db_fetch_object($result)) { 386 $blogs[] = _blogapi_get_post($blog, $bodies); 387 } 388 return $blogs; 389 } 390 391 function blogapi_metaweblog_new_post($blogid, $username, $password, $content, $publish) { 392 return blogapi_blogger_new_post('0123456789ABCDEF', $blogid, $username, $password, $content, $publish); 393 } 394 395 function blogapi_metaweblog_edit_post($postid, $username, $password, $content, $publish) { 396 return blogapi_blogger_edit_post('0123456789ABCDEF', $postid, $username, $password, $content, $publish); 397 } 398 399 function blogapi_metaweblog_get_post($postid, $username, $password) { 400 return blogapi_blogger_get_post('01234567890ABCDEF', $postid, $username, $password); 401 } 402 403 /** 404 * Blogging API callback. Inserts a file into Drupal. 405 */ 406 function blogapi_metaweblog_new_media_object($blogid, $username, $password, $file) { 407 $user = blogapi_validate_user($username, $password); 408 if (!$user->uid) { 409 return blogapi_error($user); 410 } 411 412 $usersize = 0; 413 $uploadsize = 0; 414 415 $roles = array_intersect(user_roles(FALSE, 'administer content with blog api'), $user->roles); 416 417 foreach ($roles as $rid => $name) { 418 $extensions .= ' '. strtolower(variable_get("blogapi_extensions_$rid", variable_get('blogapi_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'))); 419 $usersize= max($usersize, variable_get("blogapi_usersize_$rid", variable_get('blogapi_usersize_default', 1)) * 1024 * 1024); 420 $uploadsize = max($uploadsize, variable_get("blogapi_uploadsize_$rid", variable_get('blogapi_uploadsize_default', 1)) * 1024 * 1024); 421 } 422 423 $filesize = strlen($file['bits']); 424 425 if ($filesize > $uploadsize) { 426 return blogapi_error(t('It is not possible to upload the file, because it exceeded the maximum filesize of @maxsize.', array('@maxsize' => format_size($uploadsize)))); 427 } 428 429 if (_blogapi_space_used($user->uid) + $filesize > $usersize) { 430 return blogapi_error(t('The file can not be attached to this post, because the disk quota of @quota has been reached.', array('@quota' => format_size($usersize)))); 431 } 432 433 // Only allow files with whitelisted extensions and convert remaining dots to 434 // underscores to prevent attacks via non-terminal executable extensions with 435 // files such as exploit.php.jpg. 436 437 $whitelist = array_unique(explode(' ', trim($extensions))); 438 439 $name = basename($file['name']); 440 441 if ($extension_position = strrpos($name, '.')) { 442 $filename = drupal_substr($name, 0, $extension_position); 443 $final_extension = drupal_substr($name, $extension_position + 1); 444 445 if (!in_array(strtolower($final_extension), $whitelist)) { 446 return blogapi_error(t('It is not possible to upload the file, because it is only possible to upload files with the following extensions: @extensions', array('@extensions' => implode(' ', $whitelist)))); 447 } 448 449 $filename = str_replace('.', '_', $filename); 450 $filename .= '.'. $final_extension; 451 } 452 453 $data = $file['bits']; 454 455 if (!$data) { 456 return blogapi_error(t('No file sent.')); 457 } 458 459 if (!$file = file_save_data($data, $filename)) { 460 return blogapi_error(t('Error storing file.')); 461 } 462 463 $row = new stdClass(); 464 $row->uid = $user->uid; 465 $row->filepath = $file; 466 $row->filesize = $filesize; 467 468 drupal_write_record('blogapi_files', $row); 469 470 // Return the successful result. 471 return array('url' => file_create_url($file), 'struct'); 472 } 473 /** 474 * Blogging API callback. Returns a list of the taxonomy terms that can be 475 * associated with a blog node. 476 */ 477 function blogapi_metaweblog_get_category_list($blogid, $username, $password) { 478 $user = blogapi_validate_user($username, $password); 479 if (!$user->uid) { 480 return blogapi_error($user); 481 } 482 483 if (($error = _blogapi_validate_blogid($blogid)) !== TRUE) { 484 // Return an error if not configured type. 485 return $error; 486 } 487 488 $vocabularies = module_invoke('taxonomy', 'get_vocabularies', $blogid, 'vid'); 489 $categories = array(); 490 if ($vocabularies) { 491 foreach ($vocabularies as $vocabulary) { 492 $terms = module_invoke('taxonomy', 'get_tree', $vocabulary->vid, 0, -1); 493 foreach ($terms as $term) { 494 $term_name = $term->name; 495 foreach (module_invoke('taxonomy', 'get_parents', $term->tid, 'tid') as $parent) { 496 $term_name = $parent->name .'/'. $term_name; 497 } 498 $categories[] = array('categoryName' => $term_name, 'categoryId' => $term->tid); 499 } 500 } 501 } 502 return $categories; 503 } 504 505 function blogapi_metaweblog_get_recent_posts($blogid, $username, $password, $number_of_posts) { 506 return blogapi_blogger_get_recent_posts('0123456789ABCDEF', $blogid, $username, $password, $number_of_posts, TRUE); 507 } 508 509 function blogapi_mt_get_recent_post_titles($blogid, $username, $password, $number_of_posts) { 510 return blogapi_blogger_get_recent_posts('0123456789ABCDEF', $blogid, $username, $password, $number_of_posts, FALSE); 511 } 512 513 function blogapi_mt_get_category_list($blogid, $username, $password) { 514 return blogapi_metaweblog_get_category_list($blogid, $username, $password); 515 } 516 517 /** 518 * Blogging API callback. Returns a list of the taxonomy terms that are 519 * assigned to a particular node. 520 */ 521 function blogapi_mt_get_post_categories($postid, $username, $password) { 522 $user = blogapi_validate_user($username, $password); 523 if (!$user->uid) { 524 return blogapi_error($user); 525 } 526 527 $node = node_load($postid); 528 $terms = module_invoke('taxonomy', 'node_get_terms', $node, 'tid'); 529 $categories = array(); 530 foreach ($terms as $term) { 531 $term_name = $term->name; 532 foreach (module_invoke('taxonomy', 'get_parents', $term->tid, 'tid') as $parent) { 533 $term_name = $parent->name .'/'. $term_name; 534 } 535 $categories[] = array('categoryName' => $term_name, 'categoryId' => $term->tid, 'isPrimary' => TRUE); 536 } 537 538 return $categories; 539 } 540 541 /** 542 * Blogging API callback. Assigns taxonomy terms to a particular node. 543 */ 544 function blogapi_mt_set_post_categories($postid, $username, $password, $categories) { 545 $user = blogapi_validate_user($username, $password); 546 if (!$user->uid) { 547 return blogapi_error($user); 548 } 549 550 $node = node_load($postid); 551 $node->taxonomy = array(); 552 foreach ($categories as $category) { 553 $node->taxonomy[] = $category['categoryId']; 554 } 555 $validated = blogapi_mt_validate_terms($node); 556 if ($validated !== TRUE) { 557 return $validated; 558 } 559 node_save($node); 560 return TRUE; 561 } 562 563 /** 564 * Blogging API helper - find allowed taxonomy terms for a node type. 565 */ 566 function blogapi_mt_validate_terms($node) { 567 // We do a lot of heavy lifting here since taxonomy module doesn't have a 568 // stand-alone validation function. 569 if (module_exists('taxonomy')) { 570 $found_terms = array(); 571 if (!empty($node->taxonomy)) { 572 $term_list = array_unique($node->taxonomy); 573 $params = $term_list; 574 $params[] = $node->type; 575 $result = db_query(db_rewrite_sql("SELECT t.tid, t.vid FROM {term_data} t INNER JOIN {vocabulary_node_types} n ON t.vid = n.vid WHERE t.tid IN (". db_placeholders($term_list) .") AND n.type = '%s'", 't', 'tid'), $params); 576 $found_terms = array(); 577 $found_count = 0; 578 while ($term = db_fetch_object($result)) { 579 $found_terms[$term->vid][$term->tid] = $term->tid; 580 $found_count++; 581 } 582 // If the counts don't match, some terms are invalid or not accessible to this user. 583 if (count($term_list) != $found_count) { 584 return blogapi_error(t('Invalid categories submitted.')); 585 } 586 } 587 // Look up all the vocabularies for this node type. 588 $result2 = db_query(db_rewrite_sql("SELECT v.vid, v.name, v.required, v.multiple FROM {vocabulary} v INNER JOIN {vocabulary_node_types} n ON v.vid = n.vid WHERE n.type = '%s'", 'v', 'vid'), $node->type); 589 // Check each vocabulary associated with this node type. 590 while ($vocabulary = db_fetch_object($result2)) { 591 // Required vocabularies must have at least one term. 592 if ($vocabulary->required && empty($found_terms[$vocabulary->vid])) { 593 return blogapi_error(t('A category from the @vocabulary_name vocabulary is required.', array('@vocabulary_name' => $vocabulary->name))); 594 } 595 // Vocabularies that don't allow multiple terms may have at most one. 596 if (!($vocabulary->multiple) && (isset($found_terms[$vocabulary->vid]) && count($found_terms[$vocabulary->vid]) > 1)) { 597 return blogapi_error(t('You may only choose one category from the @vocabulary_name vocabulary.'), array('@vocabulary_name' => $vocabulary->name)); 598 } 599 } 600 } 601 elseif (!empty($node->taxonomy)) { 602 return blogapi_error(t('Error saving categories. This feature is not available.')); 603 } 604 return TRUE; 605 } 606 607 /** 608 * Blogging API callback. Sends a list of available input formats. 609 */ 610 function blogapi_mt_supported_text_filters() { 611 // NOTE: we're only using anonymous' formats because the MT spec 612 // does not allow for per-user formats. 613 $formats = filter_formats(); 614 615 $filters = array(); 616 foreach ($formats as $format) { 617 $filter['key'] = $format->format; 618 $filter['label'] = $format->name; 619 $filters[] = $filter; 620 } 621 622 return $filters; 623 } 624 625 /** 626 * Blogging API callback. Publishes the given node 627 */ 628 function blogapi_mt_publish_post($postid, $username, $password) { 629 $user = blogapi_validate_user($username, $password); 630 if (!$user->uid) { 631 return blogapi_error($user); 632 } 633 $node = node_load($postid); 634 if (!$node) { 635 return blogapi_error(t('Invalid post.')); 636 } 637 638 // Nothing needs to be done if already published. 639 if ($node->status) { 640 return; 641 } 642 643 if (!node_access('update', $node) || !user_access('administer nodes')) { 644 return blogapi_error(t('You do not have permission to update this post.')); 645 } 646 647 $node->status = 1; 648 node_save($node); 649 650 return TRUE; 651 } 652 653 /** 654 * Prepare an error message for returning to the XMLRPC caller. 655 */ 656 function blogapi_error($message) { 657 static $xmlrpcusererr; 658 if (!is_array($message)) { 659 $message = array($message); 660 } 661 662 $message = implode(' ', $message); 663 664 return xmlrpc_error($xmlrpcusererr + 1, strip_tags($message)); 665 } 666 667 /** 668 * Ensure that the given user has permission to edit a blog. 669 */ 670 function blogapi_validate_user($username, $password) { 671 global $user; 672 673 $user = user_authenticate(array('name' => $username, 'pass' => $password)); 674 675 if ($user->uid) { 676 if (user_access('administer content with blog api', $user)) { 677 return $user; 678 } 679 else { 680 return t('You do not have permission to edit this blog.'); 681 } 682 } 683 else { 684 return t('Wrong username or password.'); 685 } 686 } 687 688 /** 689 * For the blogger API, extract the node title from the contents field. 690 */ 691 function blogapi_blogger_title(&$contents) { 692 if (eregi('<title>([^<]*)</title>', $contents, $title)) { 693 $title = strip_tags($title[0]); 694 $contents = ereg_replace('<title>[^<]*</title>', '', $contents); 695 } 696 else { 697 list($title, $contents) = explode("\n", $contents, 2); 698 } 699 return $title; 700 } 701 702 function blogapi_admin_settings() { 703 $node_types = array_map('check_plain', node_get_types('names')); 704 $defaults = isset($node_types['blog']) ? array('blog' => 1) : array(); 705 $form['blogapi_node_types'] = array( 706 '#type' => 'checkboxes', 707 '#title' => t('Enable for external blogging clients'), 708 '#required' => TRUE, 709 '#default_value' => variable_get('blogapi_node_types', $defaults), 710 '#options' => $node_types, 711 '#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.') 712 ); 713 714 $blogapi_extensions_default = variable_get('blogapi_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'); 715 $blogapi_uploadsize_default = variable_get('blogapi_uploadsize_default', 1); 716 $blogapi_usersize_default = variable_get('blogapi_usersize_default', 1); 717 718 $form['settings_general'] = array( 719 '#type' => 'fieldset', 720 '#title' => t('File settings'), 721 '#collapsible' => TRUE, 722 ); 723 724 $form['settings_general']['blogapi_extensions_default'] = array( 725 '#type' => 'textfield', 726 '#title' => t('Default permitted file extensions'), 727 '#default_value' => $blogapi_extensions_default, 728 '#maxlength' => 255, 729 '#description' => t('Default extensions that users can upload. Separate extensions with a space and do not include the leading dot.'), 730 ); 731 732 $form['settings_general']['blogapi_uploadsize_default'] = array( 733 '#type' => 'textfield', 734 '#title' => t('Default maximum file size per upload'), 735 '#default_value' => $blogapi_uploadsize_default, 736 '#size' => 5, 737 '#maxlength' => 5, 738 '#description' => t('The default maximum file size a user can upload.'), 739 '#field_suffix' => t('MB') 740 ); 741 742 $form['settings_general']['blogapi_usersize_default'] = array( 743 '#type' => 'textfield', 744 '#title' => t('Default total file size per user'), 745 '#default_value' => $blogapi_usersize_default, 746 '#size' => 5, 747 '#maxlength' => 5, 748 '#description' => t('The default maximum size of all files a user can have on the site.'), 749 '#field_suffix' => t('MB') 750 ); 751 752 $form['settings_general']['upload_max_size'] = array('#value' => '<p>'. t('Your PHP settings limit the maximum file size per upload to %size.', array('%size' => format_size(file_upload_max_size()))).'</p>'); 753 754 $roles = user_roles(0, 'administer content with blog api'); 755 $form['roles'] = array('#type' => 'value', '#value' => $roles); 756 757 foreach ($roles as $rid => $role) { 758 $form['settings_role_'. $rid] = array( 759 '#type' => 'fieldset', 760 '#title' => t('Settings for @role', array('@role' => $role)), 761 '#collapsible' => TRUE, 762 '#collapsed' => TRUE, 763 ); 764 $form['settings_role_'. $rid]['blogapi_extensions_'. $rid] = array( 765 '#type' => 'textfield', 766 '#title' => t('Permitted file extensions'), 767 '#default_value' => variable_get('blogapi_extensions_'. $rid, $blogapi_extensions_default), 768 '#maxlength' => 255, 769 '#description' => t('Extensions that users in this role can upload. Separate extensions with a space and do not include the leading dot.'), 770 ); 771 $form['settings_role_'. $rid]['blogapi_uploadsize_'. $rid] = array( 772 '#type' => 'textfield', 773 '#title' => t('Maximum file size per upload'), 774 '#default_value' => variable_get('blogapi_uploadsize_'. $rid, $blogapi_uploadsize_default), 775 '#size' => 5, 776 '#maxlength' => 5, 777 '#description' => t('The maximum size of a file a user can upload (in megabytes).'), 778 ); 779 $form['settings_role_'. $rid]['blogapi_usersize_'. $rid] = array( 780 '#type' => 'textfield', 781 '#title' => t('Total file size per user'), 782 '#default_value' => variable_get('blogapi_usersize_'. $rid, $blogapi_usersize_default), 783 '#size' => 5, 784 '#maxlength' => 5, 785 '#description' => t('The maximum size of all files a user can have on the site (in megabytes).'), 786 ); 787 } 788 789 return system_settings_form($form); 790 } 791 792 function blogapi_menu() { 793 $items['blogapi/rsd'] = array( 794 'title' => 'RSD', 795 'page callback' => 'blogapi_rsd', 796 'access arguments' => array('access content'), 797 'type' => MENU_CALLBACK, 798 ); 799 $items['admin/settings/blogapi'] = array( 800 'title' => 'Blog API', 801 'description' => 'Configure the content types available to external blogging clients.', 802 'page callback' => 'drupal_get_form', 803 'page arguments' => array('blogapi_admin_settings'), 804 'access arguments' => array('administer site configuration'), 805 'type' => MENU_NORMAL_ITEM, 806 ); 807 808 return $items; 809 } 810 811 function blogapi_init() { 812 if (drupal_is_front_page()) { 813 drupal_add_link(array('rel' => 'EditURI', 814 'type' => 'application/rsd+xml', 815 'title' => t('RSD'), 816 'href' => url('blogapi/rsd', array('absolute' => TRUE)))); 817 } 818 } 819 820 function blogapi_rsd() { 821 global $base_url; 822 823 $xmlrpc = $base_url .'/xmlrpc.php'; 824 $base = url('', array('absolute' => TRUE)); 825 $blogid = 1; # until we figure out how to handle multiple bloggers 826 827 drupal_set_header('Content-Type: application/rsd+xml; charset=utf-8'); 828 print <<<__RSD__ 829 <?xml version="1.0"?> 830 <rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd"> 831 <service> 832 <engineName>Drupal</engineName> 833 <engineLink>http://drupal.org/</engineLink> 834 <homePageLink>$base</homePageLink> 835 <apis> 836 <api name="MetaWeblog" preferred="false" apiLink="$xmlrpc" blogID="$blogid" /> 837 <api name="Blogger" preferred="false" apiLink="$xmlrpc" blogID="$blogid" /> 838 <api name="MovableType" preferred="true" apiLink="$xmlrpc" blogID="$blogid" /> 839 </apis> 840 </service> 841 </rsd> 842 __RSD__; 843 } 844 845 /** 846 * Handles extra information sent by clients according to MovableType's spec. 847 */ 848 function _blogapi_mt_extra(&$node, $struct) { 849 if (is_array($node)) { 850 $was_array = TRUE; 851 $node = (object)$node; 852 } 853 854 // mt_allow_comments 855 if (array_key_exists('mt_allow_comments', $struct)) { 856 switch ($struct['mt_allow_comments']) { 857 case 0: 858 $node->comment = COMMENT_NODE_DISABLED; 859 break; 860 case 1: 861 $node->comment = COMMENT_NODE_READ_WRITE; 862 break; 863 case 2: 864 $node->comment = COMMENT_NODE_READ_ONLY; 865 break; 866 } 867 } 868 869 // merge the 3 body sections (description, mt_excerpt, mt_text_more) into 870 // one body 871 if ($struct['mt_excerpt']) { 872 $node->body = $struct['mt_excerpt'] .'<!--break-->'. $node->body; 873 } 874 if ($struct['mt_text_more']) { 875 $node->body = $node->body .'<!--extended-->'. $struct['mt_text_more']; 876 } 877 878 // mt_convert_breaks 879 if ($struct['mt_convert_breaks']) { 880 $node->format = $struct['mt_convert_breaks']; 881 } 882 883 // dateCreated 884 if ($struct['dateCreated']) { 885 $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'); 886 } 887 888 if ($was_array) { 889 $node = (array)$node; 890 } 891 } 892 893 function _blogapi_get_post($node, $bodies = TRUE) { 894 $xmlrpcval = array( 895 'userid' => $node->name, 896 'dateCreated' => xmlrpc_date($node->created), 897 'title' => $node->title, 898 'postid' => $node->nid, 899 'link' => url('node/'. $node->nid, array('absolute' => TRUE)), 900 'permaLink' => url('node/'. $node->nid, array('absolute' => TRUE)), 901 ); 902 if ($bodies) { 903 if ($node->comment == 1) { 904 $comment = 2; 905 } 906 else if ($node->comment == 2) { 907 $comment = 1; 908 } 909 $xmlrpcval['content'] = "<title>$node->title</title>$node->body"; 910 $xmlrpcval['description'] = $node->body; 911 // Add MT specific fields 912 $xmlrpcval['mt_allow_comments'] = (int) $comment; 913 $xmlrpcval['mt_convert_breaks'] = $node->format; 914 } 915 916 return $xmlrpcval; 917 } 918 919 /** 920 * Validate blog ID, which maps to a content type in Drupal. 921 * 922 * Only content types configured to work with Blog API are supported. 923 * 924 * @return 925 * TRUE if the content type is supported and the user has permission 926 * to post, or a blogapi_error() XML construct otherwise. 927 */ 928 function _blogapi_validate_blogid($blogid) { 929 $types = _blogapi_get_node_types(); 930 if (in_array($blogid, $types, TRUE)) { 931 return TRUE; 932 } 933 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))); 934 } 935 936 function _blogapi_get_node_types() { 937 $available_types = array_keys(array_filter(variable_get('blogapi_node_types', array('blog' => 1)))); 938 $types = array(); 939 foreach (node_get_types() as $type => $name) { 940 if (node_access('create', $type) && in_array($type, $available_types)) { 941 $types[] = $type; 942 } 943 } 944 945 return $types; 946 } 947 948 function _blogapi_space_used($uid) { 949 return db_result(db_query('SELECT SUM(filesize) FROM {blogapi_files} f WHERE f.uid = %d', $uid)); 950 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Mar 24 11:18:33 2011 | Cross-referenced by PHPXref 0.7 |