Textpattern | PHP Cross Reference | Content Management Systems |
1 <?php 2 3 /* 4 * Textpattern Content Management System 5 * https://textpattern.com/ 6 * 7 * XML-RPC Server for Textpattern 4.0.x 8 * https://web.archive.org/web/20150119065246/http://txp.kusor.com/rpc-api 9 * 10 * Copyright (C) 2020 The Textpattern Development Team 11 * Author: Pedro Palazón 12 * 13 * This file is part of Textpattern. 14 * 15 * Textpattern is free software; you can redistribute it and/or 16 * modify it under the terms of the GNU General Public License 17 * as published by the Free Software Foundation, version 2. 18 * 19 * Textpattern is distributed in the hope that it will be useful, 20 * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 * GNU General Public License for more details. 23 * 24 * You should have received a copy of the GNU General Public License 25 * along with Textpattern. If not, see <https://www.gnu.org/licenses/>. 26 */ 27 28 if (!defined('txpath')) { 29 die('txpath is undefined.'); 30 } 31 32 require_once txpath.'/lib/txplib_html.php'; 33 34 class TXP_RPCServer extends IXR_IntrospectionServer 35 { 36 function __construct() 37 { 38 global $enable_xmlrpc_server, $HTTP_RAW_POST_DATA; 39 40 if (!$HTTP_RAW_POST_DATA) { 41 $HTTP_RAW_POST_DATA = file_get_contents('php://input'); 42 } 43 44 parent::__construct(); 45 46 // Add API Methods as callbacks. 47 if ($enable_xmlrpc_server) { 48 // Blogger API [https://developers.google.com/blogger/docs/2.0/reference] - add as 49 // server capability. 50 $this->capabilities['bloggerAPI'] = array( 51 'specUrl' => 'https://developers.google.com/blogger/docs/2.0/reference', 52 'specVersion' => 2, 53 ); 54 $this->addCallback( 55 'blogger.newPost', 56 'this:blogger_newPost', 57 array('int', 'string', 'string', 'string', 'string', 'string', 'boolean'), 58 'makes a new post to a designated blog' 59 ); 60 $this->addCallback( 61 'blogger.editPost', 62 'this:blogger_editPost', 63 array('boolean', 'string', 'string', 'string', 'string', 'string', 'boolean'), 64 'changes the contents of a given post' 65 ); 66 $this->addCallback( 67 'blogger.getUsersBlogs', 68 'this:blogger_getUsersBlogs', 69 array('struct', 'string', 'string', 'string'), 70 'return information about all the blogs a given user is member of' 71 ); 72 $this->addCallback( 73 'blogger.getUserInfo', 74 'this:blogger_getUserInfo', 75 array('struct', 'string', 'string', 'string'), 76 'return information about the current user' 77 ); 78 $this->addCallback( 79 'blogger.getTemplate', 80 'this:blogger_getTemplate', 81 array('string', 'string', 'string', 'string', 'string', 'string'), 82 'return section template - main will return default template, archiveIndex will return section template' 83 ); 84 $this->addCallback( 85 'blogger.setTemplate', 86 'this:blogger_setTemplate', 87 array('boolean', 'string', 'string', 'string', 'string', 'string', 'string'), 88 'updates section template - main=default template, archiveIndex=section template' 89 ); 90 91 // Non-official Blogger API methods - supported by XML-RPC clients 92 // as BloggerAPI2. Place all this info on a public URI. 93 $this->addCallback( 94 'blogger.getPost', 95 'this:blogger_getPost', 96 array('struct', 'string', 'string', 'string', 'string'), 97 'retrieves contents for the given postid' 98 ); 99 $this->addCallback( 100 'blogger.deletePost', 101 'this:blogger_deletePost', 102 array('boolean', 'string', 'string', 'string', 'string', 'boolean'), 103 'deletes a given post' 104 ); 105 $this->addCallback( 106 'blogger.getRecentPosts', 107 'this:blogger_getRecentPosts', 108 array('array', 'string', 'string', 'string', 'string', 'int'), 109 'retrieves a list of posts (default 10)' 110 ); 111 112 // metaWeblog API[http://www.xmlrpc.com/metaWeblogApi] - add as 113 // server capability. 114 $this->capabilities['metaWeblog API'] = array( 115 'specUrl' => 'http://www.xmlrpc.com/metaWeblogApi', 116 'specVersion' => 1, 117 ); 118 // Implements also MovableType extension of the API methods. 119 $this->addCallback( 120 'metaWeblog.getPost', 121 'this:metaWeblog_getPost', 122 array('struct', 'string', 'string', 'string'), 123 'retrieves contents for the given postid' 124 ); 125 $this->addCallback( 126 'metaWeblog.newPost', 127 'this:metaWeblog_newPost', 128 array('string', 'string', 'string', 'string', 'struct', 'boolean'), 129 'creates a new post' 130 ); 131 $this->addCallback( 132 'metaWeblog.editPost', 133 'this:metaWeblog_editPost', 134 array('boolean', 'string', 'string', 'string', 'struct', 'boolean'), 135 'creates a new post' 136 ); 137 $this->addCallback( 138 'metaWeblog.getCategories', 139 'this:metaWeblog_getCategories', 140 array('struct', 'string', 'string', 'string'), 141 'retrieves a list of categories for the current blog' 142 ); 143 $this->addCallback( 144 'metaWeblog.getRecentPosts', 145 'this:metaWeblog_getRecentPosts', 146 array('array', 'string', 'string', 'string', 'int'), 147 'retrieves a given number of recent posts' 148 ); 149 150 // TODO: metaWeblog.newMediaObject (blogid, username, password, struct) returns struct. See https://github.com/textpattern/textpattern/issues/1050 151 152 // MovableType API[] - add as server capability. 153 $this->capabilities['MovableType API'] = array( 154 'specUrl' => 'http://www.sixapart.com/movabletype/docs/mtmanual_programmatic.html#xmlrpc%20api', 155 'specVersion' => 1, 156 ); 157 // Not completely implemented. 158 $this->addCallback( 159 'mt.getRecentPostTitles', 160 'this:mt_getRecentPostTitles', 161 array('array', 'string', 'string', 'string', 'id'), 162 'returns a bandwidth-friendly list of the most recent posts in the system' 163 ); 164 $this->addCallback( 165 'mt.getCategoryList', 166 'this:mt_getCategoryList', 167 array('array', 'string', 'string', 'string'), 168 'returns a list of all categories defined in the weblog' 169 ); 170 $this->addCallback( 171 'mt.supportedMethods', 172 'this:listMethods', 173 array('array'), 174 'return the XML-RPC Methods supported by the server(Redundant).' 175 ); 176 $this->addCallback( 177 'mt.supportedTextFilters', 178 'this:mt_supportedTextFilters', 179 array('array'), 180 'return the format filters suported by the server.' 181 ); 182 $this->addCallback( 183 'mt.getPostCategories', 184 'this:mt_getPostCategories', 185 array('array', 'string', 'string', 'string'), 186 'returns a list of all categories for the given article' 187 ); 188 $this->addCallback( 189 'mt.setPostCategories', 190 'this:mt_setPostCategories', 191 array('boolean', 'string', 'string', 'string', 'array'), 192 'sets categories for the given article' 193 ); 194 $this->addCallback( 195 'mt.publishPost', 196 'this:mt_publishPost', 197 array('boolean', 'string', 'string', 'string'), 198 'changes the status of the current article to published' 199 ); 200 } 201 } 202 203 // Override serve method in order to keep requests logs too while dealing 204 // with unknown clients. 205 function serve($data = false) 206 { 207 if (!$data) { 208 global $HTTP_RAW_POST_DATA; 209 210 if (!$HTTP_RAW_POST_DATA) { 211 // RSD lets them find us via GET requests. 212 $this->rsd(); 213 exit; 214 } 215 216 $rx = '/<?xml.*encoding=[\'"](.*?)[\'"].*?>/m'; 217 218 // First, handle the known UAs in order to serve proper content. 219 if (strpos('w.bloggar', $_SERVER['HTTP_USER_AGENT']) !== false) { 220 $encoding = 'iso-8859-1'; 221 } 222 // Find for supplied encoding before to try other things. 223 elseif (preg_match($rx, $HTTP_RAW_POST_DATA, $xml_enc)) { 224 $encoding = strtolower($xml_enc[1]); 225 } 226 // Try UTF-8 detect. 227 elseif (preg_match('/^([\x00-\x7f]|[\xc2-\xdf][\x80-\xbf]|\xe0[\xa0-\xbf][\x80-\xbf]|[\xe1-\xec][\x80-\xbf]{2}|\xed[\x80-\x9f][\x80-\xbf]|[\xee-\xef][\x80-\xbf]{2}|f0[\x90-\xbf][\x80-\xbf]{2}|[\xf1-\xf3][\x80-\xbf]{3}|\xf4[\x80-\x8f][\x80-\xbf]{2})*$/', $HTTP_RAW_POST_DATA) === 1) { 228 $encoding = 'utf-8'; 229 } 230 // Otherwise, use ISO-8859-1. 231 else { 232 $encoding = 'iso-8859-1'; 233 } 234 235 switch ($encoding) { 236 case 'utf-8': 237 $data = $HTTP_RAW_POST_DATA; 238 break; 239 240 case 'iso-8859-1': 241 // TODO: if utf8 conversion fails, throw: 32701 ---> parse error. unsupported encoding? 242 // see: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php & https://github.com/textpattern/textpattern/issues/1051 243 // This will fail on parser if utf8_encode is unavailiable. 244 $data = (function_exists('utf8_encode') && is_callable('utf8_encode')) 245 ? utf8_encode($HTTP_RAW_POST_DATA) 246 : $HTTP_RAW_POST_DATA; 247 break; 248 249 default: 250 // TODO: if utf8 conversion fails, throw: 32701 ---> parse error. unsupported encoding? See https://github.com/textpattern/textpattern/issues/1051 251 // This will fail on parser if mb_convert_encoding is unavailiable. 252 $data = (function_exists('mb_convert_encoding') && is_callable('mb_convert_encoding')) 253 ? mb_convert_encoding($HTTP_RAW_POST_DATA, 'utf-8', $encoding) 254 : $HTTP_RAW_POST_DATA; 255 break; 256 } 257 } 258 259 $this->message = new IXR_Message($data); 260 261 if (!$this->message->parse()) { 262 $this->error(-32700, 'parse error. not well formed'); 263 } 264 265 if ($this->message->messageType != 'methodCall') { 266 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); 267 } 268 269 $result = $this->call($this->message->methodName, $this->message->params); 270 271 // Is the result an error? 272 if (is_a($result, 'IXR_Error')) { 273 $this->error($result); 274 } 275 276 // Encode the result. 277 $r = new IXR_Value($result); 278 $resultxml = $r->getXml(); 279 280 // Create the XML. 281 $xml = <<<EOD 282 <methodResponse> 283 <params> 284 <param> 285 <value> 286 $resultxml 287 </value> 288 </param> 289 </params> 290 </methodResponse> 291 292 EOD; 293 294 return $this->output($xml, $encoding); 295 } 296 297 // Override default UTF-8 output, if needed. 298 function output($xml, $enc = 'utf-8') 299 { 300 // Be kind with non-UTF-8 capable clients. 301 if ($enc != 'utf-8') { 302 if ($enc == 'iso-8859-1' && function_exists('utf8_decode') && is_callable('utf8_decode')) { 303 $xml = utf8_decode($xml); 304 } elseif (function_exists('mb_convert_encoding') && is_callable('mb_convert_encoding')) { 305 $xml = mb_convert_encoding($xml, $enc, 'utf-8'); 306 } else { 307 // TODO: shouldn't this throw an error instead of serving non-UTF-8 content as UTF-8? 308 // If no decoding possible, serve contents as UTF-8. See https://github.com/textpattern/textpattern/issues/1052 309 $enc = 'utf-8'; 310 } 311 } 312 313 $xml = '<?xml version="1.0" encoding="'.$enc.'" ?>'.n.$xml; 314 $length = strlen($xml); 315 header('Connection: close'); 316 header('Content-Length: '.$length); 317 header('Content-Type: text/xml charset='.$enc); 318 header('Date: '.date('r')); 319 echo $xml; 320 exit; 321 } 322 323 //--------------------------------------------------------- 324 325 // Really Simple Discoverability 1.0 response. 326 function rsd() 327 { 328 global $enable_xmlrpc_server; 329 330 if ($enable_xmlrpc_server) { 331 $this->output( 332 tag( 333 n.tag( 334 n.tag('Textpattern', 'engineName'). 335 n.tag('https://textpattern.com/', 'engineLink'). 336 n.tag(hu, 'homePageLink'). 337 n.tag( 338 n.'<api name="Movable Type" blogID="" preferred="true" apiLink="'.txrpcpath.'" />'. 339 n.'<api name="MetaWeblog" blogID="" preferred="false" apiLink="'.txrpcpath.'" />'. 340 n.'<api name="Blogger" blogID="" preferred="false" apiLink="'.txrpcpath.'" />'.n, 341 'apis').n, 342 'service').n, 343 'rsd', ' version="1.0" xmlns="http://archipelago.phrasewise.com/rsd"') 344 ); 345 } else { 346 header('Status: 501 Not Implemented'); 347 header('HTTP/1.1 501 Not Implemented'); 348 die('Not Implemented'); 349 } 350 } 351 352 //--------------------------------------------------------- 353 354 // Blogger API. 355 function blogger_newPost($params) 356 { 357 list($appkey, $blogid, $username, $password, $content, $publish) = $params; 358 359 $txp = new TXP_Wrapper($username, $password); 360 361 if (!$txp->loggedin) { 362 return new IXR_Error(100, gTxt('bad_login')); 363 } 364 365 $contents = $this->_getBloggerContents($content); 366 367 $contents['Section'] = $blogid; 368 $contents['Status'] = $publish ? '4' : '1'; 369 370 $rs = $txp->newArticle($contents); 371 372 if (false === $rs) { 373 return new IXR_Error(201, gTxt('problem_creating_article')); 374 } 375 376 return intval($rs); 377 } 378 379 function blogger_editPost($params) 380 { 381 list($appkey, $postid, $username, $password, $content, $publish) = $params; 382 383 $txp = new TXP_Wrapper($username, $password); 384 385 if (!$txp->loggedin) { 386 return new IXR_Error(100, gTxt('bad_login')); 387 } 388 389 $id = $txp->getArticleID($postid, 'ID'); 390 391 if (!$id) { 392 return new IXR_Error(404, gTxt('invalid_article_id')); 393 } 394 395 $contents = $this->_getBloggerContents($content); 396 397 $contents['Status'] = $publish ? '4' : '1'; 398 399 $rs = $txp->updateArticleID($postid, $contents); 400 401 if (false === $rs) { 402 return new IXR_Error(202, gTxt('problem_updating_article')); 403 } 404 405 return true; 406 } 407 408 function blogger_getUsersBlogs($params) 409 { 410 list($appkey, $username, $password) = $params; 411 412 global $permlink_mode; 413 414 $txp = new TXP_Wrapper($username, $password); 415 416 if (!$txp->loggedin) { 417 return new IXR_Error(100, gTxt('bad_login')); 418 } 419 420 $rs = $txp->getSectionsList(); 421 422 if (false === $rs) { 423 return new IXR_Error(203, gTxt('problem_retrieving_sections')); 424 } 425 426 $sections = array(); 427 428 foreach ($rs as $section) { 429 $sections[] = array( 430 'blogid' => $section['name'], 431 'blogName' => $section['title'], 432 'url' => pagelinkurl(array('s' => $section['name'])), 433 ); 434 } 435 436 return $sections; 437 } 438 439 function blogger_getUserInfo($params) 440 { 441 list($appkey, $username, $password) = $params; 442 443 $txp = new TXP_Wrapper($username, $password); 444 445 if (!$txp->loggedin) { 446 return new IXR_Error(100, gTxt('bad_login')); 447 } 448 449 $rs = $txp->getUser(); 450 451 if (!$rs) { 452 return new IXR_Error(204, gTxt('unable_retrieve_user')); 453 } 454 455 extract($rs); 456 457 if (strpos($RealName, ' ') != 0) { 458 list($firstname, $lastname) = explode(' ', $RealName); 459 } else { 460 $firstname = $RealName; 461 $lastname = ''; 462 } 463 464 $uinfo = array( 465 'userid' => $user_id, 466 'firstname' => $firstname, 467 'lastname' => $lastname, 468 'nickname' => $name, 469 'email' => $email, 470 'url' => hu, 471 ); 472 473 return $uinfo; 474 } 475 476 function blogger_getTemplate($params) 477 { 478 list($appkey, $blogid, $username, $password, $templateType) = $params; 479 480 $txp = new TXP_Wrapper($username, $password); 481 482 if (!$txp->loggedin) { 483 return new IXR_Error(100, gTxt('bad_login')); 484 } 485 486 if ($templateType == 'archiveIndex' && $blogid != 'default') { 487 $section = $txp->getSection($blogid); 488 489 if (!$section) { 490 return new IXR_Error(208, gTxt('unable_retrieve_template')); 491 } 492 493 $name = $section['page']; 494 } else { 495 $name = 'default'; 496 } 497 498 $rs = $txp->getTemplate($name); 499 500 if (!$rs) { 501 return new IXR_Error(208, gTxt('unable_retrieve_template')); 502 } 503 504 return $rs; 505 } 506 507 function blogger_setTemplate($params) 508 { 509 list($appkey, $blogid, $username, $password, $template, $templateType) = $params; 510 511 $txp = new TXP_Wrapper($username, $password); 512 513 if (!$txp->loggedin) { 514 return new IXR_Error(100, gTxt('bad_login')); 515 } 516 517 if ($templateType == 'archiveIndex' && $blogid != 'default') { 518 $section = $txp->getSection($blogid); 519 520 if (!$section) { 521 return new IXR_Error(209, gTxt('unable_set_template')); 522 } 523 524 $name = $section['page']; 525 } else { 526 $name = 'default'; 527 } 528 529 $rs = $txp->setTemplate($name, $template); 530 531 if (!$rs) { 532 return new IXR_Error(209, gTxt('unable_set_template')); 533 } 534 535 return true; 536 } 537 538 //--------------------------------------------------------- 539 540 // Blogger 2.0. 541 function blogger_getPost($params) 542 { 543 list($appkey, $postid, $username, $password) = $params; 544 545 $txp = new TXP_Wrapper($username, $password); 546 547 if (!$txp->loggedin) { 548 return new IXR_Error(100, gTxt('bad_login')); 549 } 550 551 $rs = $txp->getArticleID($postid, 'ID, Body, AuthorId, unix_timestamp(Posted) as uPosted'); 552 553 if (!$rs) { 554 return new IXR_Error(205, gTxt('problem_retrieving_article')); 555 } 556 557 $out = array( 558 'content' => $rs['Body'], 559 'userId' => $rs['AuthorId'], 560 'postId' => $rs['ID'], 561 'dateCreated' => new IXR_Date($rs['uPosted'] + tz_offset()), 562 ); 563 564 return $out; 565 } 566 567 function blogger_deletePost($params) 568 { 569 list($appkey, $postid, $username, $password, $publish) = $params; 570 571 $txp = new TXP_Wrapper($username, $password); 572 573 if (!$txp->loggedin) { 574 return new IXR_Error(100, gTxt('bad_login')); 575 } 576 577 // Always delete, no matter of publish 578 $rs = $txp->deleteArticleID($postid); 579 580 if (!$rs) { 581 return new IXR_Error(206, gTxt('problem_deleting_article')); 582 } 583 584 return $rs; 585 } 586 587 function blogger_getRecentPosts($params) 588 { 589 list($appkey, $blogid, $username, $password, $numberOfPosts) = $params; 590 591 $txp = new TXP_Wrapper($username, $password); 592 593 if (!$txp->loggedin) { 594 return new IXR_Error(100, gTxt('bad_login')); 595 } 596 597 $articles = $txp->getArticleList('ID, Body, AuthorId, unix_timestamp(Posted) as uPosted', "Section='".doSlash($blogid)."'", '0', $numberOfPosts, false); 598 599 if (false === $articles) { 600 return new IXR_Error(207, gTxt('problem_getting_articles')); 601 } 602 603 foreach ($articles as $rs) { 604 $out[] = array( 605 'content' => $rs['Body'], 606 'userId' => $rs['AuthorId'], 607 'postId' => $rs['ID'], 608 'dateCreated' => new IXR_Date($rs['uPosted'] + tz_offset()), 609 ); 610 } 611 612 return $out; 613 } 614 615 //--------------------------------------------------------- 616 617 // metaWeblog API. 618 function metaWeblog_getPost($params) 619 { 620 list($postid, $username, $password) = $params; 621 622 $txp = new TXP_Wrapper($username, $password); 623 624 if (!$txp->loggedin) { 625 return new IXR_Error(100, gTxt('bad_login')); 626 } 627 628 $rs = $txp->getArticleID($postid, 'ID, Title, Body, Excerpt, Annotate, Keywords, Section, Category1, Category2, textile_body, url_title, unix_timestamp(Posted) as uPosted'); 629 630 if (!$rs) { 631 return new IXR_Error(205, gTxt('problem_retrieving_article')); 632 } 633 634 return $this->_buildMetaWeblogStruct($rs, $txp); 635 } 636 637 function metaWeblog_newPost($params) 638 { 639 list($blogid, $username, $password, $struct, $publish) = $params; 640 641 $txp = new TXP_Wrapper($username, $password); 642 643 if (!$txp->loggedin) { 644 return new IXR_Error(100, gTxt('bad_login')); 645 } 646 647 $contents = $this->_getMetaWeblogContents($struct, $publish, $txp); 648 649 $contents['Section'] = $blogid; 650 651 $rs = $txp->newArticle($contents); 652 653 if (false === $rs) { 654 return new IXR_Error(201, gTxt('problem_creating_article')); 655 } 656 657 // TODO: why "" quoted $rs? 658 return "$rs"; 659 } 660 661 function metaWeblog_editPost($params) 662 { 663 list($postid, $username, $password, $struct, $publish) = $params; 664 665 $txp = new TXP_Wrapper($username, $password); 666 667 if (!$txp->loggedin) { 668 return new IXR_Error(100, gTxt('bad_login')); 669 } 670 671 $contents = $this->_getMetaWeblogContents($struct, $publish, $txp); 672 673 $rs = $txp->updateArticleID($postid, $contents); 674 675 if (false === $rs) { 676 return new IXR_Error(201, gTxt('problem_updating_article')); 677 } 678 679 return true; 680 } 681 682 function metaWeblog_getCategories($params) 683 { 684 list($blogid, $username, $password) = $params; 685 686 global $permlink_mode; 687 688 $txp = new TXP_Wrapper($username, $password); 689 690 if (!$txp->loggedin) { 691 return new IXR_Error(100, gTxt('bad_login')); 692 } 693 694 $rs = $txp->getCategoryList(); 695 696 if (false === $rs) { 697 return new IXR_Error(210, gTxt('problem_retrieving_categories')); 698 } 699 700 $cats = array(); 701 702 foreach ($rs as $c) { 703 $cats[] = array( 704 'categoryName' => $c['title'], 705 'description' => $c['title'], 706 'htmlUrl' => pagelinkurl(array('c' => $c['name'])), 707 'rssUrl' => hu.'?rss=1&category='.$c['name'], 708 ); 709 } 710 711 return $cats; 712 } 713 714 function metaWeblog_getRecentPosts($params) 715 { 716 list($blogid, $username, $password, $numberOfPosts) = $params; 717 718 $txp = new TXP_Wrapper($username, $password); 719 720 if (!$txp->loggedin) { 721 return new IXR_Error(100, gTxt('bad_login')); 722 } 723 724 $articles = $txp->getArticleList('ID, Title, url_title, Body, Excerpt, Annotate, Keywords, Section, Category1, Category2, textile_body, AuthorID, unix_timestamp(Posted) as uPosted', 725 "Section='".doSlash($blogid)."'", '0', $numberOfPosts, false); 726 727 if (false === $articles) { 728 return new IXR_Error(207, gTxt('problem_getting_articles')); 729 } 730 731 $out = array(); 732 733 foreach ($articles as $rs) { 734 $out[] = $this->_buildMetaWeblogStruct($rs, $txp); 735 } 736 737 return $out; 738 } 739 740 //--------------------------------------------------------- 741 742 // MovableType API. 743 function mt_getRecentPostTitles($params) 744 { 745 list($blogid, $username, $password, $numberOfPosts) = $params; 746 747 global $prefs; 748 749 $txp = new TXP_Wrapper($username, $password); 750 751 if (!$txp->loggedin) { 752 return new IXR_Error(100, gTxt('bad_login')); 753 } 754 755 $articles = $txp->getArticleList('ID, Title, AuthorID, unix_timestamp(Posted) as uPosted', "Section='".doSlash($blogid)."'", '0', $numberOfPosts, false); 756 757 if (false === $articles) { 758 return new IXR_Error(207, gTxt('problem_getting_articles')); 759 } 760 761 extract($prefs); 762 763 $out = array(); 764 765 foreach ($articles as $rs) { 766 $out[] = array( 767 'userid' => $username, 768 'postid' => $rs['ID'], 769 'dateCreated' => new IXR_Date($rs['uPosted'] + tz_offset()), 770 'title' => $rs['Title'], 771 ); 772 } 773 774 return $out; 775 } 776 777 function mt_getCategoryList($params) 778 { 779 list($blogid, $username, $password) = $params; 780 781 $txp = new TXP_Wrapper($username, $password); 782 783 if (!$txp->loggedin) { 784 return new IXR_Error(100, gTxt('bad_login')); 785 } 786 787 $rs = $txp->getCategoryList(); 788 789 if (false === $rs) { 790 return new IXR_Error(210, gTxt('problem_retrieving_categories')); 791 } 792 793 $cats = array(); 794 795 foreach ($rs as $c) { 796 $cats[] = array( 797 'categoryName' => $c['title'], 798 'categoryId' => $c['id'], 799 ); 800 } 801 802 return $cats; 803 } 804 805 function mt_supportedTextFilters($params) 806 { 807 $filters = array( 808 array('key' => '0', 'label' => 'LEAVE_TEXT_UNTOUCHED'), 809 array('key' => '1', 'label' => 'USE_TEXTILE'), 810 array('key' => '2', 'label' => 'CONVERT_LINEBREAKS'), 811 ); 812 813 return $filters; 814 } 815 816 function mt_getPostCategories($params) 817 { 818 list($postid, $username, $password) = $params; 819 820 $txp = new TXP_Wrapper($username, $password); 821 822 if (!$txp->loggedin) { 823 return new IXR_Error(100, gTxt('bad_login')); 824 } 825 826 $post = $txp->getArticleID($postid, 'Category1, Category2'); 827 828 if (!$post) { 829 return new IXR_Error(211, gTxt('problem_retrieving_article_categories')); 830 } 831 832 $out = array(); 833 $isPrimary = true; 834 $cats[] = $post['Category1']; 835 $cats[] = $post['Category2']; 836 837 foreach ($cats as $category) { 838 if (!empty($category)) { 839 $rs = $txp->getCategory($category); 840 841 // TODO: remove? 842 // if (!$rs) return new IXR_Error(212, gTxt('problem_retrieving_category_info')); 843 844 $ct['categoryId'] = $rs['id']; 845 $ct['categoryName'] = $rs['title']; 846 $ct['isPrimary'] = $isPrimary; 847 848 $out[] = $ct; 849 } 850 851 if ($isPrimary) { 852 $isPrimary = false; 853 } 854 } 855 856 return $out; 857 } 858 859 // TODO: explain what 'expecific' is ;) 860 // Supported to avoid some client expecific behaviour. 861 function mt_publishPost($params) 862 { 863 list($postid, $username, $password) = $params; 864 865 $txp = new TXP_Wrapper($username, $password); 866 867 if (!$txp->loggedin) { 868 return new IXR_Error(100, gTxt('bad_login')); 869 } 870 871 $published = $txp->updateArticleField($postid, 'Status', '4'); 872 873 if (!$published) { 874 return new IXR_Error(201, gTxt('problem_updating_article')); 875 } 876 877 return true; 878 } 879 880 function mt_setPostCategories($params) 881 { 882 list($postid, $username, $password, $categories) = $params; 883 884 $txp = new TXP_Wrapper($username, $password); 885 886 if (!$txp->loggedin) { 887 return new IXR_Error(100, gTxt('bad_login')); 888 } 889 890 $Category1 = ''; 891 $Category2 = ''; 892 893 foreach ($categories as $category) { 894 extract($category); 895 896 $rs = $txp->getCategoryId($categoryId); 897 898 if (!$rs) { 899 return new IXR_Error(213, gTxt('trying_to_assign_unexisting_category_to_the_article')); 900 } 901 902 if (empty($Category1)) { 903 $Category1 = $rs['name']; 904 } else { 905 $Category2 = $rs['name']; 906 } 907 } 908 909 $ct1 = $txp->updateArticleField($postid, 'Category1', $Category1); 910 $ct2 = $txp->updateArticleField($postid, 'Category2', $Category2); 911 912 if (!$ct1 || !$ct2) { 913 return new IXR_Error(214, gTxt('problem_saving_article_categories')); 914 } 915 916 return true; 917 } 918 919 // TODO ??? 920 // MediaObjects 921 /* 922 metaWeblog.newMediaObject 923 Description: Uploads a file to your webserver. 924 Parameters: String blogid, String username, String password, struct file 925 Return value: URL to the uploaded file. 926 Notes: the struct file should contain two keys: base64 bits (the base64-encoded contents of the file) 927 and String name (the name of the file). The type key (media type of the file) is currently ignored. 928 */ 929 930 // Code refactoring for blogger_newPost and blogger_editPost. 931 function _getBloggerContents($content) 932 { 933 $body = $content; 934 935 // Trick to add title, category and excerpts using XML-RPC. 936 if (preg_match('/<title>(.*)<\/title>(.*)/s', $content, $matches)) { 937 $body = $matches[2]; 938 $title = $matches[1]; 939 } 940 941 $contents = array( 942 'Body' => str_replace('\n', n, $body), 943 ); 944 945 if (isset($title)) { 946 $contents['Title'] = $title; 947 } 948 949 return $contents; 950 } 951 952 // Code refactoring for metaWeblog_newPost and metaweblog_EditPost. 953 function _getMetaWeblogContents($struct, $publish, $txp) 954 { 955 global $gmtoffset, $is_dst; 956 957 $contents = array( 958 'Body' => str_replace('\n', n, $struct['description']), 959 'Status' => $publish ? '4' : '1', 960 'Title' => $struct['title'], 961 ); 962 963 if (!empty($struct['categories'])) { 964 if (!empty($struct['categories'][0])) { 965 $c = $txp->getCategoryTitle($struct['categories'][0]); 966 $contents['Category1'] = $c['name']; 967 } 968 969 if (!empty($struct['categories'][1])) { 970 $c = $txp->getCategoryTitle($struct['categories'][1]); 971 $contents['Category2'] = $c['name']; 972 } 973 } 974 975 if (isset($struct['date_created_gmt'])) { 976 $struct['dateCreated'] = $struct['date_created_gmt']; 977 $struct['dateCreated']->tz = 'Z'; // force GMT timezone 978 } 979 980 if (isset($struct['dateCreated'])) { 981 if ($struct['dateCreated']->tz == 'Z') { 982 // GMT-based posting time; transform into server time zone. 983 $posted = $struct['dateCreated']->getTimestamp() - tz_offset() + $gmtoffset + ($is_dst ? 3600 : 0); 984 } elseif (!$struct['dateCreated']->tz) { 985 // Posting in an unspecified time zone: Assume site time. 986 $posted = $struct['dateCreated']->getTimestamp() - tz_offset(); 987 } else { 988 // Numeric time zone offsets. 989 if (preg_match('/([+-][0-9]{2})([0-9]{2})/', $struct['dateCreated']->tz, $t)) { 990 $tz = $t[1] * 3600 + $t[2] * 60; 991 $posted = $struct['dateCreated']->getTimestamp() - tz_offset() + $gmtoffset + ($is_dst ? 3600 : 0) - $tz; 992 } 993 } 994 } 995 996 if (isset($posted)) { 997 $contents['Posted'] = date('Y-m-d H:i:s', $posted); 998 } 999 1000 // MovableType implementation add-ons 1001 if (isset($struct['mt_allow_comments'])) { 1002 $contents['Annotate'] = $struct['mt_allow_comments']; 1003 } 1004 1005 if (isset($struct['mt_convert_breaks'])) { 1006 $contents['textile_body'] = $contents['textile_excerpt'] = intval($struct['mt_convert_breaks']); 1007 } 1008 1009 if (isset($struct['mt_text_more'])) { 1010 $contents['Body'] .= n.n.str_replace('\n', n, $struct['mt_text_more']); 1011 } 1012 1013 if (isset($struct['mt_excerpt'])) { 1014 $contents['Excerpt'] = str_replace('\n', n, $struct['mt_excerpt']); 1015 } 1016 1017 if (isset($struct['mt_keywords'])) { 1018 $contents['Keywords'] = $struct['mt_keywords']; 1019 } 1020 1021 if (isset($struct['mt_basename'])) { 1022 $contents['url_title'] = stripSpace($struct['mt_basename']); 1023 } elseif (isset($struct['wp_slug'])) { 1024 $contents['url_title'] = stripSpace($struct['wp_slug']); 1025 } 1026 1027 return $contents; 1028 } 1029 1030 // Common code to metaWeblog_getPost and metaWeblog_getRecentPosts could 1031 // not be this placed on a different file from taghandlers? 1032 // Remove if it is the case. 1033 function _buildMetaWeblogStruct($rs, $txp) 1034 { 1035 global $permlink_mode, $is_dst, $gmtoffset; 1036 1037 switch ($permlink_mode) { 1038 case 'section_id_title': 1039 $url = hu.join('/', array($rs['Section'], $rs['ID'], $rs['url_title'])); 1040 break; 1041 1042 case 'year_month_day_title': 1043 $url = hu.join('/', array( 1044 date("Y", $rs['uPosted']), 1045 date("m", $rs['uPosted']), 1046 date("d", $rs['uPosted']), 1047 $rs['url_title'], 1048 )); 1049 break; 1050 1051 case 'title_only': 1052 $url = hu.$rs['url_title']; 1053 break; 1054 1055 case 'section_title': 1056 $url = hu.join('/', array($rs['Section'], $rs['url_title'])); 1057 break; 1058 1059 case 'id_title': 1060 $url = hu.join('/', array($rs['ID'], $rs['url_title'])); 1061 break; 1062 1063 default: 1064 // Assume messy mode? 1065 $url = hu.'?id='.$rs['ID']; 1066 break; 1067 } 1068 1069 $cat = $txp->getCategory($rs['Category1']); 1070 $cat1 = $cat['title']; 1071 $cat = $txp->getCategory($rs['Category2']); 1072 $cat2 = $cat['title']; 1073 1074 $out = array( 1075 'categories' => array($cat1, $cat2), 1076 'description' => $rs['Body'], 1077 'userid' => $txp->txp_user, 1078 'postid' => $rs['ID'], 1079 'dateCreated' => new IXR_Date($rs['uPosted'] + tz_offset() - $gmtoffset - ($is_dst ? 3600 : 0)), 1080 'link' => $url, 1081 'permaLink' => $url, 1082 'title' => $rs['Title'], 1083 ); 1084 1085 $out['dateCreated']->tz = 'Z'; // GMT. 1086 1087 // MovableType Implementation add-ons. 1088 if (isset($rs['Annotate']) && !empty($rs['Annotate'])) { 1089 $out['mt_allow_comments'] = intval($rs['Annotate']); 1090 } 1091 1092 if (isset($rs['textile_body']) && !empty($rs['textile_body'])) { 1093 $out['mt_convert_breaks'] = strval($rs['textile_body']); 1094 } 1095 1096 if (isset($rs['Excerpt']) && !empty($rs['Excerpt'])) { 1097 $out['mt_excerpt'] = $rs['Excerpt']; 1098 } 1099 1100 if (isset($rs['Keywords']) && !empty($rs['Keywords'])) { 1101 $out['mt_keywords'] = $rs['Keywords']; 1102 } 1103 1104 if (isset($rs['url_title']) && !empty($rs['url_title'])) { 1105 $out['mt_basename'] = $out['wp_slug'] = $rs['url_title']; 1106 } 1107 1108 return $out; 1109 } 1110 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title