]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Merge pull request #830 from ArthurHoaro/theme/timezone
authorArthurHoaro <arthur@hoa.ro>
Tue, 25 Apr 2017 17:09:13 +0000 (19:09 +0200)
committerGitHub <noreply@github.com>
Tue, 25 Apr 2017 17:09:13 +0000 (19:09 +0200)
Change timezone data structure send to the templates

31 files changed:
application/LinkDB.php
application/Utils.php
application/api/ApiUtils.php
application/api/controllers/ApiController.php
application/api/controllers/Links.php
index.php
plugins/readityourself/book-open.png [deleted file]
plugins/readityourself/readityourself.html [deleted file]
plugins/readityourself/readityourself.meta [deleted file]
plugins/readityourself/readityourself.php [deleted file]
tests/UtilsTest.php
tests/api/controllers/PostLinkTest.php [new file with mode: 0644]
tests/languages/de/UtilsDeTest.php
tests/languages/en/UtilsEnTest.php
tests/languages/fr/UtilsFrTest.php
tests/plugins/PluginReadityourselfTest.php [deleted file]
tests/utils/ReferenceLinkDB.php
tpl/default/configure.html
tpl/default/css/shaarli.css
tpl/default/daily.html
tpl/default/fonts/Fira-Sans-regular.woff [deleted file]
tpl/default/fonts/Fira-Sans-regular.woff2 [deleted file]
tpl/default/fonts/Roboto-Bold.woff [new file with mode: 0644]
tpl/default/fonts/Roboto-Bold.woff2 [new file with mode: 0644]
tpl/default/fonts/Roboto-Regular.woff [new file with mode: 0644]
tpl/default/fonts/Roboto-Regular.woff2 [new file with mode: 0644]
tpl/default/import.html
tpl/default/install.html
tpl/default/js/shaarli.js
tpl/vintage/configure.html
tpl/vintage/import.html

index 4cee2af9942559fd122189687058ddddc1fc4ebf..1e4d7ce88421aa8871264487ddf329f87e4e2d15 100644 (file)
@@ -144,10 +144,10 @@ class LinkDB implements Iterator, Countable, ArrayAccess
         if (!isset($value['id']) || empty($value['url'])) {
             die('Internal Error: A link should always have an id and URL.');
         }
-        if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) {
+        if (($offset !== null && ! is_int($offset)) || ! is_int($value['id'])) {
             die('You must specify an integer as a key.');
         }
-        if (! empty($offset) && $offset !== $value['id']) {
+        if ($offset !== null && $offset !== $value['id']) {
             die('Array offset and link ID must be equal.');
         }
 
index 5c077450e3a65016f55b0ede559dad4552b47e2b..ab463af9749cf3a597305fa37e1e91dbcf26046f 100644 (file)
@@ -321,25 +321,117 @@ function normalize_spaces($string)
  * otherwise default format '%c' will be returned.
  *
  * @param DateTime $date to format.
+ * @param bool     $time Displays time if true.
  * @param bool     $intl Use international format if true.
  *
  * @return bool|string Formatted date, or false if the input is invalid.
  */
-function format_date($date, $intl = true)
+function format_date($date, $time = true, $intl = true)
 {
     if (! $date instanceof DateTime) {
         return false;
     }
 
     if (! $intl || ! class_exists('IntlDateFormatter')) {
-        return strftime('%c', $date->getTimestamp());
+        $format = $time ? '%c' : '%x';
+        return strftime($format, $date->getTimestamp());
     }
 
     $formatter = new IntlDateFormatter(
         setlocale(LC_TIME, 0),
         IntlDateFormatter::LONG,
-        IntlDateFormatter::LONG
+        $time ? IntlDateFormatter::LONG : IntlDateFormatter::NONE
     );
 
     return $formatter->format($date);
 }
+
+/**
+ * Check if the input is an integer, no matter its real type.
+ *
+ * PHP is a bit messy regarding this:
+ *   - is_int returns false if the input is a string
+ *   - ctype_digit returns false if the input is an integer or negative
+ *
+ * @param mixed $input value
+ *
+ * @return bool true if the input is an integer, false otherwise
+ */
+function is_integer_mixed($input)
+{
+    if (is_array($input) || is_bool($input) || is_object($input)) {
+        return false;
+    }
+    $input = strval($input);
+    return ctype_digit($input) || (startsWith($input, '-') && ctype_digit(substr($input, 1)));
+}
+
+/**
+ * Convert post_max_size/upload_max_filesize (e.g. '16M') parameters to bytes.
+ *
+ * @param string $val Size expressed in string.
+ *
+ * @return int Size expressed in bytes.
+ */
+function return_bytes($val)
+{
+    if (is_integer_mixed($val) || $val === '0' || empty($val)) {
+        return $val;
+    }
+    $val = trim($val);
+    $last = strtolower($val[strlen($val)-1]);
+    $val = intval(substr($val, 0, -1));
+    switch($last) {
+        case 'g': $val *= 1024;
+        case 'm': $val *= 1024;
+        case 'k': $val *= 1024;
+    }
+    return $val;
+}
+
+/**
+ * Return a human readable size from bytes.
+ *
+ * @param int $bytes value
+ *
+ * @return string Human readable size
+ */
+function human_bytes($bytes)
+{
+    if ($bytes === '') {
+        return t('Setting not set');
+    }
+    if (! is_integer_mixed($bytes)) {
+        return $bytes;
+    }
+    $bytes = intval($bytes);
+    if ($bytes === 0) {
+        return t('Unlimited');
+    }
+
+    $units = [t('B'), t('kiB'), t('MiB'), t('GiB')];
+    for ($i = 0; $i < count($units) && $bytes >= 1024; ++$i) {
+        $bytes /= 1024;
+    }
+
+    return round($bytes) . $units[$i];
+}
+
+/**
+ * Try to determine max file size for uploads (POST).
+ * Returns an integer (in bytes) or formatted depending on $format.
+ *
+ * @param mixed $limitPost   post_max_size PHP setting
+ * @param mixed $limitUpload upload_max_filesize PHP setting
+ * @param bool  $format      Format max upload size to human readable size
+ *
+ * @return int|string max upload file size
+ */
+function get_max_upload_size($limitPost, $limitUpload, $format = true)
+{
+    $size1 = return_bytes($limitPost);
+    $size2 = return_bytes($limitUpload);
+    // Return the smaller of two:
+    $maxsize = min($size1, $size2);
+    return $format ? human_bytes($maxsize) : $maxsize;
+}
index d40158652d1f66680cfd5ceea17623b9a8eef5a8..b8155a3442669e452234e8b41a2bfc780befb130 100644 (file)
@@ -12,7 +12,7 @@ class ApiUtils
     /**
      * Validates a JWT token authenticity.
      *
-     * @param string $token  JWT token extracted from the headers.
+     * @param string $token JWT token extracted from the headers.
      * @param string $secret API secret set in the settings.
      *
      * @throws ApiAuthorizationException the token is not valid.
@@ -50,7 +50,7 @@ class ApiUtils
     /**
      * Format a Link for the REST API.
      *
-     * @param array  $link     Link data read from the datastore.
+     * @param array $link Link data read from the datastore.
      * @param string $indexUrl Shaarli's index URL (used for relative URL).
      *
      * @return array Link data formatted for the REST API.
@@ -77,4 +77,35 @@ class ApiUtils
         }
         return $out;
     }
+
+    /**
+     * Convert a link given through a request, to a valid link for LinkDB.
+     *
+     * If no URL is provided, it will generate a local note URL.
+     * If no title is provided, it will use the URL as title.
+     *
+     * @param array  $input          Request Link.
+     * @param bool   $defaultPrivate Request Link.
+     *
+     * @return array Formatted link.
+     */
+    public static function buildLinkFromRequest($input, $defaultPrivate)
+    {
+        $input['url'] = ! empty($input['url']) ? cleanup_url($input['url']) : '';
+        if (isset($input['private'])) {
+            $private = filter_var($input['private'], FILTER_VALIDATE_BOOLEAN);
+        } else {
+            $private = $defaultPrivate;
+        }
+
+        $link = [
+            'title'         => ! empty($input['title']) ? $input['title'] : $input['url'],
+            'url'           => $input['url'],
+            'description'   => ! empty($input['description']) ? $input['description'] : '',
+            'tags'          => ! empty($input['tags']) ? implode(' ', $input['tags']) : '',
+            'private'       => $private,
+            'created'       => new \DateTime(),
+        ];
+        return $link;
+    }
 }
index 1dd47f17da5709cb5bdf605c35e28c61604675a0..f35b923a0c52acf8826a61008b8f91f22c28b35f 100644 (file)
@@ -51,4 +51,14 @@ abstract class ApiController
             $this->jsonStyle = null;
         }
     }
+
+    /**
+     * Get the container.
+     *
+     * @return Container
+     */
+    public function getCi()
+    {
+        return $this->ci;
+    }
 }
index d4f1a09c8529cd53a019ef759f73e850fa8250aa..0db10fd054daff621826d58affafdc2eaa04aa04 100644 (file)
@@ -97,11 +97,53 @@ class Links extends ApiController
      */
     public function getLink($request, $response, $args)
     {
-        if (! isset($this->linkDb[$args['id']])) {
+        if (!isset($this->linkDb[$args['id']])) {
             throw new ApiLinkNotFoundException();
         }
         $index = index_url($this->ci['environment']);
         $out = ApiUtils::formatLink($this->linkDb[$args['id']], $index);
+
         return $response->withJson($out, 200, $this->jsonStyle);
     }
+
+    /**
+     * Creates a new link from posted request body.
+     *
+     * @param Request  $request  Slim request.
+     * @param Response $response Slim response.
+     *
+     * @return Response response.
+     */
+    public function postLink($request, $response)
+    {
+        $data = $request->getParsedBody();
+        $link = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links'));
+        // duplicate by URL, return 409 Conflict
+        if (! empty($link['url']) && ! empty($dup = $this->linkDb->getLinkFromUrl($link['url']))) {
+            return $response->withJson(
+                ApiUtils::formatLink($dup, index_url($this->ci['environment'])),
+                409,
+                $this->jsonStyle
+            );
+        }
+
+        $link['id'] = $this->linkDb->getNextId();
+        $link['shorturl'] = link_small_hash($link['created'], $link['id']);
+
+        // note: general relative URL
+        if (empty($link['url'])) {
+            $link['url'] = '?' . $link['shorturl'];
+        }
+
+        if (empty($link['title'])) {
+            $link['title'] = $link['url'];
+        }
+
+        $this->linkDb[$link['id']] = $link;
+        $this->linkDb->save($this->conf->get('resource.page_cache'));
+        $out = ApiUtils::formatLink($link, index_url($this->ci['environment']));
+        $redirect = $this->ci->router->relativePathFor('getLink', ['id' => $link['id']]);
+        return $response->withAddedHeader('Location', $redirect)
+                        ->withJson($out, 201, $this->jsonStyle);
+    }
 }
index 5497a23e2adfb3b25ca238c69f0161f6dd03774a..e392e501aee16891e6b7c24895b6e51d0130c60f 100644 (file)
--- a/index.php
+++ b/index.php
@@ -432,7 +432,7 @@ if (isset($_POST['login']))
         // Optional redirect after login:
         if (isset($_GET['post'])) {
             $uri = '?post='. urlencode($_GET['post']);
-            foreach (array('description', 'source', 'title') as $param) {
+            foreach (array('description', 'source', 'title', 'tags') as $param) {
                 if (!empty($_GET[$param])) {
                     $uri .= '&'.$param.'='.urlencode($_GET[$param]);
                 }
@@ -461,7 +461,7 @@ if (isset($_POST['login']))
         $redir = '&username='. $_POST['login'];
         if (isset($_GET['post'])) {
             $redir .= '&post=' . urlencode($_GET['post']);
-            foreach (array('description', 'source', 'title') as $param) {
+            foreach (array('description', 'source', 'title', 'tags') as $param) {
                 if (!empty($_GET[$param])) {
                     $redir .= '&' . $param . '=' . urlencode($_GET[$param]);
                 }
@@ -472,34 +472,6 @@ if (isset($_POST['login']))
     }
 }
 
-// ------------------------------------------------------------------------------------------
-// Misc utility functions:
-
-// Convert post_max_size/upload_max_filesize (e.g. '16M') parameters to bytes.
-function return_bytes($val)
-{
-    $val = trim($val); $last=strtolower($val[strlen($val)-1]);
-    switch($last)
-    {
-        case 'g': $val *= 1024;
-        case 'm': $val *= 1024;
-        case 'k': $val *= 1024;
-    }
-    return $val;
-}
-
-// Try to determine max file size for uploads (POST).
-// Returns an integer (in bytes)
-function getMaxFileSize()
-{
-    $size1 = return_bytes(ini_get('post_max_size'));
-    $size2 = return_bytes(ini_get('upload_max_filesize'));
-    // Return the smaller of two:
-    $maxsize = min($size1,$size2);
-    // FIXME: Then convert back to readable notations ? (e.g. 2M instead of 2000000)
-    return $maxsize;
-}
-
 // ------------------------------------------------------------------------------------------
 // Token management for XSRF protection
 // Token should be used in any form which acts on data (create,update,delete,import...).
@@ -695,9 +667,11 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
 
     $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000');
     $data = array(
+        'pagetitle' => $conf->get('general.title') .' - '. format_date($dayDate, false),
         'linksToDisplay' => $linksToDisplay,
         'cols' => $columns,
         'day' => $dayDate->getTimestamp(),
+        'dayDate' => $dayDate,
         'previousday' => $previousday,
         'nextday' => $nextday,
     );
@@ -1044,7 +1018,13 @@ function renderPage($conf, $pluginManager, $LINKSDB)
         // Show login screen, then redirect to ?post=...
         if (isset($_GET['post']))
         {
-            header('Location: ?do=login&post='.urlencode($_GET['post']).(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').(!empty($_GET['description'])?'&description='.urlencode($_GET['description']):'').(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'')); // Redirect to login page, then back to post link.
+            header( // Redirect to login page, then back to post link.
+                'Location: ?do=login&post='.urlencode($_GET['post']).
+                (!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').
+                (!empty($_GET['description'])?'&description='.urlencode($_GET['description']):'').
+                (!empty($_GET['tags'])?'&tags='.urlencode($_GET['tags']):'').
+                (!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'')
+            );
             exit;
         }
 
@@ -1141,7 +1121,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
             $conf->set('feed.rss_permalinks', !empty($_POST['enableRssPermalinks']));
             $conf->set('updates.check_updates', !empty($_POST['updateCheck']));
             $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks']));
-            $conf->set('api.enabled', !empty($_POST['apiEnabled']));
+            $conf->set('api.enabled', !empty($_POST['enableApi']));
             $conf->set('api.secret', escape($_POST['apiSecret']));
             try {
                 $conf->write(isLoggedIn());
@@ -1248,7 +1228,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
         }
 
         // lf_id should only be present if the link exists.
-        $id = !empty($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : $LINKSDB->getNextId();
+        $id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : $LINKSDB->getNextId();
         // Linkdate is kept here to:
         //   - use the same permalink for notes as they're displayed when creating them
         //   - let users hack creation date of their posts
@@ -1321,9 +1301,13 @@ function renderPage($conf, $pluginManager, $LINKSDB)
     // -------- User clicked the "Cancel" button when editing a link.
     if (isset($_POST['cancel_edit']))
     {
+        $id = isset($_POST['lf_id']) ? (int) escape($_POST['lf_id']) : false;
+        if (! isset($LINKSDB[$id])) {
+            header('Location: ?');
+        }
         // If we are called from the bookmarklet, we must close the popup:
         if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
-        $link = $LINKSDB[(int) escape($_POST['lf_id'])];
+        $link = $LINKSDB[$id];
         $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
         // Scroll to the link which has been edited.
         $returnurl .= '#'. $link['shorturl'];
@@ -1508,7 +1492,22 @@ function renderPage($conf, $pluginManager, $LINKSDB)
 
         if (! isset($_POST['token']) || ! isset($_FILES['filetoupload'])) {
             // Show import dialog
-            $PAGE->assign('maxfilesize', getMaxFileSize());
+            $PAGE->assign(
+                'maxfilesize',
+                get_max_upload_size(
+                    ini_get('post_max_size'),
+                    ini_get('upload_max_filesize'),
+                    false
+                )
+            );
+            $PAGE->assign(
+                'maxfilesizeHuman',
+                get_max_upload_size(
+                    ini_get('post_max_size'),
+                    ini_get('upload_max_filesize'),
+                    true
+                )
+            );
             $PAGE->renderPage('import');
             exit;
         }
@@ -1518,7 +1517,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
             // The file is too big or some form field may be missing.
             echo '<script>alert("The file you are trying to upload is probably'
                 .' bigger than what this webserver can accept ('
-                .getMaxFileSize().' bytes).'
+                .get_max_upload_size(ini_get('post_max_size'), ini_get('upload_max_filesize')).').'
                 .' Please upload in smaller chunks.");document.location=\'?do='
                 .Router::$PAGE_IMPORT .'\';</script>';
             exit;
@@ -2227,9 +2226,10 @@ $app = new \Slim\App($container);
 
 // REST API routes
 $app->group('/api/v1', function() {
-    $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo');
-    $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks');
-    $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink');
+    $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo')->setName('getInfo');
+    $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks')->setName('getLinks');
+    $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink')->setName('getLink');
+    $this->post('/links', '\Shaarli\Api\Controllers\Links:postLink')->setName('postLink');
 })->add('\Shaarli\Api\ApiMiddleware');
 
 $response = $app->run(true);
diff --git a/plugins/readityourself/book-open.png b/plugins/readityourself/book-open.png
deleted file mode 100644 (file)
index 36513d7..0000000
Binary files a/plugins/readityourself/book-open.png and /dev/null differ
diff --git a/plugins/readityourself/readityourself.html b/plugins/readityourself/readityourself.html
deleted file mode 100644 (file)
index 5e20071..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<span><a href="%s?url=%s"><img class="linklist-plugin-icon" src="%s/readityourself/book-open.png" title="Read with Readityourself" alt="readityourself" /></a></span>
diff --git a/plugins/readityourself/readityourself.meta b/plugins/readityourself/readityourself.meta
deleted file mode 100644 (file)
index bd611dd..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-description="For each link, add a ReadItYourself icon to save the shaared URL."
-parameters=READITYOUSELF_URL;
\ No newline at end of file
diff --git a/plugins/readityourself/readityourself.php b/plugins/readityourself/readityourself.php
deleted file mode 100644 (file)
index 961c5bd..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-/**
- * Plugin readityourself
- */
-
-// If we're talking about https://github.com/memiks/readityourself
-// it seems kinda dead.
-// Not tested.
-
-/**
- * Init function, return an error if the server is not set.
- *
- * @param $conf ConfigManager instance.
- *
- * @return array Eventual error.
- */
-function readityourself_init($conf)
-{
-    $riyUrl = $conf->get('plugins.READITYOUSELF_URL');
-    if (empty($riyUrl)) {
-        $error = 'Readityourself plugin error: '.
-            'Please define the "READITYOUSELF_URL" setting in the plugin administration page.';
-        return array($error);
-    }
-}
-
-/**
- * Add readityourself icon to link_plugin when rendering linklist.
- *
- * @param mixed         $data Linklist data.
- * @param ConfigManager $conf Configuration Manager instance.
- *
- * @return mixed - linklist data with readityourself plugin.
- */
-function hook_readityourself_render_linklist($data, $conf)
-{
-    $riyUrl = $conf->get('plugins.READITYOUSELF_URL');
-    if (empty($riyUrl)) {
-        return $data;
-    }
-
-    $readityourself_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/readityourself/readityourself.html');
-
-    foreach ($data['links'] as &$value) {
-        $readityourself = sprintf($readityourself_html, $riyUrl, $value['url'], PluginManager::$PLUGINS_PATH);
-        $value['link_plugin'][] = $readityourself;
-    }
-
-    return $data;
-}
index e70cc1aef10197d66718149a0ded03f85cbec072..d6a0aad5e8e0201da8441dcaa83eab6d7181575b 100644 (file)
@@ -4,6 +4,7 @@
  */
 
 require_once 'application/Utils.php';
+require_once 'application/Languages.php';
 require_once 'tests/utils/ReferenceSessionIdHashes.php';
 
 // Initialize reference data before PHPUnit starts a session
@@ -326,4 +327,94 @@ class UtilsTest extends PHPUnit_Framework_TestCase
         $this->assertFalse(format_date([]));
         $this->assertFalse(format_date(null));
     }
+
+    /**
+     * Test is_integer_mixed with valid values
+     */
+    public function testIsIntegerMixedValid()
+    {
+        $this->assertTrue(is_integer_mixed(12));
+        $this->assertTrue(is_integer_mixed('12'));
+        $this->assertTrue(is_integer_mixed(-12));
+        $this->assertTrue(is_integer_mixed('-12'));
+        $this->assertTrue(is_integer_mixed(0));
+        $this->assertTrue(is_integer_mixed('0'));
+        $this->assertTrue(is_integer_mixed(0x0a));
+    }
+
+    /**
+     * Test is_integer_mixed with invalid values
+     */
+    public function testIsIntegerMixedInvalid()
+    {
+        $this->assertFalse(is_integer_mixed(true));
+        $this->assertFalse(is_integer_mixed(false));
+        $this->assertFalse(is_integer_mixed([]));
+        $this->assertFalse(is_integer_mixed(['test']));
+        $this->assertFalse(is_integer_mixed([12]));
+        $this->assertFalse(is_integer_mixed(new DateTime()));
+        $this->assertFalse(is_integer_mixed('0x0a'));
+        $this->assertFalse(is_integer_mixed('12k'));
+        $this->assertFalse(is_integer_mixed('k12'));
+        $this->assertFalse(is_integer_mixed(''));
+    }
+
+    /**
+     * Test return_bytes
+     */
+    public function testReturnBytes()
+    {
+        $this->assertEquals(2 * 1024, return_bytes('2k'));
+        $this->assertEquals(2 * 1024, return_bytes('2K'));
+        $this->assertEquals(2 * (pow(1024, 2)), return_bytes('2m'));
+        $this->assertEquals(2 * (pow(1024, 2)), return_bytes('2M'));
+        $this->assertEquals(2 * (pow(1024, 3)), return_bytes('2g'));
+        $this->assertEquals(2 * (pow(1024, 3)), return_bytes('2G'));
+        $this->assertEquals(374, return_bytes('374'));
+        $this->assertEquals(374, return_bytes(374));
+        $this->assertEquals(0, return_bytes('0'));
+        $this->assertEquals(0, return_bytes(0));
+        $this->assertEquals(-1, return_bytes('-1'));
+        $this->assertEquals(-1, return_bytes(-1));
+        $this->assertEquals('', return_bytes(''));
+    }
+
+    /**
+     * Test human_bytes
+     */
+    public function testHumanBytes()
+    {
+        $this->assertEquals('2kiB', human_bytes(2 * 1024));
+        $this->assertEquals('2kiB', human_bytes(strval(2 * 1024)));
+        $this->assertEquals('2MiB', human_bytes(2 * (pow(1024, 2))));
+        $this->assertEquals('2MiB', human_bytes(strval(2 * (pow(1024, 2)))));
+        $this->assertEquals('2GiB', human_bytes(2 * (pow(1024, 3))));
+        $this->assertEquals('2GiB', human_bytes(strval(2 * (pow(1024, 3)))));
+        $this->assertEquals('374B', human_bytes(374));
+        $this->assertEquals('374B', human_bytes('374'));
+        $this->assertEquals('232kiB', human_bytes(237481));
+        $this->assertEquals('Unlimited', human_bytes('0'));
+        $this->assertEquals('Unlimited', human_bytes(0));
+        $this->assertEquals('Setting not set', human_bytes(''));
+    }
+
+    /**
+     * Test get_max_upload_size with formatting
+     */
+    public function testGetMaxUploadSize()
+    {
+        $this->assertEquals('1MiB', get_max_upload_size(2097152, '1024k'));
+        $this->assertEquals('1MiB', get_max_upload_size('1m', '2m'));
+        $this->assertEquals('100B', get_max_upload_size(100, 100));
+    }
+
+    /**
+     * Test get_max_upload_size without formatting
+     */
+    public function testGetMaxUploadSizeRaw()
+    {
+        $this->assertEquals('1048576', get_max_upload_size(2097152, '1024k', false));
+        $this->assertEquals('1048576', get_max_upload_size('1m', '2m', false));
+        $this->assertEquals('100', get_max_upload_size(100, 100, false));
+    }
 }
diff --git a/tests/api/controllers/PostLinkTest.php b/tests/api/controllers/PostLinkTest.php
new file mode 100644 (file)
index 0000000..3ed7bcb
--- /dev/null
@@ -0,0 +1,193 @@
+<?php
+
+namespace Shaarli\Api\Controllers;
+
+
+use Shaarli\Config\ConfigManager;
+use Slim\Container;
+use Slim\Http\Environment;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+/**
+ * Class PostLinkTest
+ *
+ * Test POST Link REST API service.
+ *
+ * @package Shaarli\Api\Controllers
+ */
+class PostLinkTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var string datastore to test write operations
+     */
+    protected static $testDatastore = 'sandbox/datastore.php';
+
+    /**
+     * @var ConfigManager instance
+     */
+    protected $conf;
+
+    /**
+     * @var \ReferenceLinkDB instance.
+     */
+    protected $refDB = null;
+
+    /**
+     * @var Container instance.
+     */
+    protected $container;
+
+    /**
+     * @var Links controller instance.
+     */
+    protected $controller;
+
+    /**
+     * Number of JSON field per link.
+     */
+    const NB_FIELDS_LINK = 9;
+
+    /**
+     * Before every test, instantiate a new Api with its config, plugins and links.
+     */
+    public function setUp()
+    {
+        $this->conf = new ConfigManager('tests/utils/config/configJson.json.php');
+        $this->refDB = new \ReferenceLinkDB();
+        $this->refDB->write(self::$testDatastore);
+
+        $this->container = new Container();
+        $this->container['conf'] = $this->conf;
+        $this->container['db'] = new \LinkDB(self::$testDatastore, true, false);
+
+        $this->controller = new Links($this->container);
+
+        $mock = $this->getMock('\Slim\Router', ['relativePathFor']);
+        $mock->expects($this->any())
+             ->method('relativePathFor')
+             ->willReturn('api/v1/links/1');
+
+        // affect @property-read... seems to work
+        $this->controller->getCi()->router = $mock;
+
+        // Used by index_url().
+        $this->controller->getCi()['environment'] = [
+            'SERVER_NAME' => 'domain.tld',
+            'SERVER_PORT' => 80,
+            'SCRIPT_NAME' => '/',
+        ];
+    }
+
+    /**
+     * After every test, remove the test datastore.
+     */
+    public function tearDown()
+    {
+        @unlink(self::$testDatastore);
+    }
+
+    /**
+     * Test link creation without any field: creates a blank note.
+     */
+    public function testPostLinkMinimal()
+    {
+        $env = Environment::mock([
+            'REQUEST_METHOD' => 'POST',
+        ]);
+
+        $request = Request::createFromEnvironment($env);
+
+        $response = $this->controller->postLink($request, new Response());
+        $this->assertEquals(201, $response->getStatusCode());
+        $this->assertEquals('api/v1/links/1', $response->getHeader('Location')[0]);
+        $data = json_decode((string) $response->getBody(), true);
+        $this->assertEquals(self::NB_FIELDS_LINK, count($data));
+        $this->assertEquals(43, $data['id']);
+        $this->assertRegExp('/[\w-_]{6}/', $data['shorturl']);
+        $this->assertEquals('http://domain.tld/?' . $data['shorturl'], $data['url']);
+        $this->assertEquals('?' . $data['shorturl'], $data['title']);
+        $this->assertEquals('', $data['description']);
+        $this->assertEquals([], $data['tags']);
+        $this->assertEquals(false, $data['private']);
+        $this->assertTrue(new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created']));
+        $this->assertEquals('', $data['updated']);
+    }
+
+    /**
+     * Test link creation with all available fields.
+     */
+    public function testPostLinkFull()
+    {
+        $link = [
+            'url' => 'website.tld/test?foo=bar',
+            'title' => 'new entry',
+            'description' => 'shaare description',
+            'tags' => ['one', 'two'],
+            'private' => true,
+        ];
+        $env = Environment::mock([
+            'REQUEST_METHOD' => 'POST',
+            'CONTENT_TYPE' => 'application/json'
+        ]);
+
+        $request = Request::createFromEnvironment($env);
+        $request = $request->withParsedBody($link);
+        $response = $this->controller->postLink($request, new Response());
+
+        $this->assertEquals(201, $response->getStatusCode());
+        $this->assertEquals('api/v1/links/1', $response->getHeader('Location')[0]);
+        $data = json_decode((string) $response->getBody(), true);
+        $this->assertEquals(self::NB_FIELDS_LINK, count($data));
+        $this->assertEquals(43, $data['id']);
+        $this->assertRegExp('/[\w-_]{6}/', $data['shorturl']);
+        $this->assertEquals('http://' . $link['url'], $data['url']);
+        $this->assertEquals($link['title'], $data['title']);
+        $this->assertEquals($link['description'], $data['description']);
+        $this->assertEquals($link['tags'], $data['tags']);
+        $this->assertEquals(true, $data['private']);
+        $this->assertTrue(new \DateTime('2 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created']));
+        $this->assertEquals('', $data['updated']);
+    }
+
+    /**
+     * Test link creation with an existing link (duplicate URL). Should return a 409 HTTP error and the existing link.
+     */
+    public function testPostLinkDuplicate()
+    {
+        $link = [
+            'url' => 'mediagoblin.org/',
+            'title' => 'new entry',
+            'description' => 'shaare description',
+            'tags' => ['one', 'two'],
+            'private' => true,
+        ];
+        $env = Environment::mock([
+            'REQUEST_METHOD' => 'POST',
+            'CONTENT_TYPE' => 'application/json'
+        ]);
+
+        $request = Request::createFromEnvironment($env);
+        $request = $request->withParsedBody($link);
+        $response = $this->controller->postLink($request, new Response());
+
+        $this->assertEquals(409, $response->getStatusCode());
+        $data = json_decode((string) $response->getBody(), true);
+        $this->assertEquals(self::NB_FIELDS_LINK, count($data));
+        $this->assertEquals(7, $data['id']);
+        $this->assertEquals('IuWvgA', $data['shorturl']);
+        $this->assertEquals('http://mediagoblin.org/', $data['url']);
+        $this->assertEquals('MediaGoblin', $data['title']);
+        $this->assertEquals('A free software media publishing platform #hashtagOther', $data['description']);
+        $this->assertEquals(['gnu', 'media', 'web', '.hidden', 'hashtag'], $data['tags']);
+        $this->assertEquals(false, $data['private']);
+        $this->assertEquals(
+            \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
+            \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])
+        );
+        $this->assertEquals(
+            \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130615_184230'),
+            \DateTime::createFromFormat(\DateTime::ATOM, $data['updated'])
+        );
+    }
+}
index 545fa572e46dbdeaf40db50a0da942a5f49169dd..6c9c9adce8cb6b02653bf32b4660f5f69c04edd5 100644 (file)
@@ -11,7 +11,16 @@ class UtilsDeTest extends UtilsTest
     public function testDateFormat()
     {
         $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
-        $this->assertRegExp('/1. Januar 2017 (um )?10:11:12 GMT\+0?3(:00)?/', format_date($date, true));
+        $this->assertRegExp('/1\. Januar 2017 (um )?10:11:12 GMT\+0?3(:00)?/', format_date($date, true, true));
+    }
+
+    /**
+     * Test date_format() without time.
+     */
+    public function testDateFormatNoTime()
+    {
+        $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
+        $this->assertRegExp('/1\. Januar 2017/', format_date($date, false,true));
     }
 
     /**
@@ -20,7 +29,16 @@ class UtilsDeTest extends UtilsTest
     public function testDateFormatDefault()
     {
         $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
-        $this->assertEquals('So 01 Jan 2017 10:11:12 EAT', format_date($date, false));
+        $this->assertEquals('So 01 Jan 2017 10:11:12 EAT', format_date($date, true, false));
+    }
+
+    /**
+     * Test date_format() using builtin PHP function strftime without time.
+     */
+    public function testDateFormatDefaultNoTime()
+    {
+        $date = DateTime::createFromFormat('Ymd_His', '20170201_101112');
+        $this->assertEquals('01.02.2017', format_date($date, false, false));
     }
 
     /**
index 7c829ac7e5856cd003e979949572d7c1551397da..d8680b2b429c1ae6ab4b1f0f888e61c5d3faf4a4 100644 (file)
@@ -11,7 +11,16 @@ class UtilsEnTest extends UtilsTest
     public function testDateFormat()
     {
         $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
-        $this->assertRegExp('/January 1, 2017 (at )?10:11:12 AM GMT\+0?3(:00)?/', format_date($date, true));
+        $this->assertRegExp('/January 1, 2017 (at )?10:11:12 AM GMT\+0?3(:00)?/', format_date($date, true, true));
+    }
+
+    /**
+     * Test date_format() without time.
+     */
+    public function testDateFormatNoTime()
+    {
+        $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
+        $this->assertRegExp('/January 1, 2017/', format_date($date, false, true));
     }
 
     /**
@@ -20,7 +29,16 @@ class UtilsEnTest extends UtilsTest
     public function testDateFormatDefault()
     {
         $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
-        $this->assertEquals('Sun 01 Jan 2017 10:11:12 AM EAT', format_date($date, false));
+        $this->assertEquals('Sun 01 Jan 2017 10:11:12 AM EAT', format_date($date, true, false));
+    }
+
+    /**
+     * Test date_format() using builtin PHP function strftime without time.
+     */
+    public function testDateFormatDefaultNoTime()
+    {
+        $date = DateTime::createFromFormat('Ymd_His', '20170201_101112');
+        $this->assertEquals('02/01/2017', format_date($date, false, false));
     }
 
     /**
index 45996ee27318c37c6e0e3db2ef76d58ca990a636..0d50a87829f241591b0868446831c2918dcd4618 100644 (file)
@@ -14,13 +14,31 @@ class UtilsFrTest extends UtilsTest
         $this->assertRegExp('/1 janvier 2017 (à )?10:11:12 UTC\+0?3(:00)?/', format_date($date));
     }
 
+    /**
+     * Test date_format() without time.
+     */
+    public function testDateFormatNoTime()
+    {
+        $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
+        $this->assertRegExp('/1 janvier 2017/', format_date($date, false, true));
+    }
+
     /**
      * Test date_format() using builtin PHP function strftime.
      */
     public function testDateFormatDefault()
     {
         $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
-        $this->assertEquals('dim. 01 janv. 2017 10:11:12 EAT', format_date($date, false));
+        $this->assertEquals('dim. 01 janv. 2017 10:11:12 EAT', format_date($date, true, false));
+    }
+
+    /**
+     * Test date_format() using builtin PHP function strftime without time.
+     */
+    public function testDateFormatDefaultNoTime()
+    {
+        $date = DateTime::createFromFormat('Ymd_His', '20170201_101112');
+        $this->assertEquals('01/02/2017', format_date($date, false, false));
     }
 
     /**
diff --git a/tests/plugins/PluginReadityourselfTest.php b/tests/plugins/PluginReadityourselfTest.php
deleted file mode 100644 (file)
index bbba967..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-<?php
-use Shaarli\Config\ConfigManager;
-
-/**
- * PluginReadityourselfTest.php.php
- */
-
-require_once 'plugins/readityourself/readityourself.php';
-
-/**
- * Class PluginWallabagTest
- * Unit test for the Wallabag plugin
- */
-class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
-{
-    /**
-     * Reset plugin path
-     */
-    public function setUp()
-    {
-        PluginManager::$PLUGINS_PATH = 'plugins';
-    }
-
-    /**
-     * Test Readityourself init without errors.
-     */
-    public function testReadityourselfInitNoError()
-    {
-        $conf = new ConfigManager('');
-        $conf->set('plugins.READITYOUSELF_URL', 'value');
-        $errors = readityourself_init($conf);
-        $this->assertEmpty($errors);
-    }
-
-    /**
-     * Test Readityourself init with errors.
-     */
-    public function testReadityourselfInitError()
-    {
-        $conf = new ConfigManager('');
-        $errors = readityourself_init($conf);
-        $this->assertNotEmpty($errors);
-    }
-
-    /**
-     * Test render_linklist hook.
-     */
-    public function testReadityourselfLinklist()
-    {
-        $conf = new ConfigManager('');
-        $conf->set('plugins.READITYOUSELF_URL', 'value');
-        $str = 'http://randomstr.com/test';
-        $data = array(
-            'title' => $str,
-            'links' => array(
-                array(
-                    'url' => $str,
-                )
-            )
-        );
-
-        $data = hook_readityourself_render_linklist($data, $conf);
-        $link = $data['links'][0];
-        // data shouldn't be altered
-        $this->assertEquals($str, $data['title']);
-        $this->assertEquals($str, $link['url']);
-
-        // plugin data
-        $this->assertEquals(1, count($link['link_plugin']));
-        $this->assertNotFalse(strpos($link['link_plugin'][0], $str));
-    }
-
-    /**
-     * Test without config: nothing should happened.
-     */
-    public function testReadityourselfLinklistWithoutConfig()
-    {
-        $conf = new ConfigManager('');
-        $conf->set('plugins.READITYOUSELF_URL', null);
-        $str = 'http://randomstr.com/test';
-        $data = array(
-            'title' => $str,
-            'links' => array(
-                array(
-                    'url' => $str,
-                )
-            )
-        );
-
-        $data = hook_readityourself_render_linklist($data, $conf);
-        $link = $data['links'][0];
-        // data shouldn't be altered
-        $this->assertEquals($str, $data['title']);
-        $this->assertEquals($str, $link['url']);
-
-        // plugin data
-        $this->assertArrayNotHasKey('link_plugin', $link);
-    }
-}
index 36d58c683e449eb11ac2388c3c4fe43bda2bd1b6..1f4b306372e9a8dd4959d9e3636c39220ea01202 100644 (file)
@@ -56,7 +56,7 @@ class ReferenceLinkDB
             0,
             DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
             'gnu media web .hidden hashtag',
-            null,
+            DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20130615_184230'),
             'IuWvgA'
         );
 
index fd8ee9c25140cc0f89f0a18fa871d8faf0d49fa3..7469ab597a69ab4cb468a43e0b8fb6e7d6a0cb98 100644 (file)
       <div class="pure-g">
         <div class="pure-u-lg-{$ratioLabel} pure-u-{$ratioLabelMobile}">
           <div class="form-label">
-            <label for="apiEnabled">
+            <label for="enableApi">
               <span class="label-name">{'Enable REST API'|t}</span><br>
               <span class="label-desc">{'Allow third party software to use Shaarli such as mobile application'|t}</span>
             </label>
         </div>
         <div class="pure-u-lg-{$ratioInput} pure-u-{$ratioInputMobile}">
           <div class="form-input">
-            <input type="checkbox" name="apiEnabled" id="apiEnabled"
+            <input type="checkbox" name="enableApi" id="enableApi"
                  {if="$api_enabled"}checked{/if}/>
           </div>
         </div>
index 8fcd13afdad80956789f8842e329ef1d3ac1233e..73fade5ffa3a4a6570ecb296baa7e51391f0f3df 100644 (file)
@@ -35,14 +35,29 @@ pre {
 }
 
 @font-face {
-    font-family: 'Roboto Slab';
+    font-family: 'Roboto';
     font-weight: 400;
     font-style: normal;
     src:
-    local('Fira Sans'),
-    local('Fira-Sans-regular'),
-    url('../fonts/Fira-Sans-regular.woff2') format('woff2'),
-    url('../fonts/Fira-Sans-regular.woff') format('woff');
+    local('Roboto'),
+    local('Roboto-Regular'),
+    url('../fonts/Roboto-Regular.woff2') format('woff2'),
+    url('../fonts/Roboto-Regular.woff') format('woff');
+}
+
+@font-face {
+    font-family: 'Roboto';
+    font-weight: 700;
+    font-style: normal;
+    src:
+    local('Roboto'),
+    local('Roboto-Bold'),
+    url('../fonts/Roboto-Bold.woff2') format('woff2'),
+    url('../fonts/Roboto-Bold.woff') format('woff');
+}
+
+body, .pure-g [class*="pure-u"] {
+    font-family: Roboto, Arial, sans-serif;
 }
 
 /**
@@ -68,10 +83,6 @@ pre {
     .pure-u-xl-visible { display: inline-block !important; }
 }
 
-.pure-g [class*="pure-u"]{
-    font-family: Roboto Slab, Arial, sans-serif;
-}
-
 /**
  * Make pure-extras alert closable.
  */
@@ -504,7 +515,6 @@ pre {
     color: #252525;
     text-decoration: none;
     vertical-align: middle;
-    font-family: Roboto Slab, Arial, sans-serif;
 }
 
 .linklist-item-title .linklist-link {
@@ -560,7 +570,6 @@ pre {
 .linklist-item-description {
     position: relative;
     padding: 10px;
-    font-family: Roboto Slab, Arial, sans-serif;
     word-wrap: break-word;
     color: #252525;
     line-height: 1.3em;
index d8c91078ad533dbda81d769c1f543292ae1b68e5..29d845d5307231d1337e3964837d8931a89736e0 100644 (file)
@@ -44,7 +44,7 @@
         </div>
       </div>
       <div>
-        <h3 class="window-subtitle">{function="strftime('%A %d, %B %Y', $day)"}</h3>
+        <h3 class="window-subtitle">{function="format_date($dayDate, false)"}</h3>
 
         <div id="plugin_zone_about_daily" class="plugin_zone">
           {loop="$daily_about_plugin"}
diff --git a/tpl/default/fonts/Fira-Sans-regular.woff b/tpl/default/fonts/Fira-Sans-regular.woff
deleted file mode 100644 (file)
index 014ac31..0000000
Binary files a/tpl/default/fonts/Fira-Sans-regular.woff and /dev/null differ
diff --git a/tpl/default/fonts/Fira-Sans-regular.woff2 b/tpl/default/fonts/Fira-Sans-regular.woff2
deleted file mode 100644 (file)
index bf3ad9a..0000000
Binary files a/tpl/default/fonts/Fira-Sans-regular.woff2 and /dev/null differ
diff --git a/tpl/default/fonts/Roboto-Bold.woff b/tpl/default/fonts/Roboto-Bold.woff
new file mode 100644 (file)
index 0000000..3d86753
Binary files /dev/null and b/tpl/default/fonts/Roboto-Bold.woff differ
diff --git a/tpl/default/fonts/Roboto-Bold.woff2 b/tpl/default/fonts/Roboto-Bold.woff2
new file mode 100644 (file)
index 0000000..bd05e2e
Binary files /dev/null and b/tpl/default/fonts/Roboto-Bold.woff2 differ
diff --git a/tpl/default/fonts/Roboto-Regular.woff b/tpl/default/fonts/Roboto-Regular.woff
new file mode 100644 (file)
index 0000000..464d206
Binary files /dev/null and b/tpl/default/fonts/Roboto-Regular.woff differ
diff --git a/tpl/default/fonts/Roboto-Regular.woff2 b/tpl/default/fonts/Roboto-Regular.woff2
new file mode 100644 (file)
index 0000000..f966196
Binary files /dev/null and b/tpl/default/fonts/Roboto-Regular.woff2 differ
index e6e521e8fc3d0280e2ec04f622c88a5792271968..1f040685caa5195f0a2e9a41ec199bf720bee178 100644 (file)
@@ -18,6 +18,7 @@
       <div class="center" id="import-field">
         <input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}">
         <input type="file" name="filetoupload">
+        <p><br>Maximum size allowed: <strong>{$maxfilesizeHuman}</strong></p>
       </div>
 
       <div class="pure-g">
index c5052a26b7a9077a40057d49ba288b9fed1c9d69..99aca19357f1bdef2d8b24be857a236fa2a1e6b1 100644 (file)
@@ -7,6 +7,8 @@
 
 {$ratioLabel='1-4'}
 {$ratioInput='3-4'}
+{$ratioLabelMobile='7-8'}
+{$ratioInputMobile='1-8'}
 
 <form method="POST" action="#" name="installform" id="installform">
 <div class="pure-g">
       </div>
     </div>
 
+    <div class="pure-g">
+      <div class="pure-u-lg-{$ratioLabel} pure-u-{$ratioLabelMobile}">
+        <div class="form-label">
+          <label for="enableApi">
+            <span class="label-name">{'Enable REST API'|t}</span><br>
+            <span class="label-desc">{'Allow third party software to use Shaarli such as mobile application'|t}</span>
+          </label>
+        </div>
+      </div>
+      <div class="pure-u-lg-{$ratioInput} pure-u-{$ratioInputMobile}">
+        <div class="form-input">
+          <input type="checkbox" name="enableApi" id="enableApi" checked />
+        </div>
+      </div>
+    </div>
+
     <div class="center">
       <input type="submit" value="{'Install'|t}" name="Save">
     </div>
index 714420b7a73c5e53fc750ac138edb11abd9a106d..4d47fcd0c2cd4aaf3be8080e84c61782adabd247 100644 (file)
@@ -258,10 +258,9 @@ window.onload = function () {
      * Remove CSS target padding (for fixed bar)
      */
     if (location.hash != '') {
-        var anchor = document.querySelector(location.hash);
+        var anchor = document.getElementById(location.hash.substr(1));
         if (anchor != null) {
             var padsize = anchor.clientHeight;
-            console.log(document.querySelector(location.hash).clientHeight);
             this.window.scroll(0, this.window.scrollY - padsize);
             anchor.style.paddingTop = 0;
         }
index 61907ddf99e30e6cc504978d622f91a3e07a2d5d..7adc7545532cc7d92a0494c25aea111f79292e7c 100644 (file)
       <tr>
         <td valign="top"><b>Enable REST API</b></td>
         <td>
-          <input type="checkbox" name="apiEnabled" id="apiEnabled"
+          <input type="checkbox" name="enableApi" id="enableApi"
                  {if="$api_enabled"}checked{/if}/>
-          <label for="apiEnabled">&nbsp;Allow third party software to use Shaarli such as mobile application.</label>
+          <label for="enableApi">&nbsp;Allow third party software to use Shaarli such as mobile application.</label>
         </td>
       </tr>
       <tr>
index 071e1160e5bbae106b58206bdd5c152f6891b45f..bb9e4a562040b410b00e90bbc6a6a63b5520d9ab 100644 (file)
@@ -5,7 +5,7 @@
 <div id="pageheader">
   {include="page.header"}
   <div id="uploaddiv">
-    Import Netscape HTML bookmarks (as exported from Firefox/Chrome/Opera/Delicious/Diigo...) (Max: {$maxfilesize} bytes).
+    Import Netscape HTML bookmarks (as exported from Firefox/Chrome/Opera/Delicious/Diigo...) (Max: {$maxfilesize}).
     <form method="POST" action="?do=import" enctype="multipart/form-data"
           name="uploadform" id="uploadform">
       <input type="hidden" name="token" value="{$token}">