X-Git-Url: https://git.cweiske.de/tt-rss-micropub.git/blobdiff_plain/eb1ac3f3b371181fc454b2265d7183f0db5af9f7..5f7003132e5f65cba182747d6e3e659dd22b1e83:/init.php diff --git a/init.php b/init.php index 986266d..d561aa5 100644 --- a/init.php +++ b/init.php @@ -40,12 +40,12 @@ class Micropub extends Plugin implements IHandler return 2; } + /** * Register our hooks */ public function init(/*PluginHost*/ $host) { - parent::init($host); static::$myhost = $host; $host->add_hook($host::HOOK_PREFS_TAB, $this); $host->add_hook($host::HOOK_RENDER_ARTICLE, $this); @@ -55,11 +55,6 @@ class Micropub extends Plugin implements IHandler ); } - function get_css() - { - return file_get_contents(__DIR__ . '/init.css'); - } - /** * @param array $article Article data. Keys: * - id @@ -88,7 +83,18 @@ class Micropub extends Plugin implements IHandler . '?reply=' . urlencode($article['link']); // did I tell you I hate dojo/dijit? - $accounts = array_keys(PluginHost::getInstance()->get($this, 'accounts', [])); + $accounts = PluginHost::getInstance()->get($this, 'accounts', []); + if (!count($accounts)) { + return $article; + } + + $accountUrls = array_keys($accounts); + $defaultAccount = null; + foreach ($accounts as $url => $account) { + if ($account['default']) { + $defaultAccount = $url; + } + } ob_start(); include __DIR__ . '/commentform.phtml'; @@ -113,10 +119,30 @@ class Micropub extends Plugin implements IHandler } $accounts = PluginHost::getInstance()->get($this, 'accounts', []); + if (isset($_REQUEST['accordion']) + && $_REQUEST['accordion'] == 'micropub' + ) { + $accordionActive = 'selected="true"'; + } else { + $accordionActive = ''; + } + + foreach ($accounts as $url => $account) { + $accounts[$url]['checked'] = ''; + if ($account['default']) { + $accounts[$url]['checked'] = 'checked="checked"'; + } + } + //FIXME: default identity include __DIR__ . '/settings.phtml'; } + public function get_prefs_js() + { + return file_get_contents(__DIR__ . '/settings.js'); + } + /** * CLI command */ @@ -130,6 +156,12 @@ class Micropub extends Plugin implements IHandler return $this->action($mode, $args); } + /** + * HTTP command. + * Also used by micropub() cli command method. + * + * /backend.php?op=pluginhandler&plugin=micropub&method=action + */ public function action($mode = null, $args = []) { if (isset($_POST['mode'])) { @@ -144,34 +176,54 @@ class Micropub extends Plugin implements IHandler return $this->authreturnAction(); } else if ($mode == 'post') { return $this->postAction(); + } else if ($mode == 'deleteIdentity') { + return $this->deleteIdentityAction(); + } else if ($mode == 'setDefaultIdentity') { + return $this->setDefaultIdentityAction(); } else { - $this->errorOut('Unsupported mode'); + return $this->errorOut('Unsupported mode'); } } + /** + * Post a comment, like or bookmark via micropub + */ protected function postAction() { - if (!isset($_POST['me'])) { - return $this->errorOut('"me" parameter missing'); + $action = 'comment'; + if (isset($_POST['action'])) { + $action = trim($_POST['action']); } - $me = $_POST['me']; - - if (!isset($_POST['replyTo'])) { - return $this->errorOut('"replyTo" parameter missing'); + if (array_search($action, ['bookmark', 'comment', 'like']) === false) { + return $this->errorOut('"action" parameter invalid'); } - $replyTo = $_POST['replyTo']; - if (!isset($_POST['content'])) { - return $this->errorOut('"content" parameter missing'); + if (!isset($_POST['me'])) { + return $this->errorOut('"me" parameter missing'); } - $content = $_POST['content']; - + $me = trim($_POST['me']); $accounts = PluginHost::getInstance()->get($this, 'accounts', []); if (!isset($accounts[$me])) { return $this->errorOut('"me" parameter invalid'); } $account = $accounts[$me]; + if (!isset($_POST['postUrl'])) { + return $this->errorOut('"postUrl" parameter missing'); + } + $postUrl = trim($_POST['postUrl']); + + if ($action == 'comment') { + if (!isset($_POST['content'])) { + return $this->errorOut('"content" parameter missing'); + } + $content = trim($_POST['content']); + if (!strlen($_POST['content'])) { + return $this->errorOut('"content" is empty'); + } + } + + $links = $this->getLinks($me); if (!count($links)) { return $this->errorOut('No links found'); @@ -180,35 +232,83 @@ class Micropub extends Plugin implements IHandler return $this->errorOut('No micropub endpoint found'); } - $res = fetch_file_contents( - [ - //FIXME: add content-type header once this is fixed: - // https://discourse.tt-rss.org/t//207 - 'url' => $links['micropub'], - //we use http_build_query to force cURL - // to use x-www-form-urlencoded - 'post_query' => http_build_query( - [ - 'access_token' => $account['access_token'], - 'h' => 'entry', - 'in-reply-to' => $replyTo, - 'content' => $content, - ] - ), - 'followlocation' => false, + $parameters = [ + 'access_token' => $account['access_token'], + 'h' => 'entry', + ]; + + if ($action == 'bookmark') { + $parameters['bookmark-of'] = $postUrl; + + } else if ($action == 'comment') { + $parameters['in-reply-to'] = $postUrl; + $parameters['content'] = $content; + + } else if ($action == 'like') { + $parameters['like-of'] = $postUrl; + } + + + /* unfortunately fetch_file_contents() does not return headers + so we have to bring our own way to POST data */ + $opts = [ + 'http' => [ + 'method' => 'POST', + 'header' => 'Content-type: application/x-www-form-urlencoded', + 'content' => http_build_query($parameters), + 'ignore_errors' => true, ] + ]; + $stream = fopen( + $links['micropub'], 'r', false, + stream_context_create($opts) ); + $meta = stream_get_meta_data($stream); + $headers = $meta['wrapper_data']; + $content = stream_get_contents($stream); + + //we hope there were no redirects and this is actually the only + // HTTP line in the headers + $status = array_shift($headers); + list($httpver, $code, $text) = explode(' ', $status, 3); + if ($code != 201 && $code != 202) { + $errData = json_decode($content); + if (isset($errData->error_description) + && $errData->error_description != '' + ) { + return $this->errorOut( + 'Error creating post: ' + . $errData->error_description + ); + } + return $this->errorOut( + 'Error creating post: ' + . $code . ' ' . $text.$content + ); + } - if ($GLOBALS['fetch_last_error_code'] == 201) { - //FIXME: extract location header - echo "OK, comment post created\n"; - } else { - $this->errorOut( - 'An error occured: ' - . $GLOBALS['fetch_last_error_code'] - . ' ' . $GLOBALS['fetch_last_error_code_content'] + $location = null; + foreach ($headers as $header) { + $parts = explode(':', $header, 2); + if (count($parts) == 2 && strtolower($parts[0]) == 'location') { + $location = trim($parts[1]); + } + } + if ($location === null) { + return $this->errorOut( + 'Location header missing in successful creation response.' ); } + + header('Content-type: application/json'); + echo json_encode( + [ + 'code' => intval($code), + 'location' => $location, + 'message' => 'Post created', + ] + ); + exit(); } protected function authorizeAction($args = []) @@ -310,18 +410,136 @@ class Micropub extends Plugin implements IHandler 'access_token' => $data['access_token'], 'scope' => $data['scope'], ]; + $accounts = $this->fixDefaultIdentity($accounts); $host->set($this, 'accounts', $accounts); //all fine now. - header('Location: prefs.php'); + //the accordion parameter will never work + // because fox has serious mental problems + // https://discourse.tt-rss.org/t/open-a-certain-accordion-in-preferences-by-url-parameter/234 + header('Location: prefs.php?accordion=micropub'); } + /** + * Backend preferences action: Remove a given account + */ + protected function deleteIdentityAction() + { + if (!isset($_POST['me'])) { + return $this->errorOut('"me" parameter missing'); + } + $me = trim($_POST['me']); + + $host = PluginHost::getInstance(); + $accounts = $host->get($this, 'accounts', []); + if (!isset($accounts[$me])) { + return $this->errorOut('Unknown identity'); + } + + unset($accounts[$me]); + $accounts = $this->fixDefaultIdentity($accounts); + $host->set($this, 'accounts', $accounts); + + header('Content-type: application/json'); + echo json_encode( + [ + 'code' => '200', + 'message' => 'Identity removed', + ] + ); + exit(); + } + + /** + * Backend preferences action: Make a given account the default + */ + protected function setDefaultIdentityAction() + { + if (!isset($_POST['me'])) { + return $this->errorOut('"me" parameter missing'); + } + $me = trim($_POST['me']); + + $host = PluginHost::getInstance(); + $accounts = $host->get($this, 'accounts', []); + if (!isset($accounts[$me])) { + return $this->errorOut('Unknown identity'); + } + foreach ($accounts as $url => $data) { + $accounts[$url]['default'] = ($url == $me); + } + $host->set($this, 'accounts', $accounts); + + header('Content-type: application/json'); + echo json_encode( + [ + 'code' => '200', + 'message' => 'Default account set', + ] + ); + exit(); + } + + /** + * Set the default identity if there is none + * + * @param array $accounts Array of account data arrays + * + * @return array Array of account data arrays + */ + protected function fixDefaultIdentity($accounts) + { + if (!count($accounts)) { + return $accounts; + } + + $hasDefault = false; + foreach ($accounts as $account) { + if ($account['default']) { + $hasDefault = true; + } + } + + if (!$hasDefault) { + reset($accounts); + $accounts[key($accounts)]['default'] = true; + } + return $accounts; + } + + /** + * Send an error message. + * Automatically in the correct format (plain text or json) + * + * @param string $msg Error message + * + * @return void + */ protected function errorOut($msg) { - echo $msg . "\n"; + header('HTTP/1.0 400 Bad Request'); + + //this does not take "q"uality values into account, I know. + if (isset($_SERVER['HTTP_ACCEPT']) + && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false + ) { + //send json error + header('Content-type: application/json'); + echo json_encode( + [ + 'error' => $msg, + ] + ); + } else { + header('Content-type: text/plain'); + echo $msg . "\n"; + } exit(1); } + /** + * Extract link relations from a given URL + */ protected function getLinks($url) { //FIXME: HTTP Link header support with HTTP2 @@ -349,9 +567,27 @@ class Micropub extends Plugin implements IHandler return $links; } + /** + * If a valid CSRF token is necessary or not + * + * @param string $method Plugin method name (here: "action") + * + * @return boolean True if an invalid CSRF token shall be ignored + */ function csrf_ignore($method) { - return true; + $mode = null; + if (isset($_POST['mode'])) { + $mode = $_POST['mode']; + } else if (isset($_GET['mode'])) { + $mode = $_GET['mode']; + } + + if ($mode == 'authreturn') { + return true; + } + + return false; } /**