commit fde2759722b2a4719353182aff49201c4ce82365 Author: Sven Wappler Date: Mon Aug 16 16:15:21 2021 +0200 first commit diff --git a/Classes/Controller/BookmarksController.php b/Classes/Controller/BookmarksController.php new file mode 100644 index 0000000..759b3ab --- /dev/null +++ b/Classes/Controller/BookmarksController.php @@ -0,0 +1,109 @@ +view->assignMultiple([ + 'bookmark' => $bookmark->toArray() + ]); + } + + /** + * Adds the current page as bookmark and renders/returns updated list as html + * + * This is meant to be called by ajax (typoscript_rendering) + * + * @param array $localBookmarks + */ + public function bookmarkAction($localBookmarks = []) + { + // use the parameter directly and ignore chash because url is submitted by JS + $url = GeneralUtility::_GP('url'); + $url = $url ? $url : null; + + $bookmark = Bookmark::createFromCurrent($url); + + $bookmarks = new Bookmarks(); + $bookmarks->merge($localBookmarks); + $bookmarks->addBookmark($bookmark); + $bookmarks->persist(); + + $this->updateAndSendList($bookmarks); + } + + /** + * Remove a bookmark from list and renders/returns updated list as html + * + * This is meant to be called by ajax (typoscript_rendering) + * + * @param string $id + * @param array $localBookmarks + */ + public function deleteAction($id = '', $localBookmarks = []) + { + $bookmarks = new Bookmarks(); + $bookmarks->merge($localBookmarks); + if ($id) { + $bookmarks->removeBookmark($id); + $bookmarks->persist(); + } + $this->updateAndSendList($bookmarks); + } + + /** + * Action to get bookmark list + * + * @param array $localBookmarks + */ + public function listEntriesAction($localBookmarks = []) + { + $bookmarks = new Bookmarks(); + $bookmarks->merge($localBookmarks); + $this->updateAndSendList($bookmarks); + } + + /** + * This is for ajax requests + * + * @param Bookmarks $bookmarks + */ + public function updateAndSendList(Bookmarks $bookmarks) + { + // check if we bookmarked the current page + $bookmark = Bookmark::createFromCurrent(); + $isBookmarked = $bookmarks->bookmarkExists($bookmark); + + // build the ajax response data + $response = [ + 'isBookmarked' => $isBookmarked, + 'bookmarks' => $bookmarks->getBookmarksForLocalStorage() + ]; + + header('Content-Type: application/json'); + echo json_encode($response); + die(); + } +} diff --git a/Classes/Model/Bookmark.php b/Classes/Model/Bookmark.php new file mode 100644 index 0000000..bc49897 --- /dev/null +++ b/Classes/Model/Bookmark.php @@ -0,0 +1,237 @@ +id = $url['id']; + $this->title = $url['title']; + $this->url = $url['url']; + $this->pid = $url['pid']; + $this->parameter = $url['parameter']; + } else { + $this->id = md5($pid . ':' . $parameter); + $this->title = $title; + $this->url = $url; + $this->pid = $pid; + $this->parameter = $parameter; + } + } + + /** + * Create bookmark from the current TSFE page + * + * @param string url to bookmark, if null TYPO3_REQUEST_URL will be used - which is wrong when we're in ajax context, then we use HTTP_REFERER + * @return Bookmark + */ + public static function createFromCurrent($url = null) + { + if ($url === null) { + if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') { + //request is ajax + $url = GeneralUtility::getIndpEnv('HTTP_REFERER'); + } else { + $url = GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'); + } + } + + $pid = self::getFrontend()->id; + $title = self::getCurrentPageTitle(); + + /* + + The idea was to store get parameters to make bookmark handling more flexible. + Unfortunately that didn't worked out. + + When we use ajax to trigger bookmarking the current page, we can pass the current url as parameter. + But the url doesn't have the parameters in it when you use speaking urls (realurl, simulatestatic, ...). + The problem is that there's no common api to decode urls and get the parameters. + + One solution would be to make the parameters available to the ajax javascript during page rendering. + + We skip all this and use a bit from the url for hashing and add the page id. + + */ + + $urlParts = parse_url($url); + $parameter = $urlParts['path'] . '?' . $urlParts['query'] . '#' . $urlParts['fragment']; + + return new self($url, $title, $pid, $parameter); + + /* + * So what is the idea of storing the pid and the get vars? + * + * This might makes sense if urls changed for the same page (realurl). + * With this information the new working url can be restored. + * + * Not sure which way is better ... + */ + // $parameter = (array)GeneralUtility::_GET(); + // unset($parameter['id']); + // // @todo remove cHash? + // ksort($parameter); + // $parameter = $parameter ? GeneralUtility::implodeArrayForUrl(false, $parameter) : ''; + // + // return new self($url, $title, $pid, $parameter); + } + + /** + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * @param string $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * @param string $url + */ + public function setUrl($url) + { + $this->url = $url; + } + + /** + * @return int + */ + public function getPid() + { + return $this->pid; + } + + /** + * @param int $pid + */ + public function setPid($pid) + { + $this->pid = $pid; + } + + /** + * @return string + */ + public function getParameter() + { + return $this->parameter; + } + + /** + * @param string $parameter + */ + public function setParameter($parameter) + { + $this->parameter = $parameter; + } + + /** + * Returns the bookmark data as array + * + * @return array + */ + public function toArray() + { + return [ + 'id' => $this->id, + 'title' => $this->title, + 'url' => $this->url, + 'pid' => $this->pid, + 'parameter' => $this->parameter, + ]; + } + + /** + * Get the current page title + * @return string + */ + protected static function getCurrentPageTitle() + { + return self::getFrontend()->altPageTitle? self::getFrontend()->altPageTitle : self::getFrontend()->page['title']; + } + + /** + * + * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController + */ + protected static function getFrontend() + { + return $GLOBALS['TSFE']; + } +} diff --git a/Classes/Model/Bookmarks.php b/Classes/Model/Bookmarks.php new file mode 100644 index 0000000..d21e23b --- /dev/null +++ b/Classes/Model/Bookmarks.php @@ -0,0 +1,241 @@ +getUser()->user) && $this->getUser()->user[$this->getUser()->userid_column]) { + $bookmarks = $this->getUser()->user[self::BOOKMARKS_COLUMN]; + $bookmarks = (array)GeneralUtility::xml2array($bookmarks); + foreach ($bookmarks as $bookmark) { + if (isset($bookmark['id'])) { + $this->bookmarks[$bookmark['id']] = new Bookmark($bookmark); + } + } + } + } + + /** + * persist bookmarks if needed + */ + public function __destruct() + { + $this->persist(); + } + + /** + * Get all Bookmarks + * + * @return array|Bookmark[] + */ + public function getBookmarks() + { + return (array)$this->bookmarks; + } + + /** + * clear all bookmarks + */ + public function clearBookmarks() + { + $this->bookmarks = []; + $this->changeFlag = true; + } + + /** + * Add a bookmark + * + * @param Bookmark $bookmark + */ + public function addBookmark(Bookmark $bookmark) + { + $this->bookmarks[$bookmark->getId()] = $bookmark; + $this->changeFlag = true; + } + + /** + * Get Bookmark by given id + * + * @param string $id + * @return Bookmark|mixed + */ + public function getBookmark($id) + { + return $this->bookmarks[$id]; + } + + /** + * Check if a given bookmark is stored already + * + * @param Bookmark $bookmark + * @return bool + */ + public function bookmarkExists(Bookmark $bookmark) + { + return isset($this->bookmarks[$bookmark->getId()]); + } + + /** + * Remove bookmark by given id + * + * @param string $id + */ + public function removeBookmark($id) + { + unset($this->bookmarks[$id]); + $this->changeFlag = true; + } + + /** + * persist bookmarks if needed + */ + public function persist() + { + if ($this->changeFlag && is_array($this->getUser()->user) && $this->getUser()->user[$this->getUser()->userid_column]) { + $bookmarks = []; + foreach ($this->bookmarks as $bookmark) { + $bookmarks[] = $bookmark->toArray(); + } + /* + * Why xml? + * + * Why not! You can even process it in the db if you like + * (And dooon't tell me json would be a good idea, or serialized php ... haaahaaaaaa) + */ + $bookMarksXml = GeneralUtility::array2xml($bookmarks); + + /** @var QueryBuilder $queryBuilder */ + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable($this->getUser()->user_table); + $queryBuilder + ->update($this->getUser()->user_table) + ->where( + $queryBuilder->expr()->eq( + $this->getUser()->userid_column, + $queryBuilder->createNamedParameter((int)$this->getUser()->user[$this->getUser()->userid_column], \PDO::PARAM_INT) + ) + ) + ->set(self::BOOKMARKS_COLUMN, $bookMarksXml) + ->execute(); + + $this->changeFlag = false; + } + } + + /** + * Get global frontend user + * @return \TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication + */ + protected function getUser() + { + return $GLOBALS['TSFE']->fe_user; + } + + /** + * @return array + */ + private function getAccessibleBookmarks() + { + $bookmarks = $this->getBookmarks(); + if (!$bookmarks) { + return []; + } + + // Create an array association the page uid with the bookmark id (uid => id) + $pageMap = array_flip(array_map(static function ($bookmark) { + return (int) $bookmark->getPid(); + }, $bookmarks)); + + // Get accessible pages + /** @var QueryBuilder $queryBuilder */ + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); + $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class)); + $pages = $queryBuilder + ->select('uid') + ->from('pages') + ->where($queryBuilder->expr()->in('uid', array_keys($pageMap))) + ->execute() + ->fetchAll(); + + // Collect accessible bookmarks + $accessibleBookmarks = []; + foreach ($pages as $page) { + if (isset($pageMap[$page['uid']])) { + $accessibleBookmarks[$pageMap[$page['uid']]] = $bookmarks[$pageMap[$page['uid']]]; + } + } + + // Order the bookmarks (same sequence as at the beginning) + return array_intersect_key($bookmarks, $accessibleBookmarks); + } + + /** + * Merge bookmarks into the current ones. + * + * @param $bookmarks + * @return array|Bookmark[] + */ + public function merge($bookmarks) + { + $bookmarksChanged = false; + foreach ($bookmarks as $id => $bookmark) { + if (!isset($this->bookmarks[$id])) { + $bookmarksChanged = true; + $this->bookmarks[$id] = new Bookmark($bookmark); + } + } + if ($bookmarksChanged) { + $this->persist(); + } + return $this->getBookmarks(); + } + + /** + * Get bookmarks for local storage in browser + */ + public function getBookmarksForLocalStorage(): array + { + $result = []; + foreach ($this->getAccessibleBookmarks() as $bookmark) { + $result[$bookmark->getId()] = $bookmark->toArray(); + } + return $result; + } +} diff --git a/Configuration/FlexForms/Bookmarks.xml b/Configuration/FlexForms/Bookmarks.xml new file mode 100644 index 0000000..a578307 --- /dev/null +++ b/Configuration/FlexForms/Bookmarks.xml @@ -0,0 +1,54 @@ + + + + 1 + + + + + + General + + array + + + + + + select + selectSingle + default + + + LLL:EXT:ws_bookmark_pages/Resources/Private/Language/locallang.xlf:default + default + + + LLL:EXT:ws_bookmark_pages/Resources/Private/Language/locallang.xlf:alternative + alternative + + + + + + + + + LLL:EXT:ws_bookmark_pages/Resources/Private/Language/locallang.xlf:ff.isComplementary.description + + check + 0 + + + LLL:EXT:ws_bookmark_pages/Resources/Private/Language/locallang.xlf:yes + + + + + + + + + + + diff --git a/Configuration/TCA/Overrides/sys_template.php b/Configuration/TCA/Overrides/sys_template.php new file mode 100644 index 0000000..1511f1f --- /dev/null +++ b/Configuration/TCA/Overrides/sys_template.php @@ -0,0 +1,15 @@ + + document.addEventListener('DOMContentLoaded', function(event) { + let button = document.querySelector('.frame-type-login [type="submit"]'); + if (button) { + button.addEventListener('click', function () { + localStorage.setItem('txBookmarkPagesReload', '1'); + }); + } + }); + +) diff --git a/Documentation/AdministratorManual/Index.rst b/Documentation/AdministratorManual/Index.rst new file mode 100644 index 0000000..b245b57 --- /dev/null +++ b/Documentation/AdministratorManual/Index.rst @@ -0,0 +1,28 @@ +.. include:: ../Includes.txt + + +.. _admin-manual: + +Administrator Manual +==================== + +Installation +------------ + +There are two ways to properly install the extension. + +1. Composer installation +^^^^^^^^^^^^^^^^^^^^^^^^ + +In case you use Composer to manage dependencies of your TYPO3 project, +you can just issue the following Composer command in your project root directory. + +.. code-block:: bash + + composer require wapplersystems/bookmark-pages + +2. Installation with Extension Manager +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Download and install the extension with the extension manager module. + diff --git a/Documentation/Changelog/2.0.0.rst b/Documentation/Changelog/2.0.0.rst new file mode 100644 index 0000000..7a20e1d --- /dev/null +++ b/Documentation/Changelog/2.0.0.rst @@ -0,0 +1,39 @@ +.. include:: ../Includes.txt + +.. highlight:: none + +==================================== +Changelog for release 2.0.0 +==================================== + +Features +======== +* [FEATURE] Enable multiple plugin use per page (22.04.2021, f335afc by roman) +* [FEATURE] Load bookmarks from browsers local storage (16.04.2021, 1c7511d by roman) +* [FEATURE] Cache main action, load list asynchronous (15.04.2021, d4e04e5 by roman) + +Bugfixes +======== +* [BUGFIX] Correct issue with empty list (22.04.2021, 1f87412 by Roman) +* [BUGFIX] Fix issue updating links (20.04.2021, 0a6e47e by roman) +* [BUGFIX] Fix issue with empty accessible bookmarks (20.04.2021, 1b169ab by roman) + +Breaking changes +================ +* [!!!][TASK] Enable local storage per default (27.04.2021, 2e460ca by roman) +* [!!!][TASK] Remove access restriction (18.04.2021, 28c0248 by roman) +* [!!!][TASK] Refactor js (16.04.2021, 9a09b96 by roman) + +Reference +========= + +.. highlight:: shell + +Generated by: + +git log 1.2.2..2e460cac --pretty="* %s (%cd, %h by %an)" --date=format:%d.%m.%Y --abbrev-commit --grep + +**Note:** The above list contains just commits marked with [FEATURE], [BUGFIX] and [!!!]. Complementary commits are +available at `Github `__. + + diff --git a/Documentation/Configuration/Index.rst b/Documentation/Configuration/Index.rst new file mode 100644 index 0000000..6a175f3 --- /dev/null +++ b/Documentation/Configuration/Index.rst @@ -0,0 +1,97 @@ +.. include:: ../Includes.txt + + +============================== +Configuration +============================== + +The plugin output doesn't look very cute after install. It is necessary to adopt it to your needs. Nevertheless it shouldn't be too hard. + +.. tip:: + + Have a look at the comments in the example template. + + +Make it work +============ + +Here are the needed steps described to make the plugin work. + +Additional work might be needed to adapt is to your needs. Usually this is limited to template work. + + +1. Include TypoScript +--------------------- + +Include the TypoScript in your template record or your site package. + + + +2. Include Plugin +----------------- + +Include the `Bookmark Pages` plugin as content element. This is just for testing and not usually what you want. + +Login and you should see some output. On the introduction package it looks like this: + + +.. figure:: ../Images/screenshot-fe.png + + The login box is not part of this extension. + +The plugin might be inserted more than once on a page. In that case mark `Is complementary` under `Plugin Options` +for all plugins except the first one. + + +Customization +============= + + +1. TypoScript and Templates +--------------------------- + +You may just copy the needed parts to your site package. + +The fluid template paths can be configured as usual using TypoScript. Have a look into the TypoScript template +(look for plugin.tx_wsbookmarkpages.view) + +The bookmarks might be stored in the browsers local storage allowing users to bookmark pages without being registered +on the site. Storing the bookmarks locally as well safes unnecessary server requests. The feature can be enabled +through the constants editor. + + +2. JavaScript and JQuery +------------------------ + +The provided example uses JQuery for ajax requests. JQuery is included by TypoScript. You might want to remove that with: + +.. code-block:: typoscript + + page.includeJSFooterlibs.bookmark_pages_jquery > + +If you don't use JQuery you have to adapt the JavaScript. +Have a look into Resources/Public/Scripts/JavaScript/bookmark_pages.js + + +3. Include in your page template +-------------------------------- + +The rendering of the bookmarks list might be something you want to include into your page template. This could be done +in a fluid template like this: + + +.. code-block:: html + + + + +Of course you want a bookmark button on every page. This can be done in fluid like this: + +.. code-block:: html + + + +You can place the snippet in any template not just the plugin templates. + diff --git a/Documentation/Images/screenshot-fe.png b/Documentation/Images/screenshot-fe.png new file mode 100644 index 0000000..2311908 Binary files /dev/null and b/Documentation/Images/screenshot-fe.png differ diff --git a/Documentation/Images/screenshot-styled-fe.png b/Documentation/Images/screenshot-styled-fe.png new file mode 100644 index 0000000..9d87f6c Binary files /dev/null and b/Documentation/Images/screenshot-styled-fe.png differ diff --git a/Documentation/Includes.txt b/Documentation/Includes.txt new file mode 100644 index 0000000..427bdb5 --- /dev/null +++ b/Documentation/Includes.txt @@ -0,0 +1,14 @@ +.. This is 'Includes.txt'. It is included at the very top of each and + every ReST source file in THIS documentation project (= manual). + +.. role:: aspect (emphasis) +.. role:: html(code) +.. role:: js(code) +.. role:: php(code) +.. role:: typoscript(code) + +.. role:: ts(typoscript) + :class: typoscript + +.. default-role:: code +.. highlight:: php diff --git a/Documentation/Index.rst b/Documentation/Index.rst new file mode 100644 index 0000000..ad29c61 --- /dev/null +++ b/Documentation/Index.rst @@ -0,0 +1,57 @@ +.. ================================================== +.. FOR YOUR INFORMATION +.. -------------------------------------------------- +.. -*- coding: utf-8 -*- with BOM. + +.. include:: Includes.txt + + +.. _start: + +============================================================= +Bookmark Pages +============================================================= + +.. only:: html + + :Classification: + bookmark_pages + + :Version: + |release| + + :Language: + en + + :Description: + Provides bookmarks functionality of local pages for logged in frontend users + + :Keywords: + bookmark page + + :Copyright: + 2016-2020 + + :Author: + René Fritz + + :License: + This document is published under the Open Publication License + available from http://www.opencontent.org/openpub/ + + :Rendered: + |today| + + The content of this document is related to TYPO3, + a GNU/GPL CMS/Framework available from `www.typo3.org `_. + + +.. toctree:: + :hidden: + + Sitemap/Index + Introduction/Index + Configuration/Index + AdministratorManual/Index + Support/Index + Links diff --git a/Documentation/Introduction/Index.rst b/Documentation/Introduction/Index.rst new file mode 100644 index 0000000..20902e9 --- /dev/null +++ b/Documentation/Introduction/Index.rst @@ -0,0 +1,33 @@ +.. include:: ../Includes.txt + + +Introduction +============ + +What does it do? +---------------- + +This is a TYPO3 plugin that provides bookmarks functionality of local pages. +For example this is very helpful for intranet websites. + +The plugin can be configured to use the browsers local storage allowing non registered users to use the feature too. +In case a website user likes to have the bookmarks available on different devices a frontend account might be used. +After logging in the bookmarks are saved to the server hence might be accessed from within any device. + +TypoScript and fluid templates are just examples. Adapt it to your needs. + +The extension was created by `René Fritz `__. The ownership has been transferred in +april 2021. + + +Screenshots +----------- + + +.. figure:: ../Images/screenshot-styled-fe.png + + The screenshot shows the functionality, the styling is not included. + + +You have a list with bookmarks which can be deleted and you can place a 'bookmark' button anywhere on the page. + diff --git a/Documentation/Settings.cfg b/Documentation/Settings.cfg new file mode 100644 index 0000000..eab4681 --- /dev/null +++ b/Documentation/Settings.cfg @@ -0,0 +1,17 @@ +# coding: utf-8 + +[general] +project = bookmark_pages +release = 2.0.1 +version = 2.0.1 + +[html_theme_options] +github_branch = master +github_repository = buepro/typo-bookmark_pages +project_contact = rb@buechler.pro +project_home = https://github.com/buepro/typo3-bookmark_pages +project_issues = https://github.com/buepro/typo3-bookmark_pages/issues +project_repository = https://github.com/buepro/typo3-bookmark_pages + +[extensions] +issue = https://github.com/buepro/typo3-bookmark_pages/issues/%s | Issue # diff --git a/Documentation/Sitemap/Index.rst b/Documentation/Sitemap/Index.rst new file mode 100644 index 0000000..85fa8c0 --- /dev/null +++ b/Documentation/Sitemap/Index.rst @@ -0,0 +1,12 @@ + +:template: sitemap.html + + +.. _Sitemap: + +======= +Sitemap +======= + +.. template 'sitemap.html' will insert the toctree as a sitemap here + below normal contents diff --git a/Documentation/Support/Index.rst b/Documentation/Support/Index.rst new file mode 100644 index 0000000..61d34e4 --- /dev/null +++ b/Documentation/Support/Index.rst @@ -0,0 +1,28 @@ +.. include:: ../Includes.txt + + +Support and Feedback +==================== + +The extension is provided as is. Please understand that I can't give an extensive amount of support time for free. + +Any feedback is always appreciated. + +So if you have questions or feedback there are several options: + +Bug Tracker +----------- + +For bug reports and feature requests: https://github.com/buepro/typo3-bookmark_pages/issues + +Commercial support +------------------ + +If you need commercial support, get in touch. + +Nice mails +---------- + +Some nice words fit every time - just drop me some kind words by mail to make me happy :-) + + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d7f1051 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,339 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..da45f01 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# Bookmark Pages TYPO3 Extension + +This is a TYPO3 extension that provides bookmarks functionality. In case the use from the browsers local storage is +disabled just logged in frontend users can bookmark pages. + +The extension was created by [René Fritz](https://github.com/colorcube). The ownership has been transferred in +april 2021. + +## Usage + +TypoScript and fluid templates are just examples. Adapt it to your needs. + +Further information: https://docs.typo3.org/p/wapplersystems/bookmark-pages/master/en-us/ + +### Dependencies + +* TYPO3 10.4 + +### Installation + +#### Installation using Composer + +In your Composer based TYPO3 project root, just do `composer require wapplersystems/bookmark-pages`. + +#### Installation as extension from TYPO3 Extension Repository (TER) + +Download and install the extension with the extension manager module. + diff --git a/Resources/Private/.htaccess b/Resources/Private/.htaccess new file mode 100644 index 0000000..96d0729 --- /dev/null +++ b/Resources/Private/.htaccess @@ -0,0 +1,11 @@ +# Apache < 2.3 + + Order allow,deny + Deny from all + Satisfy All + + +# Apache >= 2.3 + + Require all denied + diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf new file mode 100644 index 0000000..73c89a9 --- /dev/null +++ b/Resources/Private/Language/locallang.xlf @@ -0,0 +1,30 @@ + + + +
+ + + Yes + + + Default + + + Alternative + + + Bookmark this page + + + Is complementary + + + In case the plugin is rendered more times on a page this option should be set for all instances except the first one. + + + List type + + + + diff --git a/Resources/Private/Layouts/Default.html b/Resources/Private/Layouts/Default.html new file mode 100644 index 0000000..6eaa733 --- /dev/null +++ b/Resources/Private/Layouts/Default.html @@ -0,0 +1,23 @@ + + +
+ +
+
+ +
+ + + +
+
+
+ diff --git a/Resources/Private/Partials/ListItem.html b/Resources/Private/Partials/ListItem.html new file mode 100644 index 0000000..a3a499a --- /dev/null +++ b/Resources/Private/Partials/ListItem.html @@ -0,0 +1,15 @@ + + +
  • + + For the content to be substituted correctly the text link needs to have the class `bookmark-link` + and the remove item the data attribute `data-remove` assigned to. + + {bookmark.title} + × +
  • + + diff --git a/Resources/Private/Templates/Bookmarks/Index.html b/Resources/Private/Templates/Bookmarks/Index.html new file mode 100644 index 0000000..f4001a4 --- /dev/null +++ b/Resources/Private/Templates/Bookmarks/Index.html @@ -0,0 +1,34 @@ + + + + + + + + Render alternative list + + + + Render default list + + you may put this into your page template: + + + + + + +

    Bookmarks

    +

    {f:translate(key: 'bookmarkThisPage')}

    + +
    + + diff --git a/Resources/Public/Scripts/JavaScript/bookmark_pages.js b/Resources/Public/Scripts/JavaScript/bookmark_pages.js new file mode 100644 index 0000000..332872c --- /dev/null +++ b/Resources/Public/Scripts/JavaScript/bookmark_pages.js @@ -0,0 +1,162 @@ +(function ($) { + const settings = { + _$bookmarks: null, + _$listItemTemplate: null, + // If set bookmarks are stored locally in localStorage + _storeLocal: false, + // Time in seconds during which the bookmarks are valid hence not queried from server + _localStorageTTL: 3600, + init () { + this._$bookmarks = $('#bookmarks'); + this._$listItemTemplate = $($('#bookmark-template').html().trim()); + // Assign settings defined by host + let pluginSettings = this._$bookmarks.data('settings'); + if (typeof pluginSettings !== 'object') { + return; + } + if (pluginSettings.storeLocal !== 'undefined') { + this._storeLocal = Boolean(parseInt(pluginSettings.storeLocal)); + } + if (pluginSettings.localStorageTTL !== 'undefined') { + this._localStorageTTL = parseInt(pluginSettings.localStorageTTL); + } + }, + get updateAjaxUri () { return this._$bookmarks.data('update-ajaxuri'); }, + get addAjaxUri () { return this._$bookmarks.data('add-ajaxuri') }, + get removeAjaxUri () { return this._$bookmarks.data('remove-ajaxuri') }, + get currentBookmark () { return this._$bookmarks.data('bookmark') }, + get $listItemTemplate () { return this._$listItemTemplate }, + get storeLocal() { return this._storeLocal; }, + get localStorageTTL() { return this._localStorageTTL } + } + + const storage = { + /** + * @return JSON object from bookmarks list held in local storage from browser + */ + get list() { + return JSON.parse(localStorage.getItem('txBookmarkPagesBookmarks')) ?? {}; + }, + + /** + * @param bookmarks Array from bookmarks + */ + set list(bookmarks) { + if (settings.storeLocal) { + localStorage.setItem('txBookmarkPagesBookmarks', JSON.stringify(bookmarks)); + localStorage.setItem('txBookmarkPagesTimestamp', Date.now()); + localStorage.setItem('txBookmarkPagesReload', '0'); + } + }, + + /** + * @return {boolean} + */ + get isOutdated() { + // Check storage age + let $expired = true; + let timestamp = localStorage.getItem('txBookmarkPagesTimestamp'); + if (timestamp) { + $expired = ((Date.now() - timestamp) / 1000) > settings.localStorageTTL; + } + // Check if a reload is requested + let $reloadRequested = Boolean(parseInt(localStorage.getItem('txBookmarkPagesReload'))); + return $expired || $reloadRequested; + }, + + containsBookmark (bookmark) { + return typeof this.list[bookmark.id] === 'object'; + } + } + + const assistant = { + updateLinks () { + if (storage.containsBookmark(settings.currentBookmark)) { + $('.bookmark-this-page').addClass('is-bookmarked'); + } else { + $('.bookmark-this-page').removeClass('is-bookmarked'); + } + }, + + /** + * Ajax callback function to update the bookmarks list and the links to bookmark a page. + * + * @param ajaxResult Object with properties `list` and `isBookmarked` + */ + listQueryHandler (ajaxResult) { + this.initList(ajaxResult.bookmarks); + storage.list = ajaxResult.bookmarks; + this.updateLinks(); + }, + ajax (url, data = {}) { + data = {...data, 'tx_wsbookmarkpages_bookmarks[localBookmarks]': storage.list} + $.ajax({ + url: url, + type: 'post', + data: data + }).done($.proxy(this.listQueryHandler, this)); + }, + initList (bookmarks) { + let $bookmarksList = $('.bookmarks-list'); + $bookmarksList.empty(); + Object.values(bookmarks).forEach(bookmark => { + let $item = settings.$listItemTemplate.clone(); + $('.bookmark-link', $item) + .attr('title', bookmark.title) + .attr('href', bookmark.url) + .text(bookmark.title); + $('.bookmark-ajax-submit', $item).data('remove', bookmark.id) + $bookmarksList.append($item); + }); + }, + initListFromStorage () { + let bookmarks = storage.list; + this.initList(bookmarks); + } + } + + const bookmarks = { + init () { + if (settings.storeLocal && !storage.isOutdated) { + assistant.initListFromStorage(); + assistant.updateLinks(); + } else { + assistant.ajax(settings.updateAjaxUri); + } + }, + add () { + assistant.ajax(settings.addAjaxUri); + }, + remove (removeID) { + assistant.ajax( + settings.removeAjaxUri, + {'tx_wsbookmarkpages_bookmarks[id]': removeID ?? settings.currentBookmark.id} + ); + } + } + + /** + * Bind event handlers and initialize the app when DOM is ready + */ + $(function () { + // doing it this way: + // $('.bookmark-ajax-submit').on('click', function (event) { + // would not work for initially hidden elements + $('.bookmark-pages').on('click', '.bookmark-ajax-submit', function (event) { + event.preventDefault(); + let $this = $(this), + removeID = $(this).data('remove'); + if ($this.hasClass('bookmark-this-page')){ + bookmarks.add(); + } else if ($this.hasClass('remove-this-page')) { + bookmarks.remove() + } else if (removeID) { + bookmarks.remove(removeID) + } + }); + + // Initialize the app + settings.init(); + bookmarks.init(); + }); +})(jQuery); diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..af77f4d --- /dev/null +++ b/composer.json @@ -0,0 +1,37 @@ +{ + "name": "wapplersystems/bookmark-pages", + "type": "typo3-cms-extension", + "description": "A TYPO3 extension that provides bookmarks functionality of local pages.", + "keywords": [ + "TYPO3", + "extension", + "bookmarks" + ], + "license": "GPL-2.0-or-later", + "homepage": "", + "support": { + "issues": "" + }, + "require": { + "php": ">=7.0", + "typo3/cms-core": "^10.4" + }, + "require-dev": { + "roave/security-advisories": "dev-latest" + }, + "autoload": { + "psr-4": { + "WapplerSystems\\WsBookmarkPages\\": "Classes/" + } + }, + "replace": { + }, + "extra": { + "typo3/cms": { + "extension-key": "ws_bookmark_pages" + }, + "branch-alias": { + "dev-master": "2.x-dev" + } + } +} diff --git a/ext_emconf.php b/ext_emconf.php new file mode 100644 index 0000000..e7dd31e --- /dev/null +++ b/ext_emconf.php @@ -0,0 +1,38 @@ + 'Bookmark Pages', + 'description' => 'Provides bookmarks functionality of local pages for logged in frontend users.', + 'category' => 'plugin', + 'author' => 'René Fritz, Roman Büchler', + 'author_email' => 'r.fritz@colorcube.de, rb@buechler.pro', + 'author_company' => 'Colorcube, buechler.pro gmbh', + 'version' => '2.0.1', + 'state' => 'stable', + 'uploadfolder' => 0, + 'createDirs' => '', + 'modify_tables' => '', + 'clearCacheOnLoad' => 1, + 'constraints' => [ + 'depends' => [ + 'typo3' => '10.4.14-10.4.99', + 'typoscript_rendering' => '2.3.1-2.99.99', + ], + 'conflicts' => [], + 'suggests' => [ + 'news' => '*' + ], + ], + 'autoload' => [ + 'psr-4' => [ + 'WapplerSystems\\BookmarkPages\\' => 'Classes' + ] + ] +]; diff --git a/ext_icon.png b/ext_icon.png new file mode 100644 index 0000000..08329f1 Binary files /dev/null and b/ext_icon.png differ diff --git a/ext_localconf.php b/ext_localconf.php new file mode 100644 index 0000000..c5e0d69 --- /dev/null +++ b/ext_localconf.php @@ -0,0 +1,41 @@ + 'index, bookmark, delete, listEntries', + ], + // non-cacheable actions + [ + 'Bookmarks' => 'bookmark, delete, listEntries', + ] + ); + } else { + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'BookmarkPages', + 'Bookmarks', + [ + \WapplerSystems\BookmarkPages\Controller\BookmarksController::class => 'index, bookmark, delete, listEntries' + ], + [ + \WapplerSystems\BookmarkPages\Controller\BookmarksController::class => 'bookmark, delete, listEntries' + ] + ); + } +})(); diff --git a/ext_tables.sql b/ext_tables.sql new file mode 100644 index 0000000..f622475 --- /dev/null +++ b/ext_tables.sql @@ -0,0 +1,7 @@ +# +# Add field to table 'fe_users' +# +CREATE TABLE fe_users ( + tx_bookmarks_pages blob +); +