]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Merge pull request #764 from ArthurHoaro/feature/history
authorArthurHoaro <arthur@hoa.ro>
Sat, 6 May 2017 15:12:06 +0000 (17:12 +0200)
committerGitHub <noreply@github.com>
Sat, 6 May 2017 15:12:06 +0000 (17:12 +0200)
History mechanism

1  2 
application/LinkDB.php
index.php

diff --combined application/LinkDB.php
index 1e4d7ce88421aa8871264487ddf329f87e4e2d15,2fb15040321339cd31aa127aad692416f33e5110..0d3c85bd9815e2c879d8b18160441dfd19fd80f5
@@@ -50,12 -50,6 +50,6 @@@ class LinkDB implements Iterator, Count
      // Link date storage format
      const LINK_DATE_FORMAT = 'Ymd_His';
  
-     // Datastore PHP prefix
-     protected static $phpPrefix = '<?php /* ';
-     // Datastore PHP suffix
-     protected static $phpSuffix = ' */ ?>';
      // List of links (associative array)
      //  - key:   link date (e.g. "20110823_124546"),
      //  - value: associative array (keys: title, description...)
          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.');
          }
  
@@@ -295,16 -289,7 +289,7 @@@ You use the community supported versio
              return;
          }
  
-         // Read data
-         // Note that gzinflate is faster than gzuncompress.
-         // See: http://www.php.net/manual/en/function.gzdeflate.php#96439
-         $this->links = array();
-         if (file_exists($this->datastore)) {
-             $this->links = unserialize(gzinflate(base64_decode(
-                 substr(file_get_contents($this->datastore),
-                        strlen(self::$phpPrefix), -strlen(self::$phpSuffix)))));
-         }
+         $this->links = FileUtils::readFlatDB($this->datastore, []);
  
          $toremove = array();
          foreach ($this->links as $key => &$link) {
       */
      private function write()
      {
-         if (is_file($this->datastore) && !is_writeable($this->datastore)) {
-             // The datastore exists but is not writeable
-             throw new IOException($this->datastore);
-         } else if (!is_file($this->datastore) && !is_writeable(dirname($this->datastore))) {
-             // The datastore does not exist and its parent directory is not writeable
-             throw new IOException(dirname($this->datastore));
-         }
-         file_put_contents(
-             $this->datastore,
-             self::$phpPrefix.base64_encode(gzdeflate(serialize($this->links))).self::$phpSuffix
-         );
+         FileUtils::writeFlatDB($this->datastore, $this->links);
      }
  
      /**
diff --combined index.php
index e392e501aee16891e6b7c24895b6e51d0130c60f,7f357c69ed942a826c155b6fdfa19c80b87872ec..76aa1ae0efbc1c9d5945b34b29c23ae576b06e34
+++ b/index.php
@@@ -1,6 -1,8 +1,6 @@@
  <?php
  /**
 - * Shaarli v0.8.3 - Shaare your links...
 - *
 - * The personal, minimalist, super-fast, database free, bookmarking service.
 + * Shaarli - The personal, minimalist, super-fast, database free, bookmarking service.
   *
   * Friendly fork by the Shaarli community:
   *  - https://github.com/shaarli/Shaarli
@@@ -23,6 -25,7 +23,6 @@@ if (date_default_timezone_get() == '') 
  /*
   * PHP configuration
   */
 -define('shaarli_version', '0.8.2');
  
  // http://server.com/x/shaarli --> /shaarli/
  define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0)));
@@@ -62,6 -65,7 +62,7 @@@ require_once 'application/CachedPage.ph
  require_once 'application/config/ConfigPlugin.php';
  require_once 'application/FeedBuilder.php';
  require_once 'application/FileUtils.php';
+ require_once 'application/History.php';
  require_once 'application/HttpUtils.php';
  require_once 'application/Languages.php';
  require_once 'application/LinkDB.php';
@@@ -87,8 -91,6 +88,8 @@@ try 
      exit;
  }
  
 +define('shaarli_version', ApplicationUtils::getVersion(__DIR__ .'/'. ApplicationUtils::$VERSION_FILE));
 +
  // Force cookie path (but do not change lifetime)
  $cookie = session_get_cookie_params();
  $cookiedir = '';
@@@ -432,7 -434,7 +433,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]);
                  }
          $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]);
                  }
      }
  }
  
 -// ------------------------------------------------------------------------------------------
 -// 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...).
@@@ -667,11 -697,9 +668,11 @@@ function showDaily($pageBuilder, $LINKS
  
      $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,
      );
@@@ -727,6 -755,12 +728,12 @@@ function renderPage($conf, $pluginManag
          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));
          // 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;
          }
  
              $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());
+                 $history->updateSettings();
                  invalidateCaches($conf->get('resource.page_cache'));
              }
              catch(Exception $e) {
              $PAGE->assign('theme', $conf->get('resource.theme'));
              $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl')));
              $PAGE->assign('redirector', $conf->get('redirector.url'));
 -            list($timezone_form, $timezone_js) = generateTimeZoneForm($conf->get('general.timezone'));
 -            $PAGE->assign('timezone_form', $timezone_form);
 -            $PAGE->assign('timezone_js',$timezone_js);
 +            list($continents, $cities) = generateTimeZoneData(
 +                timezone_identifiers_list(),
 +                $conf->get('general.timezone')
 +            );
 +            $PAGE->assign('continents', $continents);
 +            $PAGE->assign('cities', $cities);
              $PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false));
              $PAGE->assign('session_protection_disabled', $conf->get('security.session_protection_disabled', false));
              $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false));
              $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;
          }
                  unset($tags[array_search($needle,$tags)]); // Remove tag.
                  $value['tags']=trim(implode(' ',$tags));
                  $LINKSDB[$key]=$value;
+                 $history->updateLink($LINKSDB[$key]);
              }
              $LINKSDB->save($conf->get('resource.page_cache'));
              echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?do=changetag\';</script>';
                  $tags[array_search($needle, $tags)] = trim($_POST['totag']);
                  $value['tags'] = implode(' ', array_unique($tags));
                  $LINKSDB[$key] = $value;
+                 $history->updateLink($LINKSDB[$key]);
              }
              $LINKSDB->save($conf->get('resource.page_cache')); // Save to disk.
              echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode(escape($_POST['totag'])).'\';</script>';
          }
  
          // 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
              $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
              $updated = new DateTime();
              $shortUrl = $LINKSDB[$id]['shorturl'];
+             $new = false;
          } else {
              // New link
              $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
              $updated = null;
              $shortUrl = link_small_hash($created, $id);
+             $new = true;
          }
  
          // Remove multiple spaces.
  
          $LINKSDB[$id] = $link;
          $LINKSDB->save($conf->get('resource.page_cache'));
+         if ($new) {
+             $history->addLink($link);
+         } else {
+             $history->updateLink($link);
+         }
  
          // If we are called from the bookmarklet, we must close the popup:
          if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) {
      // -------- 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'];
          $pluginManager->executeHooks('delete_link', $link);
          unset($LINKSDB[$id]);
          $LINKSDB->save($conf->get('resource.page_cache')); // save to disk
+         $history->deleteLink($link);
  
          // 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; }
  
          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;
          }
              // 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;
              $_POST,
              $_FILES,
              $LINKSDB,
-             $conf
+             $conf,
+             $history
          );
          echo '<script>alert("'.$status.'");document.location=\'?do='
               .Router::$PAGE_IMPORT .'\';</script>';
  
      // Plugin administration form action
      if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) {
+         $history->updateSettings();
          try {
              if (isset($_POST['parameters_form'])) {
                  unset($_POST['parameters_form']);
@@@ -1972,10 -1992,16 +1993,10 @@@ function install($conf
          exit;
      }
  
 -    // Display config form:
 -    list($timezone_form, $timezone_js) = generateTimeZoneForm();
 -    $timezone_html = '';
 -    if ($timezone_form != '') {
 -        $timezone_html = '<tr><td><b>Timezone:</b></td><td>'.$timezone_form.'</td></tr>';
 -    }
 -
      $PAGE = new PageBuilder($conf);
 -    $PAGE->assign('timezone_html',$timezone_html);
 -    $PAGE->assign('timezone_js',$timezone_js);
 +    list($continents, $cities) = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get());
 +    $PAGE->assign('continents', $continents);
 +    $PAGE->assign('cities', $cities);
      $PAGE->renderPage('install');
      exit;
  }
@@@ -2226,10 -2252,9 +2247,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);