public static function checkResourcePermissions($conf)
{
$errors = array();
+ $rainTplDir = rtrim($conf->get('resource.raintpl_tpl'), '/');
// Check script and template directories are readable
foreach (array(
'application',
'inc',
'plugins',
- $conf->get('resource.raintpl_tpl'),
- $conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme'),
+ $rainTplDir,
+ $rainTplDir.'/'.$conf->get('resource.theme'),
) as $path) {
if (! is_readable(realpath($path))) {
$errors[] = '"'.$path.'" directory is not readable';
return $errors;
}
+
+ /**
+ * Returns a salted hash representing the current Shaarli version.
+ *
+ * Useful for assets browser cache.
+ *
+ * @param string $currentVersion of Shaarli
+ * @param string $salt User personal salt, also used for the authentication
+ *
+ * @return string version hash
+ */
+ public static function getVersionHash($currentVersion, $salt)
+ {
+ return hash_hmac('sha256', $currentVersion, $salt);
+ }
}
/**
* Read data from a file containing Shaarli database format content.
- * If the file isn't readable or doesn't exists, default data will be returned.
+ *
+ * If the file isn't readable or doesn't exist, default data will be returned.
*
* @param string $file File path.
* @param mixed $default The default value to return if the file isn't readable.
{
// Note that gzinflate is faster than gzuncompress.
// See: http://www.php.net/manual/en/function.gzdeflate.php#96439
- if (is_readable($file)) {
- return unserialize(
- gzinflate(
- base64_decode(
- substr(file_get_contents($file), strlen(self::$phpPrefix), -strlen(self::$phpSuffix))
- )
- )
- );
+ if (! is_readable($file)) {
+ return $default;
+ }
+
+ $data = file_get_contents($file);
+ if ($data == '') {
+ return $default;
}
- return $default;
+ return unserialize(
+ gzinflate(
+ base64_decode(
+ substr($data, strlen(self::$phpPrefix), -strlen(self::$phpSuffix))
+ )
+ )
+ );
}
}
*/
function text2clickable($text, $redirector = '')
{
- $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[[:alnum:]]/?)!si';
+ $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[a-z0-9\(\)]/?)!si';
if (empty($redirector)) {
return preg_replace($regex, '<a href="$1">$1</a>', $text);
try {
$version = ApplicationUtils::checkUpdate(
- shaarli_version,
+ SHAARLI_VERSION,
$this->conf->get('resource.update_check'),
$this->conf->get('updates.check_updates_interval'),
$this->conf->get('updates.check_updates'),
}
$this->tpl->assign('searchcrits', $searchcrits);
$this->tpl->assign('source', index_url($_SERVER));
- $this->tpl->assign('version', shaarli_version);
+ $this->tpl->assign('version', SHAARLI_VERSION);
+ $this->tpl->assign(
+ 'version_hash',
+ ApplicationUtils::getVersionHash(SHAARLI_VERSION, $this->conf->get('credentials.salt'))
+ );
$this->tpl->assign('scripturl', index_url($_SERVER));
$this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links?
$this->tpl->assign('untaggedonly', !empty($_SESSION['untaggedonly']));
$this->tpl->assign('feed_type', $this->conf->get('feed.show_atom', true) !== false ? 'atom' : 'rss');
$this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false));
$this->tpl->assign('token', getToken($this->conf));
+
if ($this->linkDB !== null) {
$this->tpl->assign('tags', $this->linkDB->linksCountPerTag());
}
*/
public static function getThemes($tplDir)
{
+ $tplDir = rtrim($tplDir, '/');
$allTheme = glob($tplDir.'/*', GLOB_ONLYDIR);
$themes = [];
foreach ($allTheme as $value) {
*/
public function updateMethodCheckUpdateRemoteBranch()
{
- if (shaarli_version === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') {
+ if (SHAARLI_VERSION === 'dev' || $this->conf->get('updates.check_updates_branch') === 'latest') {
return true;
}
$latestMajor = $matches[1];
// Get current major version digit
- preg_match('/(\d+)\.\d+$/', shaarli_version, $matches);
+ preg_match('/(\d+)\.\d+$/', SHAARLI_VERSION, $matches);
$currentMajor = $matches[1];
if ($currentMajor === $latestMajor) {
$this->setEmpty('general.header_link', '?');
$this->setEmpty('general.links_per_page', 20);
$this->setEmpty('general.enabled_plugins', self::$DEFAULT_PLUGINS);
+ $this->setEmpty('general.default_note_title', 'Note: ');
$this->setEmpty('updates.check_updates', false);
$this->setEmpty('updates.check_updates_branch', 'stable');
- **links_per_page**: Number of shaares displayed per page.
- **timezone**: See [the list of supported timezones](http://php.net/manual/en/timezones.php).
- **enabled_plugins**: List of enabled plugins.
+- **default_note_title**: Default title of a new note.
### Security
Digest: sha256:c584131da2ac1948aa3e66468a4424b6aea2f33acba7cec0b631bdb56254c4fe
Status: Downloaded newer image for debian:wheezy
```
+
+Docker re-uses layers already downloaded. In other words if you have images based on Alpine or some Ubuntu version for example, those can share disk space.
+
+### Start a container
+A container is an instance created from an image, that can be run and that keeps running until its main process exits. Or until the user stops the container.
+
+The simplest way to start a container from image is ``docker run``. It also pulls the image for you if it is not locally available. For more advanced use, refer to ``docker create``.
+
+Stopped containers are not destroyed, unless you specify ``--rm``. To view all created, running and stopped containers, enter:
+```bash
+$ docker ps -a
+```
+
+Some containers may be designed or configured to be restarted, others are not. Also remember both network ports and volumes of a container are created on start, and not editable later.
+
+### Access a running container
+A running container is accessible using ``docker exec``, or ``docker copy``. You can use ``exec`` to start a root shell in the Shaarli container:
+```bash
+$ docker exec -ti <container-name-or-id> bash
+```
+Note the names and ID's of containers are listed in ``docker ps``. You can even type only one or two letters of the ID, given they are unique.
+
+Access can also be through one or more network ports, or disk volumes. Both are specified on and fixed on ``docker create`` or ``run``.
+
+You can view the console output of the main container process too:
+```bash
+$ docker logs -f <container-name-or-id>
+```
+
+### Docker disk use
+Trying out different images can fill some gigabytes of disk quickly. Besides images, the docker volumes usually take up most disk space.
+
+If you care only about trying out docker and not about what is running or saved, the following commands should help you out quickly if you run low on disk space:
+
+```bash
+$ docker rmi -f $(docker images -aq) # remove or mark all images for disposal
+$ docker volume rm $(docker volume ls -q) # remove all volumes
+```
+
+### Systemd config
+Systemd is the process manager of choice on Debian-based distributions. Once you have a ``docker`` service installed, you can use the following steps to set up Shaarli to run on system start.
+
+```bash
+systemctl enable /etc/systemd/system/docker.shaarli.service
+systemctl start docker.shaarli
+systemctl status docker.*
+journalctl -f # inspect system log if needed
+```
+
+You will need sudo or a root terminal to perform some or all of the steps above. Here are the contents for the service file:
+```
+[Unit]
+Description=Shaarli Bookmark Manager Container
+After=docker.service
+Requires=docker.service
+
+
+[Service]
+Restart=always
+
+# Put any environment you want in an included file, like $host- or $domainname in this example
+EnvironmentFile=/etc/sysconfig/box-environment
+
+# It's just an example..
+ExecStart=/usr/bin/docker run \
+ -p 28010:80 \
+ --name ${hostname}-shaarli \
+ --hostname shaarli.${domainname} \
+ -v /srv/docker-volumes-local/shaarli-data:/var/www/shaarli/data:rw \
+ -v /etc/localtime:/etc/localtime:ro \
+ shaarli/shaarli:latest
+
+ExecStop=/usr/bin/docker rm -f ${hostname}-shaarli
+
+
+[Install]
+WantedBy=multi-user.target
+```
Login: `demo`; Password: `demo`
+Docker users can start a personal instance from an [autobuild image](https://hub.docker.com/r/shaarli/shaarli/). For example to start a temporary Shaarli at ``localhost:8000``, and keep session data (config, storage):
+```
+MY_SHAARLI_VOLUME=$(cd /path/to/shaarli/data/ && pwd -P)
+docker run -ti --rm \
+ -p 8000:80 \
+ -v $MY_SHAARLI_VOLUME:/var/www/shaarli/data \
+ shaarli/shaarli
+```
+
+A brief guide on getting starting using docker is given in [Docker 101](docker/docker-101).
+To learn more about user data and how to keep it across versions, please see [Upgrade and Migration](Upgrade-and-migration) documentation.
## Features
exit;
}
-define('shaarli_version', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE));
+define('SHAARLI_VERSION', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE));
// Force cookie path (but do not change lifetime)
$cookie = session_get_cookie_params();
if ($url == '') {
$url = '?' . smallHash($linkdate . $LINKSDB->getNextId());
- $title = 'Note: ';
+ $title = $conf->get('general.default_note_title', 'Note: ');
}
$url = escape($url);
$title = escape($title);
$expectedText = 'stuff <a href="http://hello.there/is=someone#here">http://hello.there/is=someone#here</a> otherstuff';
$processedText = text2clickable($text, '');
$this->assertEquals($expectedText, $processedText);
+
+ $text = 'stuff http://hello.there/is=someone#here(please) otherstuff';
+ $expectedText = 'stuff <a href="http://hello.there/is=someone#here(please)">http://hello.there/is=someone#here(please)</a> otherstuff';
+ $processedText = text2clickable($text, '');
+ $this->assertEquals($expectedText, $processedText);
+
+ $text = 'stuff http://hello.there/is=someone#here(please)&no otherstuff';
+ $expectedText = 'stuff <a href="http://hello.there/is=someone#here(please)&no">http://hello.there/is=someone#here(please)&no</a> otherstuff';
+ $processedText = text2clickable($text, '');
+ $this->assertEquals($expectedText, $processedText);
}
/**
<link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" />
<link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" />
<link href="img/favicon.png" rel="shortcut icon" type="image/png" />
-<link type="text/css" rel="stylesheet" href="css/pure.min.css" />
-<link type="text/css" rel="stylesheet" href="css/grids-responsive.min.css">
-<link type="text/css" rel="stylesheet" href="css/pure-extras.css">
-<link type="text/css" rel="stylesheet" href="css/font-awesome.min.css" />
-<link type="text/css" rel="stylesheet" href="inc/awesomplete.css#" />
-<link type="text/css" rel="stylesheet" href="css/shaarli.css" />
+<link type="text/css" rel="stylesheet" href="css/pure.min.css?v={$version_hash}" />
+<link type="text/css" rel="stylesheet" href="css/grids-responsive.min.css?v={$version_hash}">
+<link type="text/css" rel="stylesheet" href="css/pure-extras.css?v={$version_hash}">
+<link type="text/css" rel="stylesheet" href="css/font-awesome.min.css?v={$version_hash}" />
+<link type="text/css" rel="stylesheet" href="inc/awesomplete.css?v={$version_hash}#" />
+<link type="text/css" rel="stylesheet" href="css/shaarli.css?v={$version_hash}" />
{if="is_file('data/user.css')"}
<link type="text/css" rel="stylesheet" href="data/user.css#" />
{/if}
{loop="$plugins_includes.css_files"}
- <link type="text/css" rel="stylesheet" href="{$value}#"/>
+ <link type="text/css" rel="stylesheet" href="{$value}?v={$version_hash}#"/>
{/loop}
<link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle}"/>
\ No newline at end of file
};
function init () {
function resize () {
+ /* Fix jumpy resizing: https://stackoverflow.com/a/18262927/1484919 */
+ var scrollTop = window.pageYOffset ||
+ (document.documentElement || document.body.parentNode || document.body).scrollTop;
+
description.style.height = 'auto';
description.style.height = description.scrollHeight+10+'px';
+
+ window.scrollTo(0, scrollTop);
}
/* 0-timeout to get the already changed text */
function delayedResize () {
<script src="{$value}#"></script>
{/loop}
-<script src="js/shaarli.js"></script>
-<script src="inc/awesomplete.js#"></script>
-<script src="inc/awesomplete-multiple-tags.js#"></script>
+<script src="js/shaarli.js?v={$version_hash}"></script>
+<script src="inc/awesomplete.js?v={$version_hash}#"></script>
+<script src="inc/awesomplete-multiple-tags.js?v={$version_hash}#"></script>