diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/rsd.xml.php | 15 | ||||
-rw-r--r-- | lib/xmlrpc.php | 635 |
2 files changed, 0 insertions, 650 deletions
diff --git a/lib/rsd.xml.php b/lib/rsd.xml.php deleted file mode 100644 index f0f9c12..0000000 --- a/lib/rsd.xml.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - require_once(__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'config.php'); - - header('Content-Type: text/xml; charset=utf-8'); -?><rsd xmlns="http://archipelago.phrasewise.com/rsd" version="1.0"> - <service> - <engineName>oelna/microblog</engineName> - <engineLink>https://github.com/oelna/microblog</engineLink> - <homePageLink><?= $config['url'] ?></homePageLink> - <apis> - <api name="Micro.blog" blogID="1" preferred="true" apiLink="<?= $config['url'] ?>/xmlrpc" /> - <api name="MetaWeblog" blogID="1" preferred="false" apiLink="<?= $config['url'] ?>/xmlrpc" /> - </apis> - </service> -</rsd> diff --git a/lib/xmlrpc.php b/lib/xmlrpc.php deleted file mode 100644 index 71f8a58..0000000 --- a/lib/xmlrpc.php +++ /dev/null @@ -1,635 +0,0 @@ -<?php - -$request_xml = isset($request_xml) ? $request_xml : file_get_contents("php://input"); - -// check prerequisites -if(!$config['xmlrpc']) { exit('No XML-RPC support detected!'); } -if(empty($request_xml) && !isset($_GET['test'])) { exit('XML-RPC server accepts POST requests only.'); } - -$logfile = ROOT.DS.'log.txt'; - -if(!function_exists('str_starts_with')) { - function str_starts_with($haystack, $needle) { - if(empty($needle)) return true; - return mb_substr($haystack, 0, mb_strlen($needle)) === $needle; - } -} - -function check_credentials($username, $password) { - global $config; - - $xmlrpc_auth = $config['admin_pass']; - if(!empty($config['app_token'])) { - $xmlrpc_auth = $config['app_token']; - } - - if($username == $config['admin_user'] && $password == $xmlrpc_auth) { - return true; - } else { - return false; - } -} - -function say_hello($method_name, $args) { - return 'Hello'; -} - -function make_post($post, $method='metaWeblog') { - global $config; - - $date_created = date('Y-m-d\TH:i:s', $post['post_timestamp']).$config['local_time_offset']; - $date_created_gmt = gmdate('Y-m-d\TH:i:s', $post['post_timestamp']).'Z'; - if(!empty($post['post_edited']) && !is_null($post['post_edited'])) { - $date_modified = date('Y-m-d\TH:i:s', $post['post_edited']).$config['local_time_offset']; - $date_modified_gmt = gmdate('Y-m-d\TH:i:s', $post['post_edited']).'Z'; - } else { - $date_modified = date('Y-m-d\TH:i:s', 0).$config['local_time_offset']; - $date_modified = null; - $date_modified_gmt = gmdate('Y-m-d\TH:i:s', 0).'Z'; - } - - @xmlrpc_set_type($date_created, 'datetime'); - @xmlrpc_set_type($date_created_gmt, 'datetime'); - @xmlrpc_set_type($date_modified, 'datetime'); - @xmlrpc_set_type($date_modified_gmt, 'datetime'); - - // format file attachments - if(!empty($post['post_attachments'])) { - foreach($post['post_attachments'] as $attachment) { - // todo: handle attachments other than images - $attachment_url = get_file_url($attachment); - $post['post_content'] .= PHP_EOL.'<img src="'.$attachment_url.'" alt="" />'; - } - } - - if(str_starts_with($method, 'microblog')) { - // convert the post format to a microblog post - // similar to metaWeblog.recentPosts but with offset parameter for paging, - // consistent field names - $mb_post = [ - 'id' => (int) $post['id'], - 'date_created' => $date_created, - 'date_modified' => $date_modified, - 'permalink' => $config['url'].'/'.$post['id'], - 'title' => '', - 'description' => $post['post_content'], - 'categories' => [], - 'post_status' => 'published', - 'author' => [ - 'name' => $config['microblog_account'], - 'username' => $config['admin_user'] - ] - ]; - - return $mb_post; - } else { - // convert the post format to a standard metaWeblog post - $mw_post = [ - 'postid' => (int) $post['id'], - 'title' => '', - 'description' => $post['post_content'], // Post content - 'link' => $config['url'].'/'.$post['id'], // Post URL - // string userid†: ID of post author. - 'dateCreated' => $date_created, - 'date_created_gmt' => $date_created_gmt, - 'date_modified' => $date_modified, - 'date_modified_gmt' => $date_modified_gmt, - // string wp_post_thumbnail†- 'permalink' => $config['url'].'/'.$post['id'], // Post URL, equivalent to link. - 'categories' => [], // Names of categories assigned to the post. - 'mt_keywords' => '', // Names of tags assigned to the post. - 'mt_excerpt' => '', - 'mt_text_more' => '', // Post "Read more" text. - ]; - - return $mw_post; - } -} - -function mw_get_users_blogs($method_name, $args) { - global $config; - - list($_, $username, $password) = $args; - - if(!check_credentials($username, $password)) { - return [ - 'faultCode' => 403, - 'faultString' => 'Incorrect username or password.' - ]; - } - - $bloginfo = [ - 'blogid' => '1', - 'url' => $config['url'], - 'blogName' => (empty($config['microblog_account']) ? "" : $config['microblog_account'] . "'s ").' microblog', - ]; - - return $bloginfo; -} - -function mw_get_categories($method_name, $args) { - - list($_, $username, $password) = $args; - - if(!check_credentials($username, $password)) { - return [ - 'faultCode' => 403, - 'faultString' => 'Incorrect username or password.' - ]; - } - - // we don't support categories, so only return a fake one - if($method_name == 'microblog.getCategories') { - $categories = [ - /* - [ - 'id' => '1', - 'name' => 'default', - ] - */ - ]; - } else { - $categories = [ - [ - 'categoryId' => '1', - 'parentId' => '0', - 'categoryName' => 'default', - 'categoryDescription' => 'The default category', - 'description' => 'default', - 'htmlUrl' => '/', - 'rssUrl' => '/' - ] - /* - [ - 'description' => 'Default', - 'htmlUrl' => '', - 'rssUrl' => '', - 'title' => 'default', - 'categoryid' => '1', - ] - */ - ]; - } - - return $categories; -} - -function mw_get_user_info($method_name, $args) { - global $config; - - list($_, $username, $password) = $args; - - if(!check_credentials($username, $password)) { - return [ - 'faultCode' => 403, - 'faultString' => 'Incorrect username or password.' - ]; - } - - $userinfo = [ - 'userid' => '1', - 'firstname' => '', - 'lastname' => '', - 'nickname' => $config['microblog_account'], - 'email' => '', - 'url' => $config['url'], - ]; - - return $userinfo; -} - -function mw_get_recent_posts($method_name, $args) { - - list($_, $username, $password, $amount) = $args; - $offset = 0; - if($method_name == 'microblog.getPosts' && !empty($args[4])) { - $offset = $args[4]; - } - - if(!check_credentials($username, $password)) { - return [ - 'faultCode' => 403, - 'faultString' => 'Incorrect username or password.' - ]; - } - - if(!$amount) $amount = 25; - $amount = min($amount, 200); // cap the max available posts at 200 (?) - - $posts = db_select_posts(null, $amount, 'desc', $offset); - if(empty($posts)) return []; - - // get attached files - $ids = array_column($posts, 'id'); - $attached_files = db_get_attached_files($ids); - - for ($i=0; $i < count($posts); $i++) { - if(!empty($attached_files[$posts[$i]['id']])) { - $posts[$i]['post_attachments'] = $attached_files[$posts[$i]['id']]; - } - } - - // call make_post() on all items - $mw_posts = array_map('make_post', $posts, array_fill(0, count($posts), $method_name)); - - return $mw_posts; -} - -function mw_get_post($method_name, $args) { - // todo: find a way to represent media attachments to editors - - list($post_id, $username, $password) = $args; - - if(!check_credentials($username, $password)) { - return [ - 'faultCode' => 403, - 'faultString' => 'Incorrect username or password.' - ]; - } - - $post = db_select_post($post_id); - - // get attached files - $attached_files = db_get_attached_files($post_id); - - if(!empty($attached_files[$post_id])) { - $post['post_attachments'] = $attached_files[$post_id]; - } - - if($post) { - if($method_name == 'microblog.getPost') { - $mw_post = make_post($post, $method_name); - } else { - $mw_post = make_post($post); - } - - return $mw_post; - } else { - return [ - 'faultCode' => 400, - 'faultString' => 'Could not fetch post.' - ]; - } -} - -function mw_new_post($method_name, $args) { - global $config; - - if($method_name == 'blogger.newPost') { - // app_key (unused), blog_id, user, pass, array of post content, publish/draft - - list($_, $blog_id, $username, $password, $content, $publish_flag) = $args; - } else { - // blog_id, user, pass, array of post content, unknown - list($blog_id, $username, $password, $content, $_) = $args; - } - - if(!check_credentials($username, $password)) { - return [ - 'faultCode' => 403, - 'faultString' => 'Incorrect username or password.' - ]; - } - - if($method_name == 'microblog.newPost') { - $post = [ - // 'post_title' => $content['title'], - 'post_content' => $content['description'], - 'post_timestamp' => time(), - // 'post_categories' => $content['categories'], - // 'post_status' => $content['post_status'], - ]; - - // use a specific timestamp, if provided - if(isset($content['date_created'])) { - $post['post_timestamp'] = $content['date_created']->timestamp; - } - } elseif($method_name == 'blogger.newPost') { - // support eg. micro.blog iOS app - $post = [ - 'post_content' => $content, - 'post_timestamp' => time() - ]; - } else { - $post = [ - // 'post_hp' => $content['flNotOnHomePage'], - 'post_timestamp' => time(), - // 'post_title' => $content['title'], - 'post_content' => $content['description'], - // 'post_url' => $content['link'], - ]; - - // use a specific timestamp, if provided - if(isset($content['dateCreated'])) { - $post['post_timestamp'] = $content['dateCreated']->timestamp; - } - } - - // clean up image tags and get references to image files - $image_urls = images_from_html($post['post_content']); - - // remove image tags - $post['post_content'] = strip_img_tags($post['post_content']); - - $insert_id = db_insert($post['post_content'], $post['post_timestamp']); - if($insert_id && $insert_id > 0) { - // create references to file attachments - if(!empty($image_urls)) { - foreach ($image_urls as $url) { - if(str_contains($url, $config['url'])) { - $filename = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_FILENAME); - - // todo: match by hash instead of filename?? - - $file = db_select_file($filename, 'filename'); - if($file) db_link_file($file['id'], $insert_id); - } - } - } - - // success - rebuild_feeds(); - if($config['activitypub'] == true) activitypub_notify_followers($insert_id); - - return (int) $insert_id; - } else { - // insert failed - // error codes: https://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php - // more error codes? https://github.com/zendframework/zend-xmlrpc/blob/master/src/Fault.php - return [ - 'faultCode' => 400, - 'faultString' => 'Could not create post.' - ]; - } -} - -function mw_edit_post($method_name, $args) { - // todo: make this work with different hash algorithms, as stored in the DB - global $config; - global $db; - - // post_id, user, password, array of post content - list($post_id, $username, $password, $content) = $args; - - if(!check_credentials($username, $password)) { - return [ - 'faultCode' => 403, - 'faultString' => 'Incorrect username or password.' - ]; - } - - if($method_name == 'microblog.editPost') { - $post = [ - // 'post_title' => $content['title'], - 'post_content' => $content['description'], - 'post_timestamp' => null, - // 'post_categories' => $content['categories'], - // 'post_status' => $content['post_status'], - ]; - - if(!empty($content['date_created'])) { - $post['post_timestamp'] = $content['date_created']->timestamp; - } - } else { - $post = [ - // 'post_hp' => $content['flNotOnHomePage'], - // 'post_title' => $content['title'], - 'post_timestamp' => null, - 'post_content' => $content['description'], - // 'post_url' => $content['link'], - ]; - - if(!empty($content['dateCreated'])) { - $post['post_timestamp'] = $content['dateCreated']->timestamp; - } - } - - // compare old and new image attachments: - - // load existing attachments - $attached_files = db_get_attached_files($post_id); - - // extract new attachments from images - $image_urls = images_from_html($post['post_content']); - -var_dump($image_urls); - // compare via file hash - $hashes_of_existing_links = array_column(reset($attached_files), 'file_hash'); - $hashes_of_edited_post = []; - - foreach($image_urls as $url) { - $file_path = ROOT.parse_url($url, PHP_URL_PATH); // fragile? - - if(file_exists($file_path)) { - var_dump($file_path); - $hashes_of_edited_post[] = make_file_hash($file_path); - } - } - // $hashes_of_edited_post = array_unique($hashes_of_edited_post); - - $hashes_to_link = array_diff($hashes_of_edited_post, $hashes_of_existing_links); - $hashes_to_unlink = array_diff($hashes_of_existing_links, $hashes_of_edited_post); - - - var_dump([ - 'post_id' => $post_id, - 'existing' => $hashes_of_existing_links, - 'new' => $hashes_of_edited_post, - 'to_link' => $hashes_to_link, - 'to_unlink' => $hashes_to_unlink - ]); - - - try { - $statement = $db->prepare('INSERT OR REPLACE INTO file_to_post (file_id, post_id, deleted) VALUES ((SELECT id FROM files WHERE file_hash = :file_hash LIMIT 1), :post_id, :deleted)'); - - $attachment = [ - 'hash' => null, - 'post' => $post_id - ]; - - $statement->bindParam(':file_hash', $attachment['hash'], PDO::PARAM_STR); - $statement->bindParam(':post_id', $attachment['post'], PDO::PARAM_INT); - - // link - foreach ($hashes_to_link as $hash) { - $attachment['hash'] = $hash; - $statement->bindValue(':deleted', null, PDO::PARAM_NULL); - $statement->execute(); - } - - // unlink - foreach ($hashes_to_unlink as $hash) { - $attachment['hash'] = $hash; - $statement->bindValue(':deleted', time(), PDO::PARAM_INT); - $statement->execute(); - } - - } catch(PDOException $e) { - return [ - 'faultCode' => 400, - 'faultString' => 'Post update SQL error: '.$e->getMessage() - ]; - } - - // remove html img tags - $post['post_content'] = strip_img_tags($post['post_content']); - - $update = db_update($post_id, $post['post_content'], $post['post_timestamp']); - if($update && $update > 0) { - // success - rebuild_feeds(); - // todo: does this have to notify activitypub followers too? - - return true; - } else { - return [ - 'faultCode' => 400, - 'faultString' => 'Could not write post update.' - ]; - } -} - -function mw_delete_post($method_name, $args) { - - if($method_name == 'microblog.deletePost') { - list($post_id, $username, $password) = $args; - } else { - // blogger.deletePost - list($_, $post_id, $username, $password, $_) = $args; - } - - if(!check_credentials($username, $password)) { - return [ - 'faultCode' => 403, - 'faultString' => 'Incorrect username or password.' - ]; - } - - $success = db_delete($post_id); - if($success > 0) { - rebuild_feeds(); - // todo: does this have to notify activitypub followers too? - - return true; - } else { - return [ - 'faultCode' => 400, - 'faultString' => 'Could not delete post.' - ]; - } -} - -function mw_new_media_object($method_name, $args) { - global $config; - - list($blog_id, $username, $password, $data) = $args; - - if(!check_credentials($username, $password)) { - return [ - 'faultCode' => 403, - 'faultString' => 'Incorrect username or password.' - ]; - } - - $new_ext = pathinfo($data['name'], PATHINFO_EXTENSION); - if(!empty($data['type'])) { - $new_ext = mime_to_extension($data['type']); - } - - // file_put_contents(ROOT.DS.'test.txt', json_encode([$data['type'], pathinfo($data['name'], PATHINFO_EXTENSION), $new_ext])); - - $media_object = $data['bits']->scalar; - - // save the file to a temporary location - $tmp_file = tempnam(sys_get_temp_dir(), 'microblog_'); - file_put_contents($tmp_file, $media_object); - - // make a DB entry for the file - $new_file_id = save_file($data['name'], $new_ext, $tmp_file, 0, $data['type']); - - // get file info - $file = db_select_file($new_file_id, 'id'); - // $file_path = get_file_path($file); - - $url = get_file_url($file); - - $success = ($new_file_id) ? 1 : 0; - - if($success > 0) { - // If newMediaObject succeeds, it returns a struct, which must contain at least one element, url, which is the url through which the object can be accessed. It must be either an FTP or HTTP url. - return [ - // 'url' => 'https://microblog.oelna.de/files/this-is-a-test.jpg' - 'url' => $url, - 'title' => $file['file_original'], - 'type' => $data['type'] ? $data['type'] : 'image/jpg' // is this reasonable? - ]; - } else { - // If newMediaObject fails, it throws an error. - return [ - 'faultCode' => 400, - 'faultString' => 'Could not store media object.' - ]; - } -} - -// https://codex.wordpress.org/XML-RPC_MetaWeblog_API -// https://community.devexpress.com/blogs/theprogressbar/metablog.ashx -// idea: http://www.hixie.ch/specs/pingback/pingback#TOC3 -$server = xmlrpc_server_create(); -xmlrpc_server_register_method($server, 'demo.sayHello', 'say_hello'); - -// http://nucleuscms.org/docs//item/204 -xmlrpc_server_register_method($server, 'blogger.newPost', 'mw_new_post'); -xmlrpc_server_register_method($server, 'blogger.editPost', 'mw_edit_post'); -xmlrpc_server_register_method($server, 'blogger.getPost', 'mw_get_post'); -xmlrpc_server_register_method($server, 'blogger.deletePost', 'mw_delete_post'); -xmlrpc_server_register_method($server, 'blogger.getUsersBlogs', 'mw_get_users_blogs'); -xmlrpc_server_register_method($server, 'blogger.getRecentPosts', 'mw_get_recent_posts'); -xmlrpc_server_register_method($server, 'blogger.getUserInfo', 'mw_get_user_info'); -xmlrpc_server_register_method($server, 'blogger.newMediaObject', 'mw_new_media_object'); - -xmlrpc_server_register_method($server, 'metaWeblog.getCategories', 'mw_get_categories'); -xmlrpc_server_register_method($server, 'metaWeblog.getRecentPosts', 'mw_get_recent_posts'); -xmlrpc_server_register_method($server, 'metaWeblog.newPost', 'mw_new_post'); -xmlrpc_server_register_method($server, 'metaWeblog.editPost', 'mw_edit_post'); -xmlrpc_server_register_method($server, 'metaWeblog.getPost', 'mw_get_post'); -xmlrpc_server_register_method($server, 'metaWeblog.newMediaObject', 'mw_new_media_object'); - -// non-standard convenience? -xmlrpc_server_register_method($server, 'metaWeblog.getPosts', 'mw_get_recent_posts'); -xmlrpc_server_register_method($server, 'metaWeblog.deletePost', 'mw_delete_post'); - -// micro.blog API methods (currently just using the metaWeblog functions) -// https://help.micro.blog/t/micro-blog-xml-rpc-api/108 -xmlrpc_server_register_method($server, 'microblog.getCategories', 'mw_get_categories'); -xmlrpc_server_register_method($server, 'microblog.getPosts', 'mw_get_recent_posts'); -xmlrpc_server_register_method($server, 'microblog.getPost', 'mw_get_post'); -xmlrpc_server_register_method($server, 'microblog.newPost', 'mw_new_post'); -xmlrpc_server_register_method($server, 'microblog.editPost', 'mw_edit_post'); -xmlrpc_server_register_method($server, 'microblog.deletePost', 'mw_delete_post'); -xmlrpc_server_register_method($server, 'microblog.newMediaObject', 'mw_new_media_object'); - -// micro.blog pages are not supported -/* -xmlrpc_server_register_method($server, 'microblog.getPages', 'say_hello'); -xmlrpc_server_register_method($server, 'microblog.getPage', 'say_hello'); -xmlrpc_server_register_method($server, 'microblog.newPage', 'say_hello'); -xmlrpc_server_register_method($server, 'microblog.editPage', 'say_hello'); -xmlrpc_server_register_method($server, 'microblog.deletePage', 'say_hello'); -*/ - -if(!isset($_GET['test'])) { - // https://docstore.mik.ua/orelly/webprog/pcook/ch12_08.htm - $response = xmlrpc_server_call_method($server, $request_xml, null, [ - 'escaping' => 'markup', - 'encoding' => 'UTF-8' - ]); - - if($response) { - header('Content-Type: text/xml; charset=utf-8'); - // todo: make error logging a config option - // error_log(date('Y-m-d H:i:s')."\n".$request_xml."\n\n".$response."\n\n", 3, $logfile); - echo($response); - } -} |