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);
);
}
- function get_css()
- {
- return file_get_contents(__DIR__ . '/init.css');
- }
-
/**
* @param array $article Article data. Keys:
* - id
. '?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';
}
$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
*/
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'])) {
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');
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 = [])
'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
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;
}
/**