diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | .htaccess | 6 | ||||
-rw-r--r-- | functions.php | 6 | ||||
-rw-r--r-- | loginform.inc.php | 13 | ||||
-rw-r--r-- | postform.inc.php | 10 | ||||
-rw-r--r-- | rsd.xml.php | 4 | ||||
-rw-r--r-- | single.inc.php | 12 | ||||
-rw-r--r-- | timeline.inc.php | 16 | ||||
-rw-r--r-- | xmlrpc.php | 246 |
9 files changed, 234 insertions, 80 deletions
@@ -2,3 +2,4 @@ config.php feed.* feeds/ +log.txt @@ -18,6 +18,12 @@ AddType application/json .json RewriteEngine On RewriteBase /microblog +# friendly URLs +RewriteRule ^rsd/?$ rsd.xml.php [L] +RewriteRule ^xmlrpc/?(.*)$ xmlrpc.php?/$1 [L] +RewriteRule ^feed/json/?$ feed/feed.json [L] +RewriteRule ^feed/atom/?$ feed/feed.xml [L] + RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*) index.php [L] diff --git a/functions.php b/functions.php index 8464267..84be8ac 100644 --- a/functions.php +++ b/functions.php @@ -74,14 +74,12 @@ function db_select_post($id=0) { return (!empty($row)) ? $row : false; } -function db_select_posts($from=NOW, $amount=10, $sort='desc', $page=1) { - global $config; +function db_select_posts($from, $amount=10, $sort='desc', $offset=0) { global $db; if(empty($db)) return false; + if(empty($from)) $from = time(); if($sort !== 'desc') $sort = 'asc'; - $offset = ($page-1)*$config['posts_per_page']; - $statement = $db->prepare('SELECT * FROM posts WHERE post_timestamp < :post_timestamp AND post_deleted IS NULL ORDER BY post_timestamp '.$sort.' LIMIT :limit OFFSET :page'); $statement->bindValue(':post_timestamp', $from, PDO::PARAM_INT); $statement->bindValue(':limit', $amount, PDO::PARAM_INT); diff --git a/loginform.inc.php b/loginform.inc.php index 00de23f..ad7e031 100644 --- a/loginform.inc.php +++ b/loginform.inc.php @@ -23,8 +23,14 @@ ?><!DOCTYPE html> <html lang="<?= $config['language'] ?>" class="login"> <head> - <title>micro.blog</title> + <meta charset="utf-8" /> + <title><?= empty($config['microblog_account']) ? "" : $config['microblog_account'] . "'s "; ?>micro.blog</title> + <meta name="viewport" content="width=device-width" /> + <link rel="alternate" type="application/json" title="JSON Feed" href="<?= $config['url'] ?>/feed/json" /> + <link rel="alternate" type="application/atom+xml" title="Atom Feed" href="<?= $config['url'] ?>/feed/atom" /> + <?php if($config['xmlrpc']): ?><link rel="EditURI" type="application/rsd+xml" title="RSD" href="<?= $config['url'] ?>/rsd" /><?php endif; ?> <link rel="stylesheet" href="<?= $config['url'] ?>/microblog.css" /> + <script src="<?= $config['url'] ?>/microblog.js" type="module" defer></script> </head> <body> <div class="wrap"> @@ -47,8 +53,9 @@ <footer> <nav> <ul> - <li><a href="<?= $config['url'] ?>/feed/feed.xml">ATOM Feed</a></li> - <li><a href="<?= $config['url'] ?>/feed/feed.json">JSON Feed</a></li> + <li><a href="<?= $config['url'] ?>/feed/atom">ATOM Feed</a></li> + <li><a href="<?= $config['url'] ?>/feed/json">JSON Feed</a></li> + <?php if($config['xmlrpc']): ?><li><a href="<?= $config['url'] ?>/xmlrpc">XML-RPC</a></li><?php endif; ?> </ul> </nav> </footer> diff --git a/postform.inc.php b/postform.inc.php index 70fdc7b..4755b98 100644 --- a/postform.inc.php +++ b/postform.inc.php @@ -44,8 +44,12 @@ ?><!DOCTYPE html> <html lang="<?= $config['language'] ?>" class="postform"> <head> + <meta charset="utf-8" /> <title>micro.blog</title> <meta name="viewport" content="width=device-width" /> + <link rel="alternate" type="application/json" title="JSON Feed" href="<?= $config['url'] ?>/feed/json" /> + <link rel="alternate" type="application/atom+xml" title="Atom Feed" href="<?= $config['url'] ?>/feed/atom" /> + <?php if($config['xmlrpc']): ?><link rel="EditURI" type="application/rsd+xml" title="RSD" href="<?= $config['url'] ?>/rsd" /><?php endif; ?> <link rel="stylesheet" href="<?= $config['url'] ?>/microblog.css" /> <script src="<?= $config['url'] ?>/microblog.js" type="module" defer></script> </head> @@ -69,9 +73,9 @@ <footer> <nav> <ul> - <li><a href="<?= $config['url'] ?>/feed/feed.xml">ATOM Feed</a></li> - <li><a href="<?= $config['url'] ?>/feed/feed.json">JSON Feed</a></li> - <?php if($config['xmlrpc']): ?><li><a href="<?= $config['url'] ?>/xmlrpc.php">XML-RPC</a></li><?php endif; ?> + <li><a href="<?= $config['url'] ?>/feed/atom">ATOM Feed</a></li> + <li><a href="<?= $config['url'] ?>/feed/json">JSON Feed</a></li> + <?php if($config['xmlrpc']): ?><li><a href="<?= $config['url'] ?>/xmlrpc">XML-RPC</a></li><?php endif; ?> </ul> </nav> </footer> diff --git a/rsd.xml.php b/rsd.xml.php index 17b8370..910e6b2 100644 --- a/rsd.xml.php +++ b/rsd.xml.php @@ -8,8 +8,8 @@ <engineLink>https://github.com/oelna/microblog</engineLink> <homePageLink><?= $config['url'] ?></homePageLink> <apis> - <api name="MetaWeblog" blogID="1" preferred="true" apiLink="<?= $config['url'] ?>/xmlrpc.php"/> - <!--<api name="Micro.blog" blogID="1" preferred="false" apiLink="<?= $config['url'] ?>/xmlrpc.php"/>--> + <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/single.inc.php b/single.inc.php index ce41e1e..7c91686 100644 --- a/single.inc.php +++ b/single.inc.php @@ -43,10 +43,12 @@ ?><!DOCTYPE html> <html lang="<?= $config['language'] ?>" class="post"> <head> + <meta charset="utf-8" /> <title><?= empty($config['microblog_account']) ? "" : $config['microblog_account'] . "'s " ?>micro.blog - entry #<?= $id ?></title> <meta name="viewport" content="width=device-width" /> - <link rel="alternate" type="application/json" title="JSON Feed" href="<?= $config['url'] ?>/feed/feed.json" /> - <link rel="alternate" type="application/atom+xml" title="Atom Feed" href="<?= $config['url'] ?>/feed/feed.xml" /> + <link rel="alternate" type="application/json" title="JSON Feed" href="<?= $config['url'] ?>/feed/json" /> + <link rel="alternate" type="application/atom+xml" title="Atom Feed" href="<?= $config['url'] ?>/feed/atom" /> + <?php if($config['xmlrpc']): ?><link rel="EditURI" type="application/rsd+xml" title="RSD" href="<?= $config['url'] ?>/rsd" /><?php endif; ?> <link rel="stylesheet" href="<?= $config['url'] ?>/microblog.css" /> <script src="<?= $config['url'] ?>/microblog.js" type="module" defer></script> </head> @@ -106,9 +108,9 @@ <footer> <nav> <ul> - <li><a href="<?= $config['url'] ?>/feed/feed.xml">ATOM Feed</a></li> - <li><a href="<?= $config['url'] ?>/feed/feed.json">JSON Feed</a></li> - <?php if($config['xmlrpc']): ?><li><a href="<?= $config['url'] ?>/xmlrpc.php">XML-RPC</a></li><?php endif; ?> + <li><a href="<?= $config['url'] ?>/feed/atom">ATOM Feed</a></li> + <li><a href="<?= $config['url'] ?>/feed/json">JSON Feed</a></li> + <?php if($config['xmlrpc']): ?><li><a href="<?= $config['url'] ?>/xmlrpc">XML-RPC</a></li><?php endif; ?> </ul> </nav> </footer> diff --git a/timeline.inc.php b/timeline.inc.php index ef58293..3b419b2 100644 --- a/timeline.inc.php +++ b/timeline.inc.php @@ -13,18 +13,20 @@ $current_page = (path(0) == 'page' && is_numeric(path(1))) ? (int) path(1) : 1; $posts_count = db_posts_count(); $total_pages = ceil($posts_count / $config['posts_per_page']); + $offset = ($current_page-1)*$config['posts_per_page']; // get posts - $posts = db_select_posts(NOW, $config['posts_per_page'], 'desc', $current_page); + $posts = db_select_posts(NOW, $config['posts_per_page'], 'desc', $offset); ?><!DOCTYPE html> <html lang="<?= $config['language'] ?>" class="timeline"> <head> + <meta charset="utf-8" /> <title><?= empty($config['microblog_account']) ? "" : $config['microblog_account'] . "'s "; ?>micro.blog</title> <meta name="viewport" content="width=device-width" /> - <link rel="alternate" type="application/json" title="JSON Feed" href="<?= $config['url'] ?>/feed/feed.json" /> - <link rel="alternate" type="application/atom+xml" title="Atom Feed" href="<?= $config['url'] ?>/feed/feed.xml" /> - <?php if($config['xmlrpc']): ?><link rel="EditURI" type="application/rsd+xml" title="RSD" href="<?= $config['url'] ?>/rsd.xml.php" /><?php endif; ?> + <link rel="alternate" type="application/json" title="JSON Feed" href="<?= $config['url'] ?>/feed/json" /> + <link rel="alternate" type="application/atom+xml" title="Atom Feed" href="<?= $config['url'] ?>/feed/atom" /> + <?php if($config['xmlrpc']): ?><link rel="EditURI" type="application/rsd+xml" title="RSD" href="<?= $config['url'] ?>/rsd" /><?php endif; ?> <link rel="stylesheet" href="<?= $config['url'] ?>/microblog.css" /> <script src="<?= $config['url'] ?>/microblog.js" type="module" defer></script> </head> @@ -69,9 +71,9 @@ <footer> <nav> <ul> - <li><a href="<?= $config['url'] ?>/feed/feed.xml">ATOM Feed</a></li> - <li><a href="<?= $config['url'] ?>/feed/feed.json">JSON Feed</a></li> - <?php if($config['xmlrpc']): ?><li><a href="<?= $config['url'] ?>/xmlrpc.php">XML-RPC</a></li><?php endif; ?> + <li><a href="<?= $config['url'] ?>/feed/atom">ATOM Feed</a></li> + <li><a href="<?= $config['url'] ?>/feed/json">JSON Feed</a></li> + <?php if($config['xmlrpc']): ?><li><a href="<?= $config['url'] ?>/xmlrpc">XML-RPC</a></li><?php endif; ?> </ul> </nav> </footer> @@ -9,6 +9,13 @@ if(empty($request_xml)) { exit('XML-RPC server accepts POST requests only.'); } // load config require_once(__DIR__.DIRECTORY_SEPARATOR.'config.php'); +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; @@ -28,16 +35,17 @@ function say_hello($method_name, $args) { return 'Hello'; } -function mw_make_post($post) { +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']) && $post['post_edited'] > 0) { + 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'; } @@ -46,31 +54,57 @@ function mw_make_post($post) { @xmlrpc_set_type($date_modified, 'datetime'); @xmlrpc_set_type($date_modified_gmt, 'datetime'); - // 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. - ]; + 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 $mw_post; + 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_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 [ @@ -80,9 +114,27 @@ function mw_get_recent_posts($method_name, $args) { } if(!$amount) $amount = 25; - - $posts = db_select_posts(time()+10, $amount); - $mw_posts = array_map('mw_make_post', $posts); + $amount = min($amount, 200); // cap the max available posts at 200 (?) + + $posts = db_select_posts(null, $amount, 'asc', $offset); + var_dump($posts); + if(empty($posts)) return []; + + if($method_name == 'microblog.getPosts') { + // currently the same as metaWeblog + /* + $mw_posts = array_map( + function($posts) { + return make_post($posts, 'microblog'); + }, + $posts + ); + */ + // https://stackoverflow.com/a/22735187/3625228 + $mw_posts = array_map('make_post', $posts, array_fill(0, count($posts), $method_name)); + } else { + $mw_posts = array_map('make_post', $posts); + } return $mw_posts; } @@ -100,7 +152,12 @@ function mw_get_post($method_name, $args) { $post = db_select_post($post_id); if($post) { - $mw_post = mw_make_post($post); + if($method_name == 'microblog.getPost') { + $mw_post = make_post($post, $method_name); + } else { + $mw_post = make_post($post); + } + return $mw_post; } else { return [ @@ -112,10 +169,11 @@ function mw_get_post($method_name, $args) { function mw_delete_post($method_name, $args) { - if($method_name == 'blogger.deletePost') { - list($_, $post_id, $username, $password, $_) = $args; - } else { + 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)) { @@ -150,17 +208,32 @@ function mw_new_post($method_name, $args) { ]; } - $post = [ - // 'post_hp' => $content['flNotOnHomePage'], - 'post_timestamp' => time(), - // 'post_title' => $content['title'], - 'post_content' => $content['description'], - // 'post_url' => $content['link'], - ]; + 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['dateCreated'])) { - $post['post_timestamp'] = $content['dateCreated']->timestamp; + // use a specific timestamp, if provided + if(isset($content['date_created'])) { + $post['post_timestamp'] = $content['date_created']->timestamp; + } + } 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; + } } $insert_id = db_insert($post['post_content'], $post['post_timestamp']); @@ -182,7 +255,7 @@ function mw_new_post($method_name, $args) { function mw_edit_post($method_name, $args) { - // post_id, unknown, unknown, array of post content, unknown + // post_id, unknown, unknown, array of post content list($post_id, $username, $password, $content, $_) = $args; if(!check_credentials($username, $password)) { @@ -192,13 +265,31 @@ function mw_edit_post($method_name, $args) { ]; } - $post = [ - // 'post_hp' => $content['flNotOnHomePage'], - 'post_timestamp' => $content['dateCreated']->timestamp, - // 'post_title' => $content['title'], - 'post_content' => $content['description'], - // 'post_url' => $content['link'], - ]; + 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; + } + } $update = db_update($post_id, $post['post_content'], $post['post_timestamp']); if($update && $update > 0) { @@ -226,15 +317,24 @@ function mw_get_categories($method_name, $args) { } // we don't support categories, so only return a fake one - $categories = [ - [ - 'description' => 'Default', - 'htmlUrl' => '', - 'rssUrl' => '', - 'title' => 'default', - 'categoryid' => '1', - ] - ]; + if($method_name == 'microblog.getCategories') { + $categories = [ + [ + 'id' => '1', + 'name' => 'default', + ] + ]; + } else { + $categories = [ + [ + 'description' => 'Default', + 'htmlUrl' => '', + 'rssUrl' => '', + 'title' => 'default', + 'categoryid' => '1', + ] + ]; + } return $categories; } @@ -284,6 +384,20 @@ function mw_get_user_info($method_name, $args) { return $userinfo; } +// micro.blog API methods +// https://help.micro.blog/t/micro-blog-xml-rpc-api/108 + +// currently, the functions can be reused directly (disabled) +/* +use function mw_get_recent_posts as mb_get_posts; +use function mw_get_post as mb_get_post; +use function mw_delete_post as mb_delete_post; +use function mw_get_categories as mb_get_categories; +use function mw_new_post as mb_new_post; +use function mw_edit_post as mb_edit_post; +*/ +// use function mw_new_mediaobject as mb_new_mediaobject; // unsupported + // 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 @@ -293,12 +407,31 @@ xmlrpc_server_register_method($server, 'blogger.getUsersBlogs', 'mw_get_users_bl xmlrpc_server_register_method($server, 'blogger.getUserInfo', 'mw_get_user_info'); 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.getPosts', 'mw_get_recent_posts'); // convenience 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, 'blogger.deletePost', 'mw_delete_post'); xmlrpc_server_register_method($server, 'metaWeblog.deletePost', 'mw_delete_post'); // does this exist? -// xmlrpc_server_register_method($server, 'metaWeblog.newMediaObject', 'say_hello'); // todo +// xmlrpc_server_register_method($server, 'metaWeblog.newMediaObject', 'mw_new_mediaobject'); // todo + +// micro.blog methods +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.getCategories', 'mw_get_categories'); +// xmlrpc_server_register_method($server, 'microblog.newMediaObject', 'mb_new_mediaobject'); + +/* +// 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'); +*/ // https://docstore.mik.ua/orelly/webprog/pcook/ch12_08.htm $response = xmlrpc_server_call_method($server, $request_xml, null, [ @@ -308,5 +441,6 @@ $response = xmlrpc_server_call_method($server, $request_xml, null, [ if($response) { header('Content-Type: text/xml; charset=utf-8'); + // error_log($request_xml."\n\n".$response."\n", 3, __DIR__.'/log.txt'); echo($response); } |