1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-25 23:56:41 +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();
/**
* Additional notes about the results to display underneath them
*
* @var array
*
*/
protected $resultNotes = array();
/**
* Initalize module config variables
*
@@ -348,35 +356,25 @@ class ProcessPageLister extends Process implements ConfigurableModule {
/**
* 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() {
$n = (int) $this->wire('input')->get('bookmark');
if(!$n) return null;
$bookmarks = $this->getBookmarksInstance()->getBookmarks();
if(!isset($bookmarks[$n])) return false;
$bookmark = $bookmarks[$n];
$user = $this->wire('user');
if(!$user->isSuperuser() && count($bookmark['roles'])) {
$allow = false;
foreach($bookmark['roles'] as $roleID) {
foreach($user->roles as $role) {
if($role->id == $roleID) {
$allow = true;
break;
}
}
}
if(!$allow) return false;
}
public function checkBookmark($bookmarkID = '') {
if(!$bookmarkID) $bookmarkID = $this->wire('input')->get('bookmark');
if(!$bookmarkID) return null;
$bookmarks = $this->getBookmarksInstance();
$bookmarkID = $bookmarks->_bookmarkID($bookmarkID);
if(!$bookmarkID) return false;
$bookmark = $bookmarks->getBookmark($bookmarkID);
if(!$bookmark || !$bookmarks->isBookmarkViewable($bookmark)) return false;
$this->sessionClear();
$this->set('defaultSelector', $bookmark['selector']);
$this->set('defaultSort', $bookmark['sort']);
$this->sessionSet('sort', $bookmark['sort']);
$this->set('columns', $bookmark['columns']);
return $n;
$this->headline($this->wire('page')->title . ' - ' . $bookmark['title']);
return $bookmarkID;
}
/**
@@ -1009,7 +1007,9 @@ class ProcessPageLister extends Process implements ConfigurableModule {
// ok, override
} else {
$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;
}
}
@@ -1039,7 +1039,9 @@ class ProcessPageLister extends Process implements ConfigurableModule {
// include=unpublished is allowed
} else if($includeMode == 'unpublished') {
// 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';
$changed = true;
}
@@ -1049,7 +1051,9 @@ class ProcessPageLister extends Process implements ConfigurableModule {
// only allow a max include mode of hidden
// regardless of edit access
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';
$changed = true;
}
@@ -1783,9 +1787,15 @@ class ProcessPageLister extends Process implements ConfigurableModule {
"<div id='ProcessListerTable'>$tableOut</div>" .
$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) {
$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) {
@@ -1889,9 +1899,7 @@ class ProcessPageLister extends Process implements ConfigurableModule {
}
if($this->allowBookmarks) {
$bookmarks = $this->getBookmarksInstance();
if($this->wire('user')->isSuperuser() || count($bookmarks->getBookmarks())) {
$out .= $bookmarks->buildBookmarkListForm()->render();
}
$out .= $bookmarks->buildBookmarkListForm()->render();
}
$out .= $this->renderExtras();
}
@@ -2096,7 +2104,27 @@ class ProcessPageLister extends Process implements ConfigurableModule {
*/
public function ___executeEditBookmark() {
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');
$languageID = $languages && !$user->language->isDefault() ? $user->language->id : '';
foreach($bookmarks as $n => $bookmark) {
foreach($bookmarks as $bookmarkID => $bookmark) {
$name = $bookmark['title'];
$icon = $bookmark['type'] ? 'user-circle-o' : 'search';
if($languageID && !empty($bookmark["title$languageID"])) $name = $bookmark["title$languageID"];
$item = array(
'id' => $n,
'id' => $bookmarkID,
'name' => $name,
'icon' => 'search',
'icon' => $icon,
);
$items[] = $item;
}

View File

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

View File

@@ -8,51 +8,73 @@
*/
class ProcessPageListerBookmarks extends Wire {
/**
* @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) {
require_once(__DIR__ . '/ListerBookmarks.php');
$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 array
* @return ListerBookmarks
*
*/
public function getBookmarks() {
public function bookmarks() {
return $this->bookmarks;
}
$page = $this->wire('page');
$key = "_$page->id";
$data = $this->wire('modules')->getModuleConfigData('ProcessPageLister');
$_bookmarks = isset($data['bookmarks'][$key]) ? $data['bookmarks'][$key] : array();
$bookmarks = array();
/**
* Set the Lister page that bookmarks will be for
*
* @param Page $page
*
*/
public function setPage(Page $page) {
$this->page = $page;
$this->bookmarks->setPage($page);
}
foreach($_bookmarks as $n => $bookmark) {
$n = (int) ltrim($n, '_');
$bookmark['url'] = $this->wire('page')->url . "?bookmark=$n";
$bookmarks[$n] = $bookmark;
}
if(!$this->wire('user')->isSuperuser()) {
$userRoles = $this->wire('user')->roles;
foreach($bookmarks as $n => $bookmark) {
$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;
/**
* Set user that bookmarks will be for
*
* @param User $user
*
*/
public function setUser(User $user) {
$this->user = $user;
$this->bookmarks->setUser($user);
}
/**
@@ -63,6 +85,9 @@ class ProcessPageListerBookmarks extends Wire {
*/
public function buildBookmarkListForm() {
/** @var Sanitizer $sanitizer */
$sanitizer = $this->wire('sanitizer');
/** @var InputfieldForm $form */
$form = $this->modules->get('InputfieldForm');
$form->attr('id', 'tab_bookmarks');
@@ -71,180 +96,318 @@ class ProcessPageListerBookmarks extends Wire {
$form->class .= ' WireTab';
$form->attr('title', $this->_x('Bookmarks', 'tab'));
$user = $this->wire('user');
$bookmarks = $this->getBookmarks();
$superuser = $user->isSuperuser();
$publicBookmarks = $this->bookmarks->getPublicBookmarks();
$ownedBookmarks = $this->bookmarks->getOwnedBookmarks();
/** @var Languages $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) {
$fieldset = $this->buildBookmarkEditForm(0, $bookmarks);
if(count($bookmarks)) $fieldset->collapsed = Inputfield::collapsedYes;
$form->add($fieldset);
}
$addBookmarkFieldset = $this->buildBookmarkEditForm(0);
$addBookmarkFieldset->collapsed = Inputfield::collapsedYes;
$form->add($addBookmarkFieldset);
$f = $this->wire('modules')->get('InputfieldMarkup');
$f->label = $form->attr('title');
$f->icon = 'bookmark-o';
$form->add($f);
$bookmarksByType = array(
ListerBookmarks::typeOwned => $ownedBookmarks,
ListerBookmarks::typePublic => $publicBookmarks,
);
if(!count($bookmarks)) return $form;
$iconsByType = array(
ListerBookmarks::typeOwned => 'user-circle-o',
ListerBookmarks::typePublic => 'bookmark-o',
);
// render table of current bookmarks
$table = $this->wire('modules')->get('MarkupAdminDataTable');
$table->setID('table_bookmarks');
$table->setSortable(false);
$headerRow = array($this->_x('Bookmark', 'bookmark-th'));
if($superuser) {
$headerRow[] = $this->_x('Selector', 'bookmark-th');
$headerRow[] = $this->_x('Columns', 'bookmark-th');
$headerRow[] = $this->_x('Access', 'bookmark-th');
$headerRow[] = $this->_x('Action', 'bookmark-th');
}
$table->headerRow($headerRow);
foreach($bookmarks as $n => $bookmark) {
$row = array();
$title = $bookmark['title'];
if($languageID && !empty($bookmark["title$languageID"])) $title = $bookmark["title$languageID"];
$row["$title\0"] = $bookmark['url'];
if($superuser) {
$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);
$typeLabels = array(
ListerBookmarks::typeOwned => $this->_('My bookmarks'),
ListerBookmarks::typePublic => $this->_('Public bookmarks')
);
foreach($bookmarksByType as $bookmarkType => $bookmarks) {
if(empty($bookmarks)) continue;
/** @var InputfieldMarkup $f */
$f = $this->wire('modules')->get('InputfieldMarkup');
$f->label = $typeLabels[$bookmarkType];
$f->icon = $iconsByType[$bookmarkType];
$headerRow = array(
0 => $this->_x('Bookmark', 'bookmark-th'),
1 => $this->_x('Description', 'bookmark-th'),
2 => $this->_x('Access', 'bookmark-th'),
3 => $this->_x('Actions', 'bookmark-th'),
);
/** @var MarkupAdminDataTable $table */
$table = $this->wire('modules')->get('MarkupAdminDataTable');
$table->setID('table_bookmarks_' . $bookmarkType);
$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;
$row[] = implode(', ', $bookmark['columns']);
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');
// actions column
$actions = array();
$actions[] = "<a href='$viewUrl'>" . $this->_x('View', 'bookmark-action') . "</a>";
if($this->bookmarks->isBookmarkEditable($bookmark)) {
$editUrl = $this->bookmarks->getBookmarkEditUrl($bookmarkID);
$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;
}
/**
* 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
*
* @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
*
*/
protected function buildBookmarkEditForm($bookmarkID = 0, $bookmarks = array()) {
protected function buildBookmarkEditForm($bookmarkID = 0) {
/** @var Languages $languages */
$languages = $this->wire('languages');
/** @var InputfieldFieldset $fieldset */
$fieldset = $this->wire('modules')->get('InputfieldFieldset');
if($bookmarkID) {
if(empty($bookmarks)) $bookmarks = $this->getBookmarks();
$bookmark = isset($bookmarks[$bookmarkID]) ? $bookmarks[$bookmarkID] : array();
if(empty($bookmark)) $bookmarkID = 0;
$bookmark = $this->bookmarks->getBookmark($bookmarkID);
if(!$bookmark) throw new WireException("Unknown bookmark");
if(!$this->bookmarks->isBookmarkEditable($bookmark)) throw new WirePermissionException('Bookmark is not editable');
$fieldset->label = $this->_('Edit Bookmark');
} else {
$bookmark = array();
$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';
if(!$bookmarkID) $fieldset->description = $this->_('Creates a new bookmark matching your current filters, columns and order.');
$f = $this->wire('modules')->get('InputfieldText');
$f->attr('name', 'bookmark_title');
$f->label = $this->_x('Title', 'bookmark-editor'); // Bookmark title
$f->required = true;
if($languages) $f->useLanguages = true;
$fieldset->add($f);
/** @var InputfieldText $titleField */
$titleField = $this->wire('modules')->get('InputfieldText');
$titleField->attr('name', 'bookmark_title');
$titleField->label = $this->_x('Title', 'bookmark-editor'); // Bookmark title
$titleField->required = true;
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) {
// editing existing bookmark
$f->attr('value', $bookmark['title']);
if($languages) foreach($languages as $language) {
if($language->isDefault()) continue;
$f->attr("value$language", isset($bookmark["title$language"]) ? $bookmark["title$language"] : "");
$titleField->attr('value', $bookmark['title']);
$descField->attr('value', $bookmark['desc']);
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');
$f->attr('name', 'bookmark_selector');
$f->label = $this->_x('What pages should this bookmark show?', 'bookmark-editor');
$selector = $bookmark['selector'];
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->user->isSuperuser()) {
/** @var InputfieldSelector $f */
$f = $this->wire('modules')->get('InputfieldSelector');
$f->attr('name', 'bookmark_selector');
$f->label = $this->_x('What pages should this bookmark show?', 'bookmark-editor');
$selector = $bookmark['selector'];
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->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);
$f = $this->wire('modules')->get('InputfieldHidden');
$f->attr('name', 'bookmark_type');
$f->attr('value', (int) $bookmark['type']);
$fieldset->add($f);
$f = $this->lister->buildColumnsField();
$f->attr('name', 'bookmark_columns');
$f->attr('value', $bookmark['columns']);
$f->label = $this->_x('Columns', 'bookmark-editor');
} else {
// add new bookmark
if($this->user->isSuperuser()) {
$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);
}
$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);
/** @var InputfieldCheckbox $f */
$f = $this->wire('modules')->get('InputfieldCheckbox');
$f->attr('name', 'bookmark_share');
$f->label = $this->_('Allow other users to access this bookmark URL?');
$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.');
$f->notes = sprintf(
$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->collapsed = Inputfield::collapsedBlank;
$f->showIf = 'bookmark_type=' . ListerBookmarks::typeOwned;
$fieldset->add($f);
if($bookmarkID) {
/** @var InputfieldAsmSelect $f */
$f = $this->wire('modules')->get('InputfieldAsmSelect');
$f->attr('name', 'bookmarks_sort');
$f->label = $this->_('Bookmarks sort order');
$f->icon = 'sort';
$f->setAsmSelectOption('removeLabel', '');
$value = array();
foreach($bookmarks as $n => $b) {
$f->addOption($n, $b['title']);
$value[] = $n;
$bookmarks = $bookmark['type'] == ListerBookmarks::typePublic ? $this->bookmarks->getPublicBookmarks() : $this->bookmarks->getOwnedBookmarks();
// option for changing the order of bookmarks
if(count($bookmarks) > 1) {
/** @var InputfieldAsmSelect $f */
$f = $this->wire('modules')->get('InputfieldAsmSelect');
$f->attr('name', 'bookmarks_sort');
$f->label = $this->_('Order');
$f->icon = 'sort';
$f->setAsmSelectOption('removeLabel', '');
$value = array();
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->attr('name', 'submit_bookmark');
$submit->attr('name', 'submit_save_bookmark');
$submit->icon = 'bookmark-o';
$fieldset->add($submit);
@@ -258,21 +421,46 @@ class ProcessPageListerBookmarks extends Wire {
* @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->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);
$this->lister->headline($fieldset->label);
/** @var InputfieldHidden $f */
$f = $this->wire('modules')->get('InputfieldHidden');
$f->attr('name', 'bookmark_id');
$f->attr('value', $bookmarkID);
@@ -290,30 +478,52 @@ class ProcessPageListerBookmarks extends Wire {
* Performs redirect after saving
*
*/
protected function saveBookmark() {
protected function executeSaveBookmark() {
$input = $this->wire('input');
$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');
$bookmarkDesc = $input->post->text('bookmark_desc');
if(!$bookmarkID && empty($bookmarkTitle)) {
$this->wire('session')->redirect('../#tab_bookmarks');
$this->redirectToBookmarks();
return;
}
if($bookmarkID) {
$existingBookmark = $this->bookmarks->getBookmark($bookmarkID);
if(!$existingBookmark || !$this->bookmarks->isBookmarkEditable($existingBookmark)) {
throw new WirePermissionException("Bookmark not editable");
}
} else {
$existingBookmark = null;
}
$bookmarkSort = '';
$textOptions = array('maxLength' => 1024, 'stripTags' => false);
$bookmarkSelector = $bookmarkID ? $input->post->text('bookmark_selector', $textOptions) : $this->lister->getSelector();
$textOptions = array(
'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];
$bookmarkSelector = preg_replace('/\b(include|sort|limit)=[^,]+,?/', '', $bookmarkSelector);
if($this->lister->initSelector && strpos($bookmarkSelector, $this->lister->initSelector) !== false) {
// ensure that $selector does not contain initSelector
$bookmarkSelector = str_replace($this->lister->initSelector, '', $bookmarkSelector);
}
$bookmarkSelector = str_replace(', , ', ', ', $bookmarkSelector);
$bookmarkSelector = trim($bookmarkSelector, ', ');
if($bookmarkID) {
$bookmarkColumns = $input->post('bookmark_columns');
@@ -330,63 +540,89 @@ class ProcessPageListerBookmarks extends Wire {
$bookmarkColumns = $this->lister->columns;
}
$bookmark = array(
$bookmark = $this->bookmarks->_bookmark(array(
'id' => $bookmarkID,
'title' => $bookmarkTitle,
'selector' => trim($bookmarkSelector, ", "),
'desc' => $bookmarkDesc,
'selector' => $bookmarkSelector,
'columns' => $bookmarkColumns,
'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');
$_bookmarks = isset($data['bookmarks']) ? $data['bookmarks'] : array();
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;
if($this->user->isSuperuser()) {
$bookmark['type'] = $input->post->int('bookmark_type');
$bookmark['roles'] = $input->post->intArray('bookmark_roles');
} else {
$n = time();
while(isset($bookmarks[$n])) $n++;
$bookmark['type'] = ListerBookmarks::typeOwned;
$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
if($input->post->bookmarks_sort) {
if($input->post('bookmarks_sort')) {
$sorted = array();
foreach($input->post->intArray('bookmarks_sort') as $bmid) {
$bm = $bookmarks["_$bmid"];
$sorted["_$bmid"] = $bm;
foreach($input->post->array('bookmarks_sort') as $bmid) {
$bmid = $this->bookmarks->_bookmarkID($bmid);
if(!isset($bookmarks[$bmid])) continue;
$bm = $bookmarks[$bmid];
$sorted[$bmid] = $bm;
}
$bookmarks = $sorted;
}
if($bookmarkID && $input->post('delete_bookmark') == $bookmarkID) {
unset($bookmarks["_$n"]);
$this->message(sprintf($this->_('Deleted bookmark: %s'), $bookmarkTitle));
$bookmarkID = 0;
if($bookmark['type'] == ListerBookmarks::typeOwned) {
$this->bookmarks->saveOwnedBookmarks($bookmarks);
} else {
$this->message(sprintf($this->_('Saved bookmark: %s'), $bookmarkTitle));
$this->bookmarks->savePublicBookmarks($bookmarks);
}
$_bookmarks["_$page->id"] = $bookmarks;
$data['bookmarks'] = $_bookmarks;
$this->redirectToBookmarks($bookmarkID);
}
$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);
}
}