- URL cleanup: automatic removal of `?utm_source=...`, `fb=...`
- discreet pop-up notification when a new release is available
+### REST API
+
+Easily extensible by any client using the REST API exposed by Shaarli.
+
+See the [API documentation](http://shaarli.github.io/api-documentation/).
+
### Other usages
Though Shaarli is primarily a bookmarking application, it can serve other purposes
(see [usage examples](https://github.com/shaarli/Shaarli/wiki#usage-examples)):
* The file will be created if it doesn't exist.
*
* @param string $file File path.
- * @param string $content Content to write.
+ * @param mixed $content Content to write.
*
* @return int|bool Number of bytes written or false if it fails.
*
$item = [
'event' => $status,
- 'datetime' => (new DateTime())->format(DateTime::ATOM),
+ 'datetime' => new DateTime(),
'id' => $id !== null ? $id : '',
];
$this->history = array_merge([$item], $this->history);
{
$comparaison = new DateTime('-'. $this->retentionTime . ' seconds');
foreach ($this->history as $key => $value) {
- if (DateTime::createFromFormat(DateTime::ATOM, $value['datetime']) < $comparaison) {
+ if ($value['datetime'] < $comparaison) {
unset($this->history[$key]);
}
}
$this->conf->write($this->isLoggedIn);
return true;
}
+
+ /**
+ * Reset history store file due to date format change.
+ */
+ public function updateMethodResetHistoryFile()
+ {
+ if (is_file($this->conf->get('resource.history'))) {
+ unlink($this->conf->get('resource.history'));
+ }
+ return true;
+ }
}
/**
use Shaarli\Api\Exceptions\ApiException;
use Shaarli\Api\Exceptions\ApiAuthorizationException;
+use Shaarli\Config\ConfigManager;
use Slim\Container;
use Slim\Http\Request;
use Slim\Http\Response;
protected $container;
/**
- * @var \ConfigManager instance.
+ * @var ConfigManager instance.
*/
protected $conf;
*
* FIXME! LinkDB could use a refactoring to avoid this trick.
*
- * @param \ConfigManager $conf instance.
+ * @param ConfigManager $conf instance.
*/
protected function setLinkDb($conf)
{
namespace Shaarli\Api\Controllers;
+use Shaarli\Config\ConfigManager;
use \Slim\Container;
/**
protected $ci;
/**
- * @var \ConfigManager
+ * @var ConfigManager
*/
protected $conf;
*/
protected $linkDb;
+ /**
+ * @var \History
+ */
+ protected $history;
+
/**
* @var int|null JSON style option.
*/
$this->ci = $ci;
$this->conf = $ci->get('conf');
$this->linkDb = $ci->get('db');
+ $this->history = $ci->get('history');
if ($this->conf->get('dev.debug', false)) {
$this->jsonStyle = JSON_PRETTY_PRINT;
} else {
*/
public function getHistory($request, $response)
{
- $history = (new \History($this->conf->get('resource.history')))->getHistory();
- $history = array_reverse($history);
+ $history = $this->history->getHistory();
// Return history operations from the {offset}th, starting from {since}.
$since = \DateTime::createFromFormat(\DateTime::ATOM, $request->getParam('since'));
$this->linkDb[$link['id']] = $link;
$this->linkDb->save($this->conf->get('resource.page_cache'));
+ $this->history->addLink($link);
$out = ApiUtils::formatLink($link, index_url($this->ci['environment']));
$redirect = $this->ci->router->relativePathFor('getLink', ['id' => $link['id']]);
return $response->withAddedHeader('Location', $redirect)
$responseLink = ApiUtils::updateLink($responseLink, $requestLink);
$this->linkDb[$responseLink['id']] = $responseLink;
$this->linkDb->save($this->conf->get('resource.page_cache'));
+ $this->history->updateLink($responseLink);
$out = ApiUtils::formatLink($responseLink, $index);
return $response->withJson($out, 200, $this->jsonStyle);
if (! isset($this->linkDb[$args['id']])) {
throw new ApiLinkNotFoundException();
}
-
+ $link = $this->linkDb[$args['id']];
unset($this->linkDb[(int) $args['id']]);
$this->linkDb->save($this->conf->get('resource.page_cache'));
+ $this->history->deleteLink($link);
return $response->withStatus(204);
}
* @param PluginManager $pluginManager Plugin Manager instance,
* @param LinkDB $LINKSDB
*/
-function renderPage($conf, $pluginManager, $LINKSDB)
+function renderPage($conf, $pluginManager, $LINKSDB, $history)
{
$updater = new Updater(
read_updates_file($conf->get('resource.updates')),
die($e->getMessage());
}
- try {
- $history = new History($conf->get('resource.history'));
- } catch(Exception $e) {
- die($e->getMessage());
- }
-
$PAGE = new PageBuilder($conf);
$PAGE->assign('linkcount', count($LINKSDB));
$PAGE->assign('privateLinkcount', count_private($LINKSDB));
$PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false));
$PAGE->assign('api_enabled', $conf->get('api.enabled', true));
$PAGE->assign('api_secret', $conf->get('api.secret'));
- $history->updateSettings();
$PAGE->renderPage('configure');
exit;
}
// Plugin administration form action
if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) {
- $history->updateSettings();
try {
if (isset($_POST['parameters_form'])) {
unset($_POST['parameters_form']);
$conf->set('general.enabled_plugins', save_plugin_config($_POST));
}
$conf->write(isLoggedIn());
+ $history->updateSettings();
}
catch (Exception $e) {
error_log(
$conf->get('redirector.encode_url')
);
+try {
+ $history = new History($conf->get('resource.history'));
+} catch(Exception $e) {
+ die($e->getMessage());
+}
+
$container = new \Slim\Container();
$container['conf'] = $conf;
$container['plugins'] = $pluginManager;
+$container['history'] = $history;
$app = new \Slim\App($container);
// REST API routes
if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) {
// We use UTF-8 for proper international characters handling.
header('Content-Type: text/html; charset=utf-8');
- renderPage($conf, $pluginManager, $linkDb);
+ renderPage($conf, $pluginManager, $linkDb, $history);
} else {
$app->respond($response);
}
$history->addLink(['id' => 0]);
$actual = $history->getHistory()[0];
$this->assertEquals(History::CREATED, $actual['event']);
- $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']);
$this->assertEquals(0, $actual['id']);
$history = new History(self::$historyFilePath);
$history->addLink(['id' => 1]);
$actual = $history->getHistory()[0];
$this->assertEquals(History::CREATED, $actual['event']);
- $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']);
$this->assertEquals(1, $actual['id']);
$history = new History(self::$historyFilePath);
$history->addLink(['id' => 'str']);
$actual = $history->getHistory()[0];
$this->assertEquals(History::CREATED, $actual['event']);
- $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']);
$this->assertEquals('str', $actual['id']);
}
$history->updateLink(['id' => 1]);
$actual = $history->getHistory()[0];
$this->assertEquals(History::UPDATED, $actual['event']);
- $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']);
$this->assertEquals(1, $actual['id']);
}
$history->deleteLink(['id' => 1]);
$actual = $history->getHistory()[0];
$this->assertEquals(History::DELETED, $actual['event']);
- $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']);
$this->assertEquals(1, $actual['id']);
}
$history->updateSettings();
$actual = $history->getHistory()[0];
$this->assertEquals(History::SETTINGS, $actual['event']);
- $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']);
$this->assertEmpty($actual['id']);
}
$history->updateLink(['id' => 1]);
$actual = $history->getHistory()[0];
$this->assertEquals(History::UPDATED, $actual['event']);
- $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']);
$this->assertEquals(1, $actual['id']);
$history->addLink(['id' => 1]);
$actual = $history->getHistory()[0];
$this->assertEquals(History::CREATED, $actual['event']);
- $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']);
$this->assertEquals(1, $actual['id']);
}
$history = new History(self::$historyFilePath);
$actual = $history->getHistory()[0];
$this->assertEquals(History::UPDATED, $actual['event']);
- $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']);
$this->assertEquals(1, $actual['id']);
}
$history = new History(self::$historyFilePath);
$actual = $history->getHistory()[0];
$this->assertEquals(History::CREATED, $actual['event']);
- $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']);
$this->assertEquals(1, $actual['id']);
$actual = $history->getHistory()[1];
$this->assertEquals(History::UPDATED, $actual['event']);
- $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']);
$this->assertEquals(1, $actual['id']);
}
$history->updateLink(['id' => 1]);
$this->assertEquals(1, count($history->getHistory()));
$arr = $history->getHistory();
- $arr[0]['datetime'] = (new DateTime('-1 hour'))->format(DateTime::ATOM);
+ $arr[0]['datetime'] = new DateTime('-1 hour');
FileUtils::writeFlatDB(self::$historyFilePath, $arr);
$history = new History(self::$historyFilePath, 60);
$this->assertEquals($nbLinks, count($history));
foreach ($history as $value) {
$this->assertEquals(History::CREATED, $value['event']);
- $this->assertTrue(new DateTime('-5 seconds') < DateTime::createFromFormat(DateTime::ATOM, $value['datetime']));
+ $this->assertTrue(new DateTime('-5 seconds') < $value['datetime']);
$this->assertTrue(is_int($value['id']));
}
$this->assertEquals($nbLinks * 2, count($history));
for ($i = 0 ; $i < $nbLinks ; $i++) {
$this->assertEquals(History::UPDATED, $history[$i]['event']);
- $this->assertTrue(new DateTime('-5 seconds') < DateTime::createFromFormat(DateTime::ATOM, $history[$i]['datetime']));
+ $this->assertTrue(new DateTime('-5 seconds') < $history[$i]['datetime']);
$this->assertTrue(is_int($history[$i]['id']));
}
}
*/
protected static $testDatastore = 'sandbox/datastore.php';
+ /**
+ * @var string datastore to test write operations
+ */
+ protected static $testHistory = 'sandbox/history.php';
+
/**
* @var ConfigManager instance
*/
*/
protected $linkDB;
+ /**
+ * @var \History instance.
+ */
+ protected $history;
+
/**
* @var Container instance.
*/
$this->refDB = new \ReferenceLinkDB();
$this->refDB->write(self::$testDatastore);
$this->linkDB = new \LinkDB(self::$testDatastore, true, false);
+ $refHistory = new \ReferenceHistory();
+ $refHistory->write(self::$testHistory);
+ $this->history = new \History(self::$testHistory);
$this->container = new Container();
$this->container['conf'] = $this->conf;
$this->container['db'] = $this->linkDB;
+ $this->container['history'] = $this->history;
$this->controller = new Links($this->container);
}
public function tearDown()
{
@unlink(self::$testDatastore);
+ @unlink(self::$testHistory);
}
/**
$this->linkDB = new \LinkDB(self::$testDatastore, true, false);
$this->assertFalse(isset($this->linkDB[$id]));
+
+ $historyEntry = $this->history->getHistory()[0];
+ $this->assertEquals(\History::DELETED, $historyEntry['event']);
+ $this->assertTrue(
+ (new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime']
+ );
+ $this->assertEquals($id, $historyEntry['id']);
}
/**
$this->container = new Container();
$this->container['conf'] = $this->conf;
$this->container['db'] = new \LinkDB(self::$testDatastore, true, false);
+ $this->container['history'] = null;
$this->controller = new Links($this->container);
}
$this->container = new Container();
$this->container['conf'] = $this->conf;
$this->container['db'] = new \LinkDB(self::$testDatastore, true, false);
+ $this->container['history'] = null;
$this->controller = new Links($this->container);
}
*/
protected $refHistory = null;
- /**
- * @var \History instance.
- */
- protected $history;
-
/**
* @var Container instance.
*/
$this->conf = new ConfigManager('tests/utils/config/configJson.json.php');
$this->refHistory = new \ReferenceHistory();
$this->refHistory->write(self::$testHistory);
- $this->conf->set('resource.history', self::$testHistory);
$this->container = new Container();
$this->container['conf'] = $this->conf;
$this->container['db'] = true;
+ $this->container['history'] = new \History(self::$testHistory);
$this->controller = new History($this->container);
}
$this->container = new Container();
$this->container['conf'] = $this->conf;
$this->container['db'] = new \LinkDB(self::$testDatastore, true, false);
+ $this->container['history'] = null;
$this->controller = new Info($this->container);
}
*/
protected static $testDatastore = 'sandbox/datastore.php';
+ /**
+ * @var string datastore to test write operations
+ */
+ protected static $testHistory = 'sandbox/history.php';
+
/**
* @var ConfigManager instance
*/
*/
protected $refDB = null;
+ /**
+ * @var \History instance.
+ */
+ protected $history;
+
/**
* @var Container instance.
*/
$this->refDB = new \ReferenceLinkDB();
$this->refDB->write(self::$testDatastore);
+ $refHistory = new \ReferenceHistory();
+ $refHistory->write(self::$testHistory);
+ $this->history = new \History(self::$testHistory);
+
$this->container = new Container();
$this->container['conf'] = $this->conf;
$this->container['db'] = new \LinkDB(self::$testDatastore, true, false);
+ $this->container['history'] = new \History(self::$testHistory);
$this->controller = new Links($this->container);
public function tearDown()
{
@unlink(self::$testDatastore);
+ @unlink(self::$testHistory);
}
/**
$this->assertEquals(false, $data['private']);
$this->assertTrue(new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created']));
$this->assertEquals('', $data['updated']);
+
+ $historyEntry = $this->history->getHistory()[0];
+ $this->assertEquals(\History::CREATED, $historyEntry['event']);
+ $this->assertTrue(
+ (new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime']
+ );
+ $this->assertEquals(43, $historyEntry['id']);
}
/**
*/
protected static $testDatastore = 'sandbox/datastore.php';
+ /**
+ * @var string datastore to test write operations
+ */
+ protected static $testHistory = 'sandbox/history.php';
+
/**
* @var ConfigManager instance
*/
*/
protected $refDB = null;
+ /**
+ * @var \History instance.
+ */
+ protected $history;
+
/**
* @var Container instance.
*/
$this->refDB = new \ReferenceLinkDB();
$this->refDB->write(self::$testDatastore);
+ $refHistory = new \ReferenceHistory();
+ $refHistory->write(self::$testHistory);
+ $this->history = new \History(self::$testHistory);
+
$this->container = new Container();
$this->container['conf'] = $this->conf;
$this->container['db'] = new \LinkDB(self::$testDatastore, true, false);
+ $this->container['history'] = new \History(self::$testHistory);
$this->controller = new Links($this->container);
public function tearDown()
{
@unlink(self::$testDatastore);
+ @unlink(self::$testHistory);
}
/**
\DateTime::createFromFormat(\DateTime::ATOM, $data['created'])
);
$this->assertTrue(new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['updated']));
+
+ $historyEntry = $this->history->getHistory()[0];
+ $this->assertEquals(\History::UPDATED, $historyEntry['event']);
+ $this->assertTrue(
+ (new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime']
+ );
+ $this->assertEquals($id, $historyEntry['id']);
}
/**
public function __construct()
{
$this->addEntry(
- History::CREATED,
- DateTime::createFromFormat('Ymd_His', '20170101_121212'),
- 123
+ History::DELETED,
+ DateTime::createFromFormat('Ymd_His', '20170303_121216'),
+ 124
);
$this->addEntry(
- History::CREATED,
- DateTime::createFromFormat('Ymd_His', '20170201_121214'),
- 124
+ History::SETTINGS,
+ DateTime::createFromFormat('Ymd_His', '20170302_121215')
);
$this->addEntry(
);
$this->addEntry(
- History::SETTINGS,
- DateTime::createFromFormat('Ymd_His', '20170302_121215')
+ History::CREATED,
+ DateTime::createFromFormat('Ymd_His', '20170201_121214'),
+ 124
);
$this->addEntry(
- History::DELETED,
- DateTime::createFromFormat('Ymd_His', '20170303_121216'),
- 124
+ History::CREATED,
+ DateTime::createFromFormat('Ymd_His', '20170101_121212'),
+ 123
);
}