1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-26 08:04:38 +02:00

Add support for Lister user-specific bookmarks. This enables users to create their own private or shared bookmarks in Lister.

This commit is contained in:
Ryan Cramer
2019-06-12 15:37:26 -04:00
parent d977cabb82
commit eb299ee598
5 changed files with 1289 additions and 250 deletions

View File

@@ -0,0 +1,763 @@
<?php namespace ProcessWire;
class ListerBookmarks extends Wire {
/**
* Indicates public bookmark stored in module settings
*
*/
const typePublic = 0;
/**
* Indicates user-owned bookmark stored in user meta data
*
*/
const typeOwned = 1;
/**
* @var ProcessPageLister
*
*/
protected $lister;
/**
* Cached user settings, becomes array once loaded
*
* @var null|array
*
*/
protected $userSettings = null;
/**
* User ID associated with above user settings (just in case it changes)
*
* @var int
*
*/
protected $userSettingsID = 0;
/**
* Module config settings, becomes array once loaded
*
* @var null|array
*
*/
protected $moduleConfig = null;
/**
* Page that Lister bookmarks are for
*
* @var Page|null
*
*/
protected $page;
/**
* User that Lister bookmarks are for
*
* @var User|null
*
*/
protected $user;
public function __construct(Page $page, User $user) {
$this->page = $page;
$this->user = $user;
parent::__construct();
}
/**
* Set the Lister page that bookmarks will be for
*
* @param Page $page
*
*/
public function setPage(Page $page) {
$this->page = $page;
}
/**
* Set user that bookmarks will be for
*
* @param User $user
*
*/
public function setUser(User $user) {
$this->user = $user;
}
/**
* Get owned bookmarks
*
* @param int $userID
* @return array
*
*/
public function getOwnedBookmarks($userID = 0) {
$settings = $this->getUserSettings($userID);
$bookmarks = array();
if(!isset($settings['bookmarks'])) $settings['bookmarks'] = array();
foreach($settings['bookmarks'] as $bookmarkID => $bookmark) {
if(empty($bookmarkID) || empty($bookmark['title'])) continue;
$bookmarkID = $this->bookmarkStrID($bookmarkID, self::typeOwned);
$bookmark = $this->wakeupBookmark($bookmark, $bookmarkID, self::typeOwned);
if(!$bookmark) continue;
if($userID && !$userID != $this->user->id && empty($bookmark['share'])) continue;
$bookmarks[$bookmarkID] = $bookmark;
}
return $bookmarks;
}
/**
* Save owned bookmarks
*
* @param array $bookmarks
*
*/
public function saveOwnedBookmarks(array $bookmarks) {
$settings = $this->getUserSettings();
$saveBookmarks = array();
if(!isset($settings['bookmarks'])) $settings['bookmarks'] = array();
// prep for save
foreach($bookmarks as $bookmarkID => $bookmark) {
if(empty($bookmark['title'])) continue;
$bookmark = $this->sleepBookmark($bookmark);
$bookmarkID = $this->bookmarkStrID($bookmarkID, self::typeOwned);
$saveBookmarks[$bookmarkID] = $bookmark;
}
$bookmarks = $saveBookmarks;
if($settings['bookmarks'] !== $bookmarks) {
$settings['bookmarks'] = $bookmarks;
if(empty($bookmarks)) unset($settings['bookmarks']);
$this->message('Updated owned bookmarks', Notice::debug);
$this->saveUserSettings($settings);
}
}
/**
* Get users lister settings for current page
*
* @param int $userID
* @return array
*
*/
public function getUserSettings($userID = 0) {
$pageKey = $this->strID($this->page->id);
$userSettings = array();
if($userID === $this->user->id) $userID = 0;
if($userID) {
// other user
$user = $this->wire('users')->get((int) $userID);
if($user && $user->id) {
$userSettings = $user->meta('lister');
if(!is_array($userSettings)) $userSettings = array();
}
} else if($this->userSettings !== null && $this->user->id === $this->userSettingsID) {
$userSettings = $this->userSettings;
} else {
$userSettings = $this->user->meta('lister');
if(!is_array($userSettings)) $userSettings = array();
$this->userSettings = $userSettings;
$this->userSettingsID = $this->user->id;
}
if(!isset($userSettings[$pageKey])) {
$userSettings[$pageKey] = array();
if(!$userID) $this->userSettings = $userSettings;
}
return $userSettings[$pageKey];
}
/**
* Save user settings for current page
*
* @param array $settings
* @return bool
*
*/
public function saveUserSettings(array $settings) {
$pageKey = $this->strID($this->page->id);
$userSettings = $this->getUserSettings();
$userSettings[$pageKey] = $settings;
foreach($userSettings as $key => $value) {
if(!$this->isID($key)) continue; // not a pageKey setting
// remove empty keys and settings
if(is_array($value)) {
foreach($value as $k => $v) {
if(empty($v)) unset($value[$k]); // i.e. an empty $value['bookmarks']
}
}
$userSettings[$key] = $value;
if(!$this->isValidPageKey($key)) $value = array(); // maintenance
if(empty($value)) unset($userSettings[$key]);
}
// if no changes, exit now
if($userSettings === $this->userSettings) return false;
// save user settings
$this->user->meta('lister', $userSettings);
$this->userSettings = $userSettings;
$this->message('Updated user settings', Notice::debug);
return true;
}
/**
* Get public bookmarks (from module config)
*
* @return array
*
*/
public function getPublicBookmarks() {
$pageKey = $this->strID($this->page->id);
$moduleConfig = $this->getModuleConfig();
$bookmarks = array();
if(!isset($moduleConfig['bookmarks'][$pageKey])) {
$moduleConfig['bookmarks'][$pageKey] = array();
}
foreach($moduleConfig['bookmarks'][$pageKey] as $bookmarkID => $bookmark) {
if(empty($bookmarkID) || empty($bookmark['title'])) continue;
$bookmarkID = $this->bookmarkStrID($bookmarkID, self::typePublic);
$bookmark = $this->wakeupBookmark($bookmark, $bookmarkID, self::typePublic);
if($bookmark) $bookmarks[$bookmarkID] = $bookmark;
}
$moduleConfig['bookmarks'][$pageKey] = $bookmarks;
$this->setModuleConfig($moduleConfig);
return $bookmarks;
}
/**
* Save public bookmarks (to module config)
*
* @param array $bookmarks
* @return bool
*
*/
public function savePublicBookmarks(array $bookmarks) {
$pageKey = $this->strID($this->page->id);
$moduleConfig = $this->getModuleConfig();
$saveBookmarks = array();
if(isset($moduleConfig['bookmarks'][$pageKey])) {
// if given bookmarks are identical to what is in module config, there are no changes to save
if($bookmarks === $moduleConfig['bookmarks'][$pageKey]) return false;
}
// prep bookmarks for save
foreach($bookmarks as $bookmarkID => $bookmark) {
// don't save bookmarks that lack a title or of the wrong type
if(empty($bookmark['title'])) continue;
if($bookmark['type'] != self::typePublic) continue;
// assign IDs for any bookmarks that don't have them
if(empty($bookmarkID)) {
$bookmarkID = time();
while(isset($bookmarks["_$bookmarkID"])) $bookmarkID++;
}
$bookmarkID = $this->bookmarkStrID($bookmarkID, self::typePublic);
$saveBookmarks[$bookmarkID] = $this->sleepBookmark($bookmark);
}
$bookmarks = $saveBookmarks;
if(empty($bookmarks)) {
// remove if empty...
unset($moduleConfig['bookmarks'][$pageKey]);
} else {
// ...otherwise populate
$moduleConfig['bookmarks'][$pageKey] = $bookmarks;
}
// check if any bookmarks in module config are for pages that no longer exist
foreach($moduleConfig['bookmarks'] as $key => $bookmarks) {
if(!$this->isValidPageKey($key)) {
$this->warning("Removed expired bookmark for page $key", Notice::debug);
unset($moduleConfig['bookmarks'][$key]);
}
}
// if there are changes, save them
return $this->saveModuleConfig($moduleConfig);
}
/**
* Save all bookmarks (whether public or owned)
*
* @param array $allBookmarks
*
*/
public function saveBookmarks(array $allBookmarks) {
// save owned (user) bookmarks
$ownedBookmarks = $this->filterBookmarksByType($allBookmarks, self::typeOwned);
$this->saveOwnedBookmarks($ownedBookmarks);
if($this->user->isSuperuser()) {
$publicBookmarks = $this->filterBookmarksByType($allBookmarks, self::typePublic);
$this->savePublicBookmarks($publicBookmarks);
}
}
/**
* Get all bookmarks (public and owned)
*
* @return array
*
*/
public function getAllBookmarks() {
$publicBookmarks = $this->getPublicBookmarks();
$ownedBookmarks = $this->getOwnedBookmarks();
$allBookmarks = array_merge($publicBookmarks, $ownedBookmarks);
return $allBookmarks;
}
/**
* Get configured bookmarks allowed for current user, indexed by bookmark ID (int)
*
* @return array
*
*/
public function getBookmarks() {
$bookmarks = array();
foreach($this->getAllBookmarks() as $bookmarkID => $bookmark) {
if(!$this->isBookmarkViewable($bookmark)) continue;
$bookmarks[$bookmarkID] = $bookmark;
}
return $bookmarks;
}
/**
* Get a bookmark by ID (whether public or owned)
*
* @param string|int $bookmarkID
* @param int|null $type
* @return array|null
*
*/
public function getBookmark($bookmarkID, $type = null) {
if($type === null && strpos($bookmarkID, $this->typePrefix(self::typeOwned)) !== false) {
$type = self::typeOwned;
}
if($type === self::typeOwned) {
$prefix = $this->typePrefix(self::typeOwned);
if(strpos($bookmarkID, $prefix) > 0) {
// 123O456 where 123 is user ID and 456 is bookmark ID
list($userID, $bookmarkID) = explode($prefix, $bookmarkID);
$userID = (int) $userID;
if($userID === $this->user->id) $userID = 0;
$bookmarkID = $prefix . ((int) $bookmarkID);
} else {
$bookmarkID = $this->_bookmarkID($bookmarkID);
$userID = 0;
}
$bookmarks = $this->getOwnedBookmarks($userID);
$bookmarkID = $this->bookmarkStrID($bookmarkID, self::typeOwned);
} else {
$bookmarks = $this->getPublicBookmarks();
$bookmarkID = $this->bookmarkStrID($bookmarkID, self::typePublic);
}
$bookmark = isset($bookmarks[$bookmarkID]) ? $bookmarks[$bookmarkID] : null;
return $bookmark;
}
/**
* Get the URL for a bookmark
*
* @param string $bookmarkID
* @param User|null $user
* @return string
*
*/
public function getBookmarkUrl($bookmarkID, $user = null) {
if(strpos($bookmarkID, $this->typePrefix(self::typeOwned)) === 0) {
if($user) $bookmarkID = $user->id . $bookmarkID;
} else {
$bookmarkID = $this->intID($bookmarkID);
}
return $this->page->url . "bm$bookmarkID";
}
/**
* Get the URL for a bookmark
*
* @param string $bookmarkID
* @return string
*
*/
public function getBookmarkEditUrl($bookmarkID) {
return $this->page->url . "edit-bookmark/?bookmark=$bookmarkID";
}
/**
* Get the title for the given bookmark ID or bookmark array
*
* @param int|array $bookmarkID
* @return mixed|string
* @throws WireException
*
*/
public function getBookmarkTitle($bookmarkID) {
if(is_array($bookmarkID)) {
$bookmark = $bookmarkID;
} else {
$bookmark = $this->getBookmark($bookmarkID);
if(empty($bookmark)) return '';
}
$languages = $this->wire('languages');
$title = $bookmark['title'];
if($languages) {
$user = $this->wire('user');
if(!$user->language->isDefault() && !empty($bookmark["title$user->language"])) {
$title = $bookmark["title$user->language"];
}
}
return $title;
}
/**
* Delete a bookmark by ID
*
* @param int $bookmarkID
* @return bool
*
*/
public function deleteBookmarkByID($bookmarkID) {
$bookmark = $this->getBookmark($bookmarkID);
if(!$bookmark) return false;
if(!$this->isBookmarkDeletable($bookmark)) return false;
if($bookmark['type'] == self::typeOwned) {
$bookmarks = $this->getOwnedBookmarks();
unset($bookmarks[$bookmarkID]);
$this->saveOwnedBookmarks($bookmarks);
} else {
$bookmarks = $this->getPublicBookmarks();
unset($bookmarks[$bookmarkID]);
$this->savePublicBookmarks($bookmarks);
}
return true;
}
/**
* Filter bookmarks, removing those that are not of the requested type
*
* @param array $allBookmarks
* @param int $type
* @return array
*
*/
public function filterBookmarksByType(array $allBookmarks, $type) {
$filteredBookmarks = array();
foreach($allBookmarks as $key => $bookmark) {
if(!isset($bookmark['type'])) $bookmark['type'] = self::typePublic;
if($bookmark['type'] != $type) continue;
$filteredBookmarks[$key] = $bookmark;
}
return $filteredBookmarks;
}
/**
* Filter bookmarks, removing those user does not have access to
*
* @param array $bookmarks
* @return array
*
*/
public function filterBookmarksByAccess(array $bookmarks) {
foreach($bookmarks as $key => $bookmark) {
if(!$this->isBookmarkViewable($bookmark)) unset($bookmarks[$key]);
}
return $bookmarks;
}
/**
* Is the given bookmark editable?
*
* @param array $bookmark
* @return bool
*
*/
public function isBookmarkEditable(array $bookmark) {
if($this->user->isSuperuser()) return true;
if($bookmark['type'] == self::typePublic) return false;
return true;
}
/**
* Is the given bookmark viewable?
*
* @param array $bookmark
* @return bool
*
*/
public function isBookmarkViewable(array $bookmark) {
if(empty($bookmark['roles'])) return true;
if($this->user->isSuperuser()) return true;
$userRoles = $this->user->roles;
$viewable = false;
foreach($bookmark['roles'] as $roleID) {
foreach($userRoles as $userRole) {
if($userRole->id == $roleID) {
$viewable = true;
break;
}
}
}
return $viewable;
}
/**
* Is the given bookmark deletable?
*
* @param array $bookmark
* @return bool
*
*/
public function isBookmarkDeletable(array $bookmark) {
return $this->isBookmarkEditable($bookmark);
}
/**
* Get a template array for a bookmark
*
* @param array $bookmark
* @return array
*
*/
public function _bookmark(array $bookmark = array()) {
$template = array(
'id' => '',
'title' => '',
'desc' => '',
'selector' => '',
'columns' => array(),
'sort' => '',
'type' => self::typePublic,
'roles' => array(),
'share' => false,
);
return empty($bookmark) ? $template : array_merge($template, $bookmark);
}
/**
* Sanitize a bookmark ID
*
* @param string|array $bookmarkID
* @return string
*
*/
public function _bookmarkID($bookmarkID) {
if(is_array($bookmarkID)) {
$bookmark = $bookmarkID;
$type = $bookmark['type'];
$bookmarkID = $bookmark['id'];
} else {
$type = self::typePublic;
$ownedPrefix = $this->typePrefix(self::typeOwned);
if(strpos($bookmarkID, $ownedPrefix) !== false) {
list($userID, $bookmarkID) = explode($ownedPrefix, $bookmarkID);
$userID = empty($userID) ? '' : (int) $userID;
$bookmarkID = $userID . $ownedPrefix . ((int) $bookmarkID);
return $bookmarkID;
} else {
$bookmarkID = ltrim($bookmarkID, $this->typePrefix(self::typePublic));
}
}
if(!ctype_digit("$bookmarkID")) return '';
return $this->typePrefix($type) . ((int) $bookmarkID);
}
protected function getModuleConfig() {
if($this->moduleConfig === null) {
$this->moduleConfig = $this->wire('modules')->getConfig('ProcessPageLister');
}
if(!isset($this->moduleConfig['bookmarks'])) $this->moduleConfig['bookmarks'] = array();
return $this->moduleConfig;
}
protected function setModuleConfig(array $moduleConfig) {
$this->moduleConfig = $moduleConfig;
}
protected function saveModuleConfig(array $moduleConfig) {
if($moduleConfig === $this->moduleConfig) return false;
$this->wire('modules')->saveConfig('ProcessPageLister', $moduleConfig);
$this->moduleConfig = $moduleConfig;
$this->message('Updated module config (bookmarks)', Notice::debug);
return true;
}
protected function wakeupBookmark(array $bookmark, $bookmarkID, $type = null) {
if(empty($bookmarkID) || empty($bookmark['title'])) return false;
if($type === null) $type = $this->idType($bookmarkID);
$bookmarkID = $this->bookmarkStrID($bookmarkID, $type);
$bookmark = $this->_bookmark($bookmark);
$bookmark['type'] = $type;
$bookmark['id'] = $bookmarkID;
$bookmark['url'] = $this->getBookmarkUrl($bookmarkID);
$bookmark['editUrl'] = $this->getBookmarkEditUrl($bookmarkID);
$bookmark['share'] = empty($bookmark['share']) ? false : true;
//$bookmark['shareUrl'] = $bookmark['url'];
return $bookmark;
}
protected function sleepBookmark(array $bookmark) {
unset($bookmark['id'], $bookmark['url'], $bookmark['editUrl']);
if($bookmark['type'] === self::typeOwned) unset($bookmark['roles']);
if(empty($bookmark['share'])) unset($bookmark['share']);
return $bookmark;
}
/**
* Given an id or string key, return an int ID
*
* @param string|int $val
* @return int
*
*/
public function intID($val) {
return (int) ltrim($val, '_O');
}
/**
* Given an id or string key, return an string ID (with leading underscore)
*
* @param string|int $val
* @return int
*
*/
public function strID($val) {
return '_' . ltrim($val, '_O');
}
/**
* Given an id or string key, return an bookmark string ID
*
* @param string|int $val
* @param int $type
* @return int
*
*/
public function bookmarkStrID($val, $type) {
return ($type === self::typeOwned ? 'O' : '_') . ltrim($val, '_O');
}
/**
* Does the given string value represent an ID? If yes, return ID, otherwise return false.
*
* @param string $val
* @return bool|int
*
*/
public function isID($val) {
$val = trim($val, '_O');
return ctype_digit($val) ? (int) $val : false;
}
/**
* Get the type from the given id string
*
* @param string $val
* @return int
*
*/
public function idType($val) {
if(strpos($val, 'O') === 0) return self::typeOwned;
return self::typePublic;
}
/**
* Get the prefix for the given bookmark type
*
* @param int $type
* @return string
*
*/
public function typePrefix($type) {
if($type == self::typePublic) return '_';
if($type == self::typeOwned) return 'O';
return '';
}
/**
* Is the given page ID or key valid and existing?
*
* @param int|string $val
* @return bool
*
*/
public function isValidPageKey($val) {
$id = $this->intID($val);
return $id === $this->page->id || $this->wire('pages')->get($id)->id > 0;
}
/**
* Return a readable selector from bookmark for output purposes
*
* @param array $bookmark
* @return string
*
*/
public function readableBookmarkSelector(array $bookmark) {
$selector = $bookmark['selector'];
if(strpos($selector, 'template=') !== false && preg_match('/template=([\d\|]+)/', $selector, $matches)) {
// make templates readable, for output purposes
$t = '';
foreach(explode('|', $matches[1]) as $templateID) {
$template = $this->wire('templates')->get((int) $templateID);
$t .= ($t ? '|' : '') . ($template ? $template->name : $templateID);
}
$selector = str_replace($matches[0], "template=$t", $selector);
}
if(!empty($bookmark['sort'])) $selector .= ($selector ? ", " : "") . "sort=$bookmark[sort]";
return $selector;
}
}

View File

@@ -1 +1 @@
#ProcessLister{margin-top:-2px}#ProcessListerResultsTab{padding-top:.5em}#ProcessListerResults>form.InputfieldForm{margin-bottom:0}#ProcessListerResults #ProcessListerTable{clear:both;overflow-x:auto}#ProcessListerResults #ProcessListerTable>div{margin-top:0}#ProcessListerResults #ProcessListerTable table.ProcessListerTable{clear:both;margin-top:0}#ProcessListerResults #ProcessListerTable table.ProcessListerTable td table{width:100%}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>thead th{font-size:.8571428571em}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>thead th i{font-size:14px}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>thead th strong{white-space:nowrap}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>thead th b,#ProcessListerResults #ProcessListerTable table.ProcessListerTable>thead .th b{display:none}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>thead th:first-child{padding-left:0}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td{font-size:.9285714286em}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td:first-child,#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td:first-child>a{padding-left:0}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td ul.MarkupFieldtype{margin:0;padding-left:0}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td ul.MarkupFieldtype>li{list-style:none;margin:0;padding-left:0}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td ul.MarkupFieldtype>li+li{border-top:1px solid #eee}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td>*:first-child,#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td .col_preview>*:first-child{margin-top:0}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td>*:last-child,#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td .col_preview>*:last-child{margin-bottom:0}#ProcessListerResults .PageListerActions{display:none;line-height:1.3em;text-transform:lowercase}#ProcessListerResults .PageListerActions a.PageExtra{margin-bottom:3px}#ProcessListerResults .PageListerActions a.PageExtras i{margin-left:3px;margin-right:2px}#ProcessListerResults .PageListerActions a.PageExtras.open i{margin-left:2px;margin-right:3px}#ProcessListerResults .row_message{display:inline}#ProcessListerResults .PageListStatusUnpublished{text-decoration:line-through;opacity:.5}#ProcessListerResults .PageListStatusHidden{opacity:.5}#ProcessListerResults .MarkupPagerNav{float:right}#ProcessListerResults .nobr{white-space:nowrap}#ProcessListerResults table+.MarkupPagerNav{margin:0}#ProcessListerResults .datetime{white-space:nowrap}#ProcessListerResults td:not(.col_editing) .InputfieldHasFileList .InputfieldHeader{display:none}#ProcessListerResults td:not(.col_editing) .InputfieldHasFileList .InputfieldContent{padding:5px;border:none;margin-top:5px;margin-bottom:5px}@media only screen and (max-width: 767px){#ProcessListerResults table.ProcessListerTable+.MarkupPagerNav{margin-bottom:1em}#ProcessListerResults .MarkupPagerNav{float:none}}.AdminDataTable p{margin:1em 0}#ProcessListerResults+a button{float:left;margin-right:0;margin-top:0}#ProcessListerSpinner{margin-left:.5em;font-size:20px;position:relative}#ProcessListerSpinner i{position:absolute;top:-15px;left:0}.pw-content .lister_headline,#content .lister_headline{float:left;margin-top:1em}@media only screen and (max-width: 767px){.pw-content .lister_headline,#content .lister_headline{float:none;clear:both}}#lister_open_cnt{display:none}#filters_spinner{float:right;margin-top:4px}#ProcessListerRefreshTab{float:right}#ProcessListerSelector{display:inline-block}p.version{clear:both;padding-top:1em}#ProcessListerScript{display:none}#table_bookmarks{margin-top:.5em}.AdminThemeReno a.lister-lightbox{padding:0 !important}.AdminThemeReno a.lister-lightbox img{margin:0 !important}.AdminThemeReno #content .lister_headline{margin-top:.5em}#ProcessListerTable .InputfieldFile .InputfieldContent,#ProcessListerTable .InputfieldFile .InputfieldHeader,#ProcessListerTable .InputfieldImage .InputfieldContent,#ProcessListerTable .InputfieldImage .InputfieldHeader{background:inherit}#ProcessListerTable .InputfieldFile .InputfieldHeader,#ProcessListerTable .InputfieldImage .InputfieldHeader{padding-left:0;padding-right:0;padding-top:0}#ProcessListerTable .InputfieldFile .gridImage__tooltip table th,#ProcessListerTable .InputfieldFile .gridImage__tooltip table td,#ProcessListerTable .InputfieldImage .gridImage__tooltip table th,#ProcessListerTable .InputfieldImage .gridImage__tooltip table td{padding:0} #ProcessLister{margin-top:-2px}#ProcessListerResultsTab{padding-top:.5em}#ProcessListerResults>form.InputfieldForm{margin-bottom:0}#ProcessListerResults #ProcessListerTable{clear:both;overflow-x:auto}#ProcessListerResults #ProcessListerTable>div{margin-top:0}#ProcessListerResults #ProcessListerTable table.ProcessListerTable{clear:both;margin-top:0}#ProcessListerResults #ProcessListerTable table.ProcessListerTable td table{width:100%}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>thead th{font-size:.8571428571em}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>thead th i{font-size:14px}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>thead th strong{white-space:nowrap}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>thead th b,#ProcessListerResults #ProcessListerTable table.ProcessListerTable>thead .th b{display:none}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>thead th:first-child{padding-left:0}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td{font-size:.9285714286em}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td:first-child,#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td:first-child>a{padding-left:0}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td ul.MarkupFieldtype{margin:0;padding-left:0}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td ul.MarkupFieldtype>li{list-style:none;margin:0;padding-left:0}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td ul.MarkupFieldtype>li+li{border-top:1px solid #eee}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td>*:first-child,#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td .col_preview>*:first-child{margin-top:0}#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td>*:last-child,#ProcessListerResults #ProcessListerTable table.ProcessListerTable>tbody>tr>td .col_preview>*:last-child{margin-bottom:0}#ProcessListerResults .PageListerActions{display:none;line-height:1.3em;text-transform:lowercase}#ProcessListerResults .PageListerActions a.PageExtra{margin-bottom:3px}#ProcessListerResults .PageListerActions a.PageExtras i{margin-left:3px;margin-right:2px}#ProcessListerResults .PageListerActions a.PageExtras.open i{margin-left:2px;margin-right:3px}#ProcessListerResults .row_message{display:inline}#ProcessListerResults .PageListStatusUnpublished{text-decoration:line-through;opacity:.5}#ProcessListerResults .PageListStatusHidden{opacity:.5}#ProcessListerResults .MarkupPagerNav{float:right}#ProcessListerResults .nobr{white-space:nowrap}#ProcessListerResults table+.MarkupPagerNav{margin:0}#ProcessListerResults .datetime{white-space:nowrap}#ProcessListerResults td:not(.col_editing) .InputfieldHasFileList .InputfieldHeader{display:none}#ProcessListerResults td:not(.col_editing) .InputfieldHasFileList .InputfieldContent{padding:5px;border:none;margin-top:5px;margin-bottom:5px}@media only screen and (max-width: 767px){#ProcessListerResults table.ProcessListerTable+.MarkupPagerNav{margin-bottom:1em}#ProcessListerResults .MarkupPagerNav{float:none}}.AdminDataTable p{margin:1em 0}#ProcessListerResults+a button{float:left;margin-right:0;margin-top:0}#ProcessListerSpinner{margin-left:.5em;font-size:20px;position:relative}#ProcessListerSpinner i{position:absolute;top:-15px;left:0}.pw-content .lister_headline,#content .lister_headline{float:left;margin-top:1em}@media only screen and (max-width: 767px){.pw-content .lister_headline,#content .lister_headline{float:none;clear:both}}#lister_open_cnt{display:none}#filters_spinner{float:right;margin-top:4px}#ProcessListerRefreshTab{float:right}#ProcessListerSelector{display:inline-block}#ProcessListerResultNotes+#ProcessListerSelector{margin-top:0}p.version{clear:both;padding-top:1em}#ProcessListerScript{display:none}#tab_bookmarks table tr>td:first-child{width:30%}#tab_bookmarks table tr>td:nth-child(2){width:40%}.AdminThemeReno a.lister-lightbox{padding:0 !important}.AdminThemeReno a.lister-lightbox img{margin:0 !important}.AdminThemeReno #content .lister_headline{margin-top:.5em}#ProcessListerTable .InputfieldFile .InputfieldContent,#ProcessListerTable .InputfieldFile .InputfieldHeader,#ProcessListerTable .InputfieldImage .InputfieldContent,#ProcessListerTable .InputfieldImage .InputfieldHeader{background:inherit}#ProcessListerTable .InputfieldFile .InputfieldHeader,#ProcessListerTable .InputfieldImage .InputfieldHeader{padding-left:0;padding-right:0;padding-top:0}#ProcessListerTable .InputfieldFile .gridImage__tooltip table th,#ProcessListerTable .InputfieldFile .gridImage__tooltip table td,#ProcessListerTable .InputfieldImage .gridImage__tooltip table th,#ProcessListerTable .InputfieldImage .gridImage__tooltip table td{padding:0}

View File

@@ -146,6 +146,14 @@ class ProcessPageLister extends Process implements ConfigurableModule {
*/ */
protected $openPageIDs = array(); protected $openPageIDs = array();
/**
* Additional notes about the results to display underneath them
*
* @var array
*
*/
protected $resultNotes = array();
/** /**
* Initalize module config variables * Initalize module config variables
* *
@@ -348,35 +356,25 @@ class ProcessPageLister extends Process implements ConfigurableModule {
/** /**
* Check for a bookmark specified in GET variable $n * Check for a bookmark specified in GET variable $n
* *
* return null|int|bool Returns NULL if not applicable, boolean false if bookmark not found, or integer of bookmark ID if applied * @param string $bookmarkID
* @return null|int|bool Returns NULL if not applicable, boolean false if bookmark not found, or integer of bookmark ID if applied
* *
*/ */
public function checkBookmark() { public function checkBookmark($bookmarkID = '') {
$n = (int) $this->wire('input')->get('bookmark'); if(!$bookmarkID) $bookmarkID = $this->wire('input')->get('bookmark');
if(!$n) return null; if(!$bookmarkID) return null;
$bookmarks = $this->getBookmarksInstance()->getBookmarks(); $bookmarks = $this->getBookmarksInstance();
if(!isset($bookmarks[$n])) return false; $bookmarkID = $bookmarks->_bookmarkID($bookmarkID);
$bookmark = $bookmarks[$n]; if(!$bookmarkID) return false;
$user = $this->wire('user'); $bookmark = $bookmarks->getBookmark($bookmarkID);
if(!$user->isSuperuser() && count($bookmark['roles'])) { if(!$bookmark || !$bookmarks->isBookmarkViewable($bookmark)) return false;
$allow = false;
foreach($bookmark['roles'] as $roleID) {
foreach($user->roles as $role) {
if($role->id == $roleID) {
$allow = true;
break;
}
}
}
if(!$allow) return false;
}
$this->sessionClear(); $this->sessionClear();
$this->set('defaultSelector', $bookmark['selector']); $this->set('defaultSelector', $bookmark['selector']);
$this->set('defaultSort', $bookmark['sort']); $this->set('defaultSort', $bookmark['sort']);
$this->sessionSet('sort', $bookmark['sort']); $this->sessionSet('sort', $bookmark['sort']);
$this->set('columns', $bookmark['columns']); $this->set('columns', $bookmark['columns']);
$this->headline($this->wire('page')->title . ' - ' . $bookmark['title']);
return $n; return $bookmarkID;
} }
/** /**
@@ -1009,7 +1007,9 @@ class ProcessPageLister extends Process implements ConfigurableModule {
// ok, override // ok, override
} else { } else {
$selectors->remove($s); $selectors->remove($s);
if($value && $showIncludeWarnings) $this->error($this->_("Specified 'include=' mode is not allowed here.")); if($value && $showIncludeWarnings) {
$this->resultNotes[] = $this->_("Specified 'include=' mode is not allowed here.") . " (include=$value)";
}
$changed = true; $changed = true;
} }
} }
@@ -1039,7 +1039,9 @@ class ProcessPageLister extends Process implements ConfigurableModule {
// include=unpublished is allowed // include=unpublished is allowed
} else if($includeMode == 'unpublished') { } else if($includeMode == 'unpublished') {
// include=unpublished is not allowed // include=unpublished is not allowed
if($showIncludeWarnings) $this->error($this->_("Not all specified templates are editable. Only 'include=hidden' is allowed")); if($showIncludeWarnings) {
$this->resultNotes[] = $this->_("Not all specified templates are editable. Only 'include=hidden' is allowed");
}
$includeSelector->value = 'hidden'; $includeSelector->value = 'hidden';
$changed = true; $changed = true;
} }
@@ -1049,7 +1051,9 @@ class ProcessPageLister extends Process implements ConfigurableModule {
// only allow a max include mode of hidden // only allow a max include mode of hidden
// regardless of edit access // regardless of edit access
if($includeMode != 'hidden') { if($includeMode != 'hidden') {
if($showIncludeWarnings) $this->error($this->_("No templates specified so 'include=hidden' is max allowed include mode")); if($showIncludeWarnings) {
$this->resultNotes[] = $this->_("No templates specified so 'include=hidden' is max allowed include mode");
}
$includeSelector->value = 'hidden'; $includeSelector->value = 'hidden';
$changed = true; $changed = true;
} }
@@ -1783,9 +1787,15 @@ class ProcessPageLister extends Process implements ConfigurableModule {
"<div id='ProcessListerTable'>$tableOut</div>" . "<div id='ProcessListerTable'>$tableOut</div>" .
$pagerOut; $pagerOut;
if(count($this->resultNotes)) {
$notes = array();
foreach($this->resultNotes as $note) {
$notes[] = wireIconMarkup('warning') . ' ' . $this->wire('sanitizer')->entities1($note);
}
$out .= "<p id='ProcessListerResultNotes' class='detail'>" . implode('<br />', $notes) . "</p>";
}
if($this->wire('config')->debug) { if($this->wire('config')->debug) {
$out .= "<p id='ProcessListerSelector' class='notes'>" . $this->wire('sanitizer')->entities($this->finalSelector) . "</p>"; $out .= "<p id='ProcessListerSelector' class='notes'>" . $this->wire('sanitizer')->entities($this->finalSelector) . "</p>";
//$out .= "<p id='ProcessListerSelector' class='notes'>" . $this->wire('sanitizer')->entities($findSelector) . "</p>";
} }
if(!$this->editOption) { if(!$this->editOption) {
@@ -1889,9 +1899,7 @@ class ProcessPageLister extends Process implements ConfigurableModule {
} }
if($this->allowBookmarks) { if($this->allowBookmarks) {
$bookmarks = $this->getBookmarksInstance(); $bookmarks = $this->getBookmarksInstance();
if($this->wire('user')->isSuperuser() || count($bookmarks->getBookmarks())) { $out .= $bookmarks->buildBookmarkListForm()->render();
$out .= $bookmarks->buildBookmarkListForm()->render();
}
} }
$out .= $this->renderExtras(); $out .= $this->renderExtras();
} }
@@ -2096,7 +2104,27 @@ class ProcessPageLister extends Process implements ConfigurableModule {
*/ */
public function ___executeEditBookmark() { public function ___executeEditBookmark() {
if(!$this->allowBookmarks) throw new WireException("Bookmarks are disabled"); if(!$this->allowBookmarks) throw new WireException("Bookmarks are disabled");
return $this->getBookmarksInstance()->editBookmark(); return $this->getBookmarksInstance()->executeEditBookmark();
}
/**
* Catch-all for bookmarks
*
* @return string
* @throws Wire404Exception
* @throws WireException
*
*/
public function ___executeUnknown() {
$bookmarkID = $this->wire('input')->urlSegment1;
if(strpos($bookmarkID, 'bm') === 0) {
$bookmarks = $this->getBookmarksInstance();
$bookmarkID = $bookmarks->_bookmarkID(ltrim($bookmarkID, 'bm'));
} else {
$bookmarkID = '';
}
if(!$bookmarkID || !$this->checkBookmark($bookmarkID)) throw new Wire404Exception();
return $this->execute();
} }
/** /**
@@ -2123,13 +2151,14 @@ class ProcessPageLister extends Process implements ConfigurableModule {
$user = $this->wire('user'); $user = $this->wire('user');
$languageID = $languages && !$user->language->isDefault() ? $user->language->id : ''; $languageID = $languages && !$user->language->isDefault() ? $user->language->id : '';
foreach($bookmarks as $n => $bookmark) { foreach($bookmarks as $bookmarkID => $bookmark) {
$name = $bookmark['title']; $name = $bookmark['title'];
$icon = $bookmark['type'] ? 'user-circle-o' : 'search';
if($languageID && !empty($bookmark["title$languageID"])) $name = $bookmark["title$languageID"]; if($languageID && !empty($bookmark["title$languageID"])) $name = $bookmark["title$languageID"];
$item = array( $item = array(
'id' => $n, 'id' => $bookmarkID,
'name' => $name, 'name' => $name,
'icon' => 'search', 'icon' => $icon,
); );
$items[] = $item; $items[] = $item;
} }

View File

@@ -201,6 +201,9 @@
#ProcessListerSelector { #ProcessListerSelector {
display: inline-block; display: inline-block;
} }
#ProcessListerResultNotes + #ProcessListerSelector {
margin-top: 0;
}
p.version { p.version {
clear: both; clear: both;
@@ -211,10 +214,18 @@ p.version {
display: none; display: none;
} }
#table_bookmarks { #tab_bookmarks {
margin-top: 0.5em; table tr {
> td:first-child {
width: 30%;
}
> td:nth-child(2) {
width: 40%;
}
}
} }
.AdminThemeReno { .AdminThemeReno {
a.lister-lightbox { a.lister-lightbox {
padding: 0 !important; padding: 0 !important;

View File

@@ -7,52 +7,74 @@
* *
*/ */
class ProcessPageListerBookmarks extends Wire { class ProcessPageListerBookmarks extends Wire {
protected $lister; /**
* @var ProcessPageLister
*
*/
protected $lister;
/**
* @var ListerBookmarks
*
*/
protected $bookmarks;
/**
* @var Page
*
*/
protected $page;
/**
* @var User
*
*/
protected $user;
/**
* Construct
*
* @param ProcessPageLister $lister
*
*/
public function __construct(ProcessPageLister $lister) { public function __construct(ProcessPageLister $lister) {
require_once(__DIR__ . '/ListerBookmarks.php');
$this->lister = $lister; $this->lister = $lister;
$this->page = $lister->wire('page');
$this->user = $lister->wire('user');
$this->bookmarks = new ListerBookmarks($this->page, $this->user);
parent::__construct();
} }
/** /**
* Get configured bookmarks allowed for current user * @return ListerBookmarks
* *
* @return array
*
*/ */
public function getBookmarks() { public function bookmarks() {
return $this->bookmarks;
}
$page = $this->wire('page'); /**
$key = "_$page->id"; * Set the Lister page that bookmarks will be for
$data = $this->wire('modules')->getModuleConfigData('ProcessPageLister'); *
$_bookmarks = isset($data['bookmarks'][$key]) ? $data['bookmarks'][$key] : array(); * @param Page $page
$bookmarks = array(); *
*/
public function setPage(Page $page) {
$this->page = $page;
$this->bookmarks->setPage($page);
}
foreach($_bookmarks as $n => $bookmark) { /**
$n = (int) ltrim($n, '_'); * Set user that bookmarks will be for
$bookmark['url'] = $this->wire('page')->url . "?bookmark=$n"; *
$bookmarks[$n] = $bookmark; * @param User $user
} *
*/
if(!$this->wire('user')->isSuperuser()) { public function setUser(User $user) {
$userRoles = $this->wire('user')->roles; $this->user = $user;
foreach($bookmarks as $n => $bookmark) { $this->bookmarks->setUser($user);
$allowBookmark = false;
if(empty($bookmark['roles'])) {
$allowBookmark = true;
} else foreach($bookmark['roles'] as $roleID) {
foreach($userRoles as $userRole) {
if($userRole->id == $roleID) {
$allowBookmark = true;
break;
}
}
}
if(!$allowBookmark) unset($bookmarks[$n]);
}
}
return $bookmarks;
} }
/** /**
@@ -63,6 +85,9 @@ class ProcessPageListerBookmarks extends Wire {
*/ */
public function buildBookmarkListForm() { public function buildBookmarkListForm() {
/** @var Sanitizer $sanitizer */
$sanitizer = $this->wire('sanitizer');
/** @var InputfieldForm $form */ /** @var InputfieldForm $form */
$form = $this->modules->get('InputfieldForm'); $form = $this->modules->get('InputfieldForm');
$form->attr('id', 'tab_bookmarks'); $form->attr('id', 'tab_bookmarks');
@@ -71,180 +96,318 @@ class ProcessPageListerBookmarks extends Wire {
$form->class .= ' WireTab'; $form->class .= ' WireTab';
$form->attr('title', $this->_x('Bookmarks', 'tab')); $form->attr('title', $this->_x('Bookmarks', 'tab'));
$user = $this->wire('user'); $publicBookmarks = $this->bookmarks->getPublicBookmarks();
$bookmarks = $this->getBookmarks(); $ownedBookmarks = $this->bookmarks->getOwnedBookmarks();
$superuser = $user->isSuperuser();
/** @var Languages $languages */
$languages = $this->wire('languages'); $languages = $this->wire('languages');
$languageID = $languages && !$user->language->isDefault() ? $user->language->id : ''; $languageID = $languages && !$this->user->language->isDefault() ? $this->user->language->id : '';
if($superuser) { $addBookmarkFieldset = $this->buildBookmarkEditForm(0);
$fieldset = $this->buildBookmarkEditForm(0, $bookmarks); $addBookmarkFieldset->collapsed = Inputfield::collapsedYes;
if(count($bookmarks)) $fieldset->collapsed = Inputfield::collapsedYes; $form->add($addBookmarkFieldset);
$form->add($fieldset);
}
$f = $this->wire('modules')->get('InputfieldMarkup'); $bookmarksByType = array(
$f->label = $form->attr('title'); ListerBookmarks::typeOwned => $ownedBookmarks,
$f->icon = 'bookmark-o'; ListerBookmarks::typePublic => $publicBookmarks,
$form->add($f); );
if(!count($bookmarks)) return $form; $iconsByType = array(
ListerBookmarks::typeOwned => 'user-circle-o',
ListerBookmarks::typePublic => 'bookmark-o',
);
// render table of current bookmarks $typeLabels = array(
$table = $this->wire('modules')->get('MarkupAdminDataTable'); ListerBookmarks::typeOwned => $this->_('My bookmarks'),
$table->setID('table_bookmarks'); ListerBookmarks::typePublic => $this->_('Public bookmarks')
$table->setSortable(false); );
$headerRow = array($this->_x('Bookmark', 'bookmark-th'));
if($superuser) { foreach($bookmarksByType as $bookmarkType => $bookmarks) {
$headerRow[] = $this->_x('Selector', 'bookmark-th');
$headerRow[] = $this->_x('Columns', 'bookmark-th'); if(empty($bookmarks)) continue;
$headerRow[] = $this->_x('Access', 'bookmark-th');
$headerRow[] = $this->_x('Action', 'bookmark-th'); /** @var InputfieldMarkup $f */
} $f = $this->wire('modules')->get('InputfieldMarkup');
$table->headerRow($headerRow); $f->label = $typeLabels[$bookmarkType];
foreach($bookmarks as $n => $bookmark) { $f->icon = $iconsByType[$bookmarkType];
$row = array();
$title = $bookmark['title']; $headerRow = array(
if($languageID && !empty($bookmark["title$languageID"])) $title = $bookmark["title$languageID"]; 0 => $this->_x('Bookmark', 'bookmark-th'),
$row["$title\0"] = $bookmark['url']; 1 => $this->_x('Description', 'bookmark-th'),
if($superuser) { 2 => $this->_x('Access', 'bookmark-th'),
$selector = $bookmark['selector']; 3 => $this->_x('Actions', 'bookmark-th'),
if(strpos($selector, 'template=') !== false && preg_match('/template=([\d\|]+)/', $selector, $matches)) { );
// make templates readable, for output purposes
$t = ''; /** @var MarkupAdminDataTable $table */
foreach(explode('|', $matches[1]) as $templateID) { $table = $this->wire('modules')->get('MarkupAdminDataTable');
$template = $this->wire('templates')->get((int) $templateID); $table->setID('table_bookmarks_' . $bookmarkType);
$t .= ($t ? '|' : '') . ($template ? $template->name : $templateID); $table->setSortable(false);
$table->setEncodeEntities(false);
$table->headerRow($headerRow);
foreach($bookmarks as $bookmarkID => $bookmark) {
$row = array();
if(!$this->bookmarks->isBookmarkViewable($bookmark)) continue;
// title column
$title = $bookmark['title'];
if($languageID && !empty($bookmark["title$languageID"])) $title = $bookmark["title$languageID"];
$title = $sanitizer->entities($title);
$viewUrl = $this->bookmarks->getBookmarkUrl($bookmarkID, $this->user);
$row["$title\0"] = $viewUrl;
// description column
$desc = $bookmark['desc'];
if($languageID && !empty($bookmark["desc$languageID"])) $desc = $bookmark["desc$languageID"];
if(empty($desc)) {
$selector = $this->bookmarks->readableBookmarkSelector($bookmark);
$columns = implode(', ', $bookmark['columns']);
$desc = "$selector ($columns)";
}
$row[] = $sanitizer->entities($desc);
// access column (public bookmarks only)
if($bookmark['type'] == ListerBookmarks::typePublic) {
if(count($bookmark['roles']) < 2 && ((int) reset($bookmark['roles'])) === 0) {
$row[] = $this->_x('all', 'bookmark-roles');
} else if(count($bookmark['roles'])) {
$row[] = $this->wire('pages')->getById($bookmark['roles'])->implode(', ', 'name');
} }
$selector = str_replace($matches[0], "template=$t", $selector); } else {
$row[] = $this->_('you');
} }
if($bookmark['sort']) $selector .= ($selector ? ", " : "") . "sort=$bookmark[sort]";
$row[] = $selector; // actions column
$row[] = implode(', ', $bookmark['columns']); $actions = array();
if(count($bookmark['roles']) < 2 && ((int) reset($bookmark['roles'])) === 0) { $actions[] = "<a href='$viewUrl'>" . $this->_x('View', 'bookmark-action') . "</a>";
$row[] = $this->_x('all', 'bookmark-roles'); if($this->bookmarks->isBookmarkEditable($bookmark)) {
} else if(count($bookmark['roles'])) { $editUrl = $this->bookmarks->getBookmarkEditUrl($bookmarkID);
$row[] = $this->wire('pages')->getById($bookmark['roles'])->implode(', ', 'name'); $actions[] = "<a href='$editUrl'>" . $this->_x('Modify', 'bookmark-action') . "</a>";
if($this->bookmarks->isBookmarkDeletable($bookmark)) {
$actions[] = "<a href='$editUrl&delete=1'>" . $this->_x('Delete', 'bookmark-action') . "</a>";
}
} }
$row[$this->_x('Edit', 'bookmark-action')] = "./edit-bookmark/?n=$n";
$actions = implode(' &nbsp;/&nbsp; ', $actions);
$row[] = $actions;
$table->row($row);
} }
$table->row($row);
$f->value = $table->render();
$form->add($f);
} }
if($superuser) $f->appendMarkup = "<p class='detail'>" . $this->_('Superuser note: other users can see and click bookmarks, but may not add or edit them.') . "</p>";
$f->value = $table->render();
return $form; return $form;
} }
/**
* Build form for deleting a bookmark
*
* @param int $bookmarkID Bookmark ID
*
* @return InputfieldFieldset
* @throws WireException
* @throws WirePermissionException
*
*/
protected function buildBookmarkDeleteForm($bookmarkID) {
$bookmark = $this->bookmarks->getBookmark($bookmarkID);
if(!$bookmark) throw new WireException('Unknown bookmark');
/** @var InputfieldFieldset $fieldset */
$fieldset = $this->wire('modules')->get('InputfieldFieldset');
$fieldset->icon = 'trash-o';
$fieldset->label = $this->_('Please check the box to confirm you want to delete this bookmark');
/** @var InputfieldCheckbox $f */
$f = $this->wire('modules')->get('InputfieldCheckbox');
$f->attr('name', 'delete_bookmark');
$f->label = $this->_('Delete this bookmark?');
$f->icon = 'trash-o';
$f->attr('value', $bookmarkID);
$fieldset->add($f);
/** @var InputfieldSubmit $submit */
$submit = $this->wire('modules')->get('InputfieldSubmit');
$submit->attr('name', 'submit_delete_bookmark');
$submit->icon = 'trash-o';
$fieldset->add($submit);
/** @var InputfieldButton $cancel */
$cancel = $this->wire('modules')->get('InputfieldButton');
$cancel->href = '../#tab_bookmarks';
$cancel->setSecondary(true);
$cancel->attr('value', $this->_('Cancel'));
$fieldset->add($cancel);
return $fieldset;
}
/** /**
* Build the form needed to edit/add bookmarks * Build the form needed to edit/add bookmarks
* *
* @param int $bookmarkID Specify bookmark ID if editing existing bookmark * @param int $bookmarkID Specify bookmark ID if editing existing bookmark
* @param array $bookmarks Optionally include list of all bookmarks to prevent this method from having to re-load them *
* @return InputfieldWrapper * @return InputfieldWrapper
* *
*/ */
protected function buildBookmarkEditForm($bookmarkID = 0, $bookmarks = array()) { protected function buildBookmarkEditForm($bookmarkID = 0) {
/** @var Languages $languages */
$languages = $this->wire('languages'); $languages = $this->wire('languages');
/** @var InputfieldFieldset $fieldset */
$fieldset = $this->wire('modules')->get('InputfieldFieldset'); $fieldset = $this->wire('modules')->get('InputfieldFieldset');
if($bookmarkID) { if($bookmarkID) {
if(empty($bookmarks)) $bookmarks = $this->getBookmarks(); $bookmark = $this->bookmarks->getBookmark($bookmarkID);
$bookmark = isset($bookmarks[$bookmarkID]) ? $bookmarks[$bookmarkID] : array(); if(!$bookmark) throw new WireException("Unknown bookmark");
if(empty($bookmark)) $bookmarkID = 0; if(!$this->bookmarks->isBookmarkEditable($bookmark)) throw new WirePermissionException('Bookmark is not editable');
$fieldset->label = $this->_('Edit Bookmark'); $fieldset->label = $this->_('Edit Bookmark');
} else { } else {
$bookmark = array(); $bookmark = array();
$fieldset->label = $this->_('Add New Bookmark'); $fieldset->label = $this->_('Add New Bookmark');
$fieldset->description = $this->_('Creates a new bookmark matching your current filters, columns and order.');
} }
$fieldset->icon = $bookmarkID ? 'bookmark-o' : 'plus-circle'; $fieldset->icon = $bookmarkID ? 'bookmark-o' : 'plus-circle';
if(!$bookmarkID) $fieldset->description = $this->_('Creates a new bookmark matching your current filters, columns and order.');
$f = $this->wire('modules')->get('InputfieldText'); /** @var InputfieldText $titleField */
$f->attr('name', 'bookmark_title'); $titleField = $this->wire('modules')->get('InputfieldText');
$f->label = $this->_x('Title', 'bookmark-editor'); // Bookmark title $titleField->attr('name', 'bookmark_title');
$f->required = true; $titleField->label = $this->_x('Title', 'bookmark-editor'); // Bookmark title
if($languages) $f->useLanguages = true; $titleField->required = true;
$fieldset->add($f); if($languages) $titleField->useLanguages = true;
$fieldset->add($titleField);
/** @var InputfieldText $descField */
$descField = $this->wire('modules')->get('InputfieldText');
$descField->attr('name', 'bookmark_desc');
$descField->label = $this->_x('Description', 'bookmark-editor'); // Bookmark title
if($languages) $descField->useLanguages = true;
$fieldset->add($descField);
if($bookmarkID) { if($bookmarkID) {
// editing existing bookmark // editing existing bookmark
$f->attr('value', $bookmark['title']); $titleField->attr('value', $bookmark['title']);
if($languages) foreach($languages as $language) { $descField->attr('value', $bookmark['desc']);
if($language->isDefault()) continue;
$f->attr("value$language", isset($bookmark["title$language"]) ? $bookmark["title$language"] : ""); if($languages) {
foreach($languages as $language) {
if($language->isDefault()) continue;
$titleField->attr("value$language", isset($bookmark["title$language"]) ? $bookmark["title$language"] : "");
$descField->attr("value$language", isset($bookmark["desc$language"]) ? $bookmark["desc$language"] : "");
}
} }
$f = $this->wire('modules')->get('InputfieldSelector'); if($this->user->isSuperuser()) {
$f->attr('name', 'bookmark_selector'); /** @var InputfieldSelector $f */
$f->label = $this->_x('What pages should this bookmark show?', 'bookmark-editor'); $f = $this->wire('modules')->get('InputfieldSelector');
$selector = $bookmark['selector']; $f->attr('name', 'bookmark_selector');
if($bookmark['sort']) $selector .= ", sort=$bookmark[sort]"; $f->label = $this->_x('What pages should this bookmark show?', 'bookmark-editor');
if($this->lister->initSelector && strpos($selector, $this->lister->initSelector) !== false) { $selector = $bookmark['selector'];
$selector = str_replace($this->lister->initSelector, '', $selector); // ensure that $selector does not contain initSelector if($bookmark['sort']) $selector .= ", sort=$bookmark[sort]";
if($this->lister->initSelector && strpos($selector, $this->lister->initSelector) !== false) {
$selector = str_replace($this->lister->initSelector, '', $selector); // ensure that $selector does not contain initSelector
}
if($this->lister->template) $f->initTemplate = $this->lister->template;
$default = $this->lister->className() == 'ProcessPageLister';
$f->preview = false;
$f->allowSystemCustomFields = true;
$f->allowSystemTemplates = true;
$f->allowSubfieldGroups = $default ? false : true;
$f->allowSubselectors = $default ? false : true;
$f->showFieldLabels = $this->lister->useColumnLabels ? 1 : 0;
$f->initValue = $this->lister->initSelector;
$f->attr('value', $selector);
$fieldset->add($f);
$f = $this->lister->buildColumnsField();
$f->attr('name', 'bookmark_columns');
$f->attr('value', $bookmark['columns']);
$f->label = $this->_x('Columns', 'bookmark-editor');
$fieldset->add($f);
} }
if($this->lister->template) $f->initTemplate = $this->lister->template;
$default = $this->lister->className() == 'ProcessPageLister'; $f = $this->wire('modules')->get('InputfieldHidden');
$f->preview = false; $f->attr('name', 'bookmark_type');
$f->allowSystemCustomFields = true; $f->attr('value', (int) $bookmark['type']);
$f->allowSystemTemplates = true;
$f->allowSubfieldGroups = $default ? false : true;
$f->allowSubselectors = $default ? false : true;
$f->showFieldLabels = $this->lister->useColumnLabels ? 1 : 0;
$f->initValue = $this->lister->initSelector;
$f->attr('value', $selector);
$fieldset->add($f); $fieldset->add($f);
$f = $this->lister->buildColumnsField(); } else {
$f->attr('name', 'bookmark_columns'); // add new bookmark
$f->attr('value', $bookmark['columns']); if($this->user->isSuperuser()) {
$f->label = $this->_x('Columns', 'bookmark-editor'); $f = $this->wire('modules')->get('InputfieldRadios');
$f->attr('name', 'bookmark_type');
$f->label = $this->_('Bookmark type');
$f->addOption(ListerBookmarks::typeOwned, $this->_('Owned') . ' ' .
"[span.detail] " . $this->_('(visible to me only)') . " [/span] ");
$f->addOption(ListerBookmarks::typePublic, $this->_('Public'));
$f->attr('value', isset($bookmark['type']) ? (int) $bookmark['type'] : ListerBookmarks::typeOwned);
$fieldset->add($f);
}
}
if($this->user->isSuperuser()) {
/** @var InputfieldAsmSelect $f */
$f = $this->wire('modules')->get('InputfieldAsmSelect');
$f->attr('name', 'bookmark_roles');
$f->label = $this->_x('Access', 'bookmark-editor');
$f->icon = 'key';
$f->description = $this->_('What user roles will see this bookmark? If no user roles are selected, then all roles with permission to use this Lister can view the bookmark.');
foreach($this->wire('roles') as $role) {
if($role->name != 'guest') $f->addOption($role->id, $role->name);
}
if($bookmarkID) $f->attr('value', $bookmark['roles']);
$f->collapsed = Inputfield::collapsedBlank;
$f->showIf = 'bookmark_type=' . ListerBookmarks::typePublic;
$fieldset->add($f); $fieldset->add($f);
} }
$f = $this->wire('modules')->get('InputfieldAsmSelect'); /** @var InputfieldCheckbox $f */
$f->attr('name', 'bookmark_roles'); $f = $this->wire('modules')->get('InputfieldCheckbox');
$f->label = $this->_x('Access', 'bookmark-editor'); $f->attr('name', 'bookmark_share');
$f->icon = 'key'; $f->label = $this->_('Allow other users to access this bookmark URL?');
$f->description = $this->_('What user roles will see this bookmark? If no user roles are selected, then all roles with permission to use this Lister can view the bookmark.'); $f->description = $this->_('If you send the bookmark URL to someone else that is already logged in to the admin, they can view the bookmark if you check this box.');
foreach($this->wire('roles') as $role) { $f->notes = sprintf(
if($role->name != 'guest') $f->addOption($role->id, $role->name); $this->_('Shareable bookmark URL: [View](%s)'),
$this->page->httpUrl() . str_replace($this->page->url, '', $this->bookmarks->getBookmarkUrl($bookmarkID, $this->user))
);
if(empty($bookmark['share'])) {
$f->collapsed = Inputfield::collapsedYes;
} else {
$f->attr('checked', 'checked');
} }
if($bookmarkID) $f->attr('value', $bookmark['roles']); $f->showIf = 'bookmark_type=' . ListerBookmarks::typeOwned;
$f->collapsed = Inputfield::collapsedBlank;
$fieldset->add($f); $fieldset->add($f);
if($bookmarkID) { if($bookmarkID) {
$bookmarks = $bookmark['type'] == ListerBookmarks::typePublic ? $this->bookmarks->getPublicBookmarks() : $this->bookmarks->getOwnedBookmarks();
/** @var InputfieldAsmSelect $f */ // option for changing the order of bookmarks
$f = $this->wire('modules')->get('InputfieldAsmSelect'); if(count($bookmarks) > 1) {
$f->attr('name', 'bookmarks_sort'); /** @var InputfieldAsmSelect $f */
$f->label = $this->_('Bookmarks sort order'); $f = $this->wire('modules')->get('InputfieldAsmSelect');
$f->icon = 'sort'; $f->attr('name', 'bookmarks_sort');
$f->setAsmSelectOption('removeLabel', ''); $f->label = $this->_('Order');
$value = array(); $f->icon = 'sort';
foreach($bookmarks as $n => $b) { $f->setAsmSelectOption('removeLabel', '');
$f->addOption($n, $b['title']); $value = array();
$value[] = $n; foreach($bookmarks as $bmid => $bm) {
$f->addOption($bmid, $bm['title']);
$value[] = $bmid;
}
$f->attr('value', $value);
$fieldset->add($f);
} }
$f->attr('value', $value);
$f->collapsed = Inputfield::collapsedYes;
$fieldset->add($f);
$f = $this->wire('modules')->get('InputfieldCheckbox');
$f->attr('name', 'delete_bookmark');
$f->label = $this->_x('Delete', 'bookmark-editor');
$f->label2 = $this->_('Delete this bookmark?');
$f->icon = 'trash-o';
$f->attr('value', $bookmarkID);
$f->collapsed = Inputfield::collapsedYes;
$fieldset->add($f);
} }
/** @var InputfieldSubmit $submit */
$submit = $this->wire('modules')->get('InputfieldSubmit'); $submit = $this->wire('modules')->get('InputfieldSubmit');
$submit->attr('name', 'submit_bookmark'); $submit->attr('name', 'submit_save_bookmark');
$submit->icon = 'bookmark-o'; $submit->icon = 'bookmark-o';
$fieldset->add($submit); $fieldset->add($submit);
@@ -258,21 +421,46 @@ class ProcessPageListerBookmarks extends Wire {
* @throws WirePermissionException * @throws WirePermissionException
* *
*/ */
public function editBookmark() { public function executeEditBookmark() {
if(!$this->wire('user')->isSuperuser()) throw new WirePermissionException("Only superuser can edit bookmarks"); /** @var WireInput $input */
$input = $this->wire('input');
if($this->wire('input')->post('bookmark_title')) return $this->saveBookmark(); $deleteBookmarkID = $this->bookmarks->_bookmarkID($input->post('delete_bookmark'));
if($deleteBookmarkID) {
if($this->bookmarks->deleteBookmarkByID($deleteBookmarkID)) {
$this->message($this->_('Deleted bookmark'));
} else {
$this->error($this->_('Bookmark is not deletable'));
}
$this->redirectToBookmarks();
return '';
}
$bookmarkID = $this->wire('input')->get->int('n'); if($input->post('bookmark_title')) {
return $this->executeSaveBookmark();
}
$bookmarkID = $this->bookmarks->_bookmarkID($input->get('bookmark'));
$bookmark = $this->bookmarks->getBookmark($bookmarkID);
if(!$bookmark) $this->redirectToBookmarks();
if(!$this->bookmarks->isBookmarkEditable($bookmark)) throw new WirePermissionException("Bookmark not editable");
/** @var InputfieldForm $form */
$form = $this->wire('modules')->get('InputfieldForm'); $form = $this->wire('modules')->get('InputfieldForm');
$form->attr('action', './'); $form->attr('action', './');
$fieldset = $this->buildBookmarkEditForm($bookmarkID); if($input->get('delete')) {
$fieldset = $this->buildBookmarkDeleteForm($bookmarkID);
$this->lister->headline($bookmark['title']);
} else {
$fieldset = $this->buildBookmarkEditForm($bookmarkID);
$this->lister->headline($fieldset->label);
}
$form->add($fieldset); $form->add($fieldset);
$this->lister->headline($fieldset->label);
/** @var InputfieldHidden $f */
$f = $this->wire('modules')->get('InputfieldHidden'); $f = $this->wire('modules')->get('InputfieldHidden');
$f->attr('name', 'bookmark_id'); $f->attr('name', 'bookmark_id');
$f->attr('value', $bookmarkID); $f->attr('value', $bookmarkID);
@@ -290,30 +478,52 @@ class ProcessPageListerBookmarks extends Wire {
* Performs redirect after saving * Performs redirect after saving
* *
*/ */
protected function saveBookmark() { protected function executeSaveBookmark() {
$input = $this->wire('input'); $input = $this->wire('input');
$sanitizer = $this->wire('sanitizer'); $sanitizer = $this->wire('sanitizer');
$page = $this->wire('page'); $languages = $this->wire('languages');
$bookmarkID = $input->post->int('bookmark_id'); $bookmarkID = $this->bookmarks->_bookmarkID($input->post('bookmark_id'));
$bookmarkTitle = $input->post->text('bookmark_title'); $bookmarkTitle = $input->post->text('bookmark_title');
$bookmarkDesc = $input->post->text('bookmark_desc');
if(!$bookmarkID && empty($bookmarkTitle)) { if(!$bookmarkID && empty($bookmarkTitle)) {
$this->wire('session')->redirect('../#tab_bookmarks'); $this->redirectToBookmarks();
return; return;
} }
if($bookmarkID) {
$existingBookmark = $this->bookmarks->getBookmark($bookmarkID);
if(!$existingBookmark || !$this->bookmarks->isBookmarkEditable($existingBookmark)) {
throw new WirePermissionException("Bookmark not editable");
}
} else {
$existingBookmark = null;
}
$bookmarkSort = ''; $bookmarkSort = '';
$textOptions = array('maxLength' => 1024, 'stripTags' => false); $textOptions = array(
$bookmarkSelector = $bookmarkID ? $input->post->text('bookmark_selector', $textOptions) : $this->lister->getSelector(); 'maxLength' => 1024,
'stripTags' => false
);
if($this->user->isSuperuser()) {
$bookmarkSelector = $bookmarkID ? $input->post->text('bookmark_selector', $textOptions) : $this->lister->getSelector();
} else {
$bookmarkSelector = $existingBookmark ? $existingBookmark['selector'] : $this->lister->getSelector();
}
if(preg_match('/\bsort=([-_.a-zA-Z]+)/', $bookmarkSelector, $matches)) $bookmarkSort = $matches[1]; if(preg_match('/\bsort=([-_.a-zA-Z]+)/', $bookmarkSelector, $matches)) $bookmarkSort = $matches[1];
$bookmarkSelector = preg_replace('/\b(include|sort|limit)=[^,]+,?/', '', $bookmarkSelector); $bookmarkSelector = preg_replace('/\b(include|sort|limit)=[^,]+,?/', '', $bookmarkSelector);
if($this->lister->initSelector && strpos($bookmarkSelector, $this->lister->initSelector) !== false) { if($this->lister->initSelector && strpos($bookmarkSelector, $this->lister->initSelector) !== false) {
// ensure that $selector does not contain initSelector // ensure that $selector does not contain initSelector
$bookmarkSelector = str_replace($this->lister->initSelector, '', $bookmarkSelector); $bookmarkSelector = str_replace($this->lister->initSelector, '', $bookmarkSelector);
} }
$bookmarkSelector = str_replace(', , ', ', ', $bookmarkSelector); $bookmarkSelector = str_replace(', , ', ', ', $bookmarkSelector);
$bookmarkSelector = trim($bookmarkSelector, ', ');
if($bookmarkID) { if($bookmarkID) {
$bookmarkColumns = $input->post('bookmark_columns'); $bookmarkColumns = $input->post('bookmark_columns');
@@ -330,63 +540,89 @@ class ProcessPageListerBookmarks extends Wire {
$bookmarkColumns = $this->lister->columns; $bookmarkColumns = $this->lister->columns;
} }
$bookmark = array( $bookmark = $this->bookmarks->_bookmark(array(
'id' => $bookmarkID,
'title' => $bookmarkTitle, 'title' => $bookmarkTitle,
'selector' => trim($bookmarkSelector, ", "), 'desc' => $bookmarkDesc,
'selector' => $bookmarkSelector,
'columns' => $bookmarkColumns, 'columns' => $bookmarkColumns,
'sort' => $bookmarkSort, 'sort' => $bookmarkSort,
'roles' => $input->post->intArray('bookmark_roles') 'share' => $input->post('bookmark_share') ? true : false
); ));
$languages = $this->wire('languages');
if($languages) foreach($languages as $language) {
if($language->isDefault()) continue;
$bookmark["title$language"] = $input->post->text("bookmark_title__$language");
}
$data = $this->wire('modules')->getModuleConfigData('ProcessPageLister'); if($this->user->isSuperuser()) {
$_bookmarks = isset($data['bookmarks']) ? $data['bookmarks'] : array(); $bookmark['type'] = $input->post->int('bookmark_type');
$bookmark['roles'] = $input->post->intArray('bookmark_roles');
foreach($_bookmarks as $pageID => $bookmarks) {
// remove bookmarks for Lister pages that no longer exist
$pageID = (int) ltrim($pageID, '_');
if($pageID == $page->id) continue;
if(!$this->wire('pages')->get($pageID)->id) unset($_bookmarks[$pageID]);
}
$bookmarks = isset($_bookmarks["_$page->id"]) ? $_bookmarks["_$page->id"] : array();
if($bookmarkID) {
$n = $bookmarkID;
} else { } else {
$n = time(); $bookmark['type'] = ListerBookmarks::typeOwned;
while(isset($bookmarks[$n])) $n++; $bookmark['roles'] = array();
} }
$bookmarks["_$n"] = $bookmark; if($languages) {
foreach($languages as $language) {
if($language->isDefault()) continue;
$bookmark["title$language"] = $input->post->text("bookmark_title__$language");
$bookmark["desc$language"] = $input->post->text("bookmark_desc__$language");
}
}
if($bookmark['type'] == ListerBookmarks::typeOwned) {
$bookmarks = $this->bookmarks->getOwnedBookmarks();
} else {
$bookmarks = $this->bookmarks->getPublicBookmarks();
}
$typePrefix = $this->bookmarks->typePrefix($bookmark['type']);
if(!$bookmarkID) $bookmarkID = $typePrefix . time(); // new bookmark
$bookmarks[$bookmarkID] = $bookmark;
// update sort order of all bookmarks // update sort order of all bookmarks
if($input->post->bookmarks_sort) { if($input->post('bookmarks_sort')) {
$sorted = array(); $sorted = array();
foreach($input->post->intArray('bookmarks_sort') as $bmid) { foreach($input->post->array('bookmarks_sort') as $bmid) {
$bm = $bookmarks["_$bmid"]; $bmid = $this->bookmarks->_bookmarkID($bmid);
$sorted["_$bmid"] = $bm; if(!isset($bookmarks[$bmid])) continue;
$bm = $bookmarks[$bmid];
$sorted[$bmid] = $bm;
} }
$bookmarks = $sorted; $bookmarks = $sorted;
} }
if($bookmarkID && $input->post('delete_bookmark') == $bookmarkID) { if($bookmark['type'] == ListerBookmarks::typeOwned) {
unset($bookmarks["_$n"]); $this->bookmarks->saveOwnedBookmarks($bookmarks);
$this->message(sprintf($this->_('Deleted bookmark: %s'), $bookmarkTitle));
$bookmarkID = 0;
} else { } else {
$this->message(sprintf($this->_('Saved bookmark: %s'), $bookmarkTitle)); $this->bookmarks->savePublicBookmarks($bookmarks);
} }
$_bookmarks["_$page->id"] = $bookmarks; $this->redirectToBookmarks($bookmarkID);
$data['bookmarks'] = $_bookmarks;
$this->wire('modules')->saveModuleConfigData('ProcessPageLister', $data);
$this->wire('session')->redirect("../?bookmark=$bookmarkID#tab_bookmarks");
} }
}
protected function redirectToBookmarks($bookmarkID = '') {
$url = $this->page->url;
if($bookmarkID) {
$this->wire('session')->redirect($url . "?bookmark=$bookmarkID#tab_bookmarks");
} else {
$this->wire('session')->redirect($url . '#tab_bookmarks');
}
}
public function _bookmark(array $bookmark = array()) {
return $this->bookmarks->_bookmark($bookmark);
}
public function _bookmarkID($bookmarkID) {
return $this->bookmarks->_bookmarkID($bookmarkID);
}
public function getBookmark($bookmarkID, $type = null) {
return $this->bookmarks->getBookmark($bookmarkID, $type);
}
public function getBookmarks() {
return $this->bookmarks->getBookmarks();
}
public function isBookmarkViewable($bookmark) {
return $this->bookmarks->isBookmarkViewable($bookmark);
}
}