├── .gitignore ├── README.md ├── PageListPermissions.css ├── PageListPermissions.module ├── ProcessPageListPermissions.module ├── PageListPermissions.js └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .svn -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Page List Permissions 2 | ===================== 3 | 4 | This module is intended as a helper module and/or UI extension to User Groups, 5 | making it possible to view and edit Page permissions directly from Page List. 6 | 7 | User Groups is fully functional without Page List Permissions and the intention 8 | here isn't to add anything really "new" to it's core feature set, just to solve 9 | certain permission management issues / needs by providing an alternative UI. 10 | 11 | ## Installing 12 | 13 | Copy PageListPermissions folder to your /site/modules/, go to Admin > Modules, 14 | hit "Check for new modules" and install Page List Permissions. Process Page 15 | List Permissions will be installed automatically. 16 | 17 | ## Requirements 18 | 19 | * User Groups module: https://github.com/apeisa/UserGroups 20 | * ProcessWire 2.3.14+ (current "dev" branch) 21 | 22 | ## License 23 | 24 | This program is free software; you can redistribute it and/or 25 | modify it under the terms of the GNU General Public License 26 | as published by the Free Software Foundation; either version 2 27 | of the License, or (at your option) any later version. 28 | 29 | This program is distributed in the hope that it will be useful, 30 | but WITHOUT ANY WARRANTY; without even the implied warranty of 31 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 32 | GNU General Public License for more details. 33 | 34 | You should have received a copy of the GNU General Public License 35 | along with this program; if not, write to the Free Software 36 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 37 | 38 | (See included LICENSE file for full license text.) -------------------------------------------------------------------------------- /PageListPermissions.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow-y: scroll; 3 | } 4 | 5 | .access-management-close { 6 | position: absolute; 7 | right: -0.25em; 8 | top: -1.85em; 9 | color: #333; 10 | font-size: 1em; 11 | padding: 0.25em 0.35em; 12 | } 13 | 14 | .content .PageList .PageListItem > a.PageListPage i.access-management { 15 | color: #ff5c00; 16 | padding-left: 0.5em; 17 | display: inline-block; 18 | text-decoration: none !important; 19 | } 20 | 21 | .content .PageList .PageListItem > a.PageListPage i.access-management.trash { 22 | display: none; 23 | } 24 | 25 | body.access-management-loading *:hover { 26 | cursor: progress; 27 | } 28 | 29 | i.access-management.status-disabled { 30 | opacity: 0.2; 31 | } 32 | 33 | i.access-management.open { 34 | color: red; 35 | opacity: 1; 36 | } 37 | 38 | #access-management { 39 | position: absolute; 40 | margin-top: -2em; 41 | overflow: visible; 42 | background: #fefefe; 43 | border: 5px solid #eee; 44 | box-shadow: inset 0 0 1px #333; 45 | border-radius: 2px; 46 | padding: 0.5em 1em; 47 | min-height: 50px; 48 | z-index: 100; 49 | width: 50%; 50 | } 51 | 52 | #access-management.preview .edit-only, 53 | #access-management.edit .preview-only { 54 | display: none; 55 | } 56 | 57 | #access-management-overlay { 58 | top: 0; 59 | left: 0; 60 | z-index: 98; 61 | width: 100%; 62 | height: 100%; 63 | opacity: 0.5; 64 | display: none; 65 | background: #fff; 66 | position: absolute; 67 | } 68 | 69 | #access-management-overlay.transparent { 70 | background: transparent; 71 | } 72 | 73 | body.access-management-overlay #content { 74 | /* for PW 2.4 default admin theme */ 75 | z-index: auto; 76 | } 77 | 78 | #access-management.preview { 79 | display: none; 80 | } 81 | 82 | #access-management.edit { 83 | border: 5px solid #ddd; 84 | padding-bottom: 1.05em; 85 | } 86 | 87 | #access-management p { 88 | font-style: italic; 89 | text-align: center; 90 | line-height: 1.6em; 91 | padding-top: 0.15em; 92 | } 93 | 94 | #access-management p strong { 95 | padding: 0.25em; 96 | background: #eee; 97 | border-radius: 2px; 98 | } 99 | 100 | #access-management table { 101 | width: 100%; 102 | } 103 | 104 | #access-management table th, 105 | #access-management table td { 106 | padding: 0.25em; 107 | border: 1px solid #eee; 108 | } 109 | 110 | #access-management table th, 111 | #access-management table th:hover { 112 | color: #222; 113 | background: #eee; 114 | text-shadow: 0 1px 0 rgba(255,255,255,0.5); 115 | } 116 | 117 | #access-management table th.first { 118 | width: 75%; 119 | } 120 | 121 | #access-management textarea { 122 | display: none; 123 | } 124 | 125 | #access-management .decoration { 126 | top: 8px; 127 | color: #eee; 128 | left: -18px; 129 | width: 15px; 130 | height: 15px; 131 | font-size: 26px; 132 | overflow: hidden; 133 | position: absolute; 134 | text-align: right; 135 | } 136 | 137 | #access-management.edit .decoration { 138 | color: #ddd; 139 | } -------------------------------------------------------------------------------- /PageListPermissions.module: -------------------------------------------------------------------------------- 1 | 'Page List Permissions', 29 | 'summary' => 'Enable viewing and editing permissions set by User Groups module via Page List', 30 | 'author' => 'Teppo Koivula', 31 | 'version' => 40, 32 | 'singular' => true, 33 | 'autoload' => true, 34 | 'requires' => 'ProcessUserGroups', 35 | 'installs' => 'ProcessPageListPermissions', 36 | ); 37 | } 38 | 39 | /** 40 | * Initialization function 41 | * 42 | * This function attachs required hooks. 43 | * 44 | */ 45 | public function init() { 46 | if ($this->user->hasPermission('user-admin')) { 47 | $this->addHookAfter('ProcessPageListRender::getPageLabel', $this, 'hookGetPageLabel'); 48 | $this->addHookAfter('ProcessPageList::execute', $this, 'hookExecute'); 49 | } 50 | } 51 | 52 | /** 53 | * Add access management icon to Page List 54 | * 55 | * Access management icons are added after names of each page with template 56 | * which includes manage_access field. 57 | * 58 | * @param HookEvent $event 59 | */ 60 | public function hookGetPageLabel(HookEvent $event) { 61 | 62 | // this only applies to pages with access management enabled 63 | $page = $event->arguments[0]; 64 | if (!$page->template->hasField('manage_access')) return; 65 | 66 | $icon = $page->manage_access && (count($page->view_groups) || count($page->edit_groups)) ? "lock" : "unlock-alt"; 67 | $status = $page->manage_access ? "active" : "disabled"; 68 | $trash = $page->isTrash() ? " trash" : ""; 69 | $icon = ""; 70 | $event->return .= $icon; 71 | 72 | } 73 | 74 | /** 75 | * Inject scripts and styles to Page Edit and translations to JS config var 76 | * 77 | * @param HookEvent $event 78 | */ 79 | public function hookExecute(HookEvent $event) { 80 | 81 | // load MarkupAdminDataTable (we'll need it's styles later) 82 | $table = $this->modules->get("MarkupAdminDataTable"); 83 | 84 | // inject settings and translations 85 | $process = $this->modules->getModuleID("ProcessPageListPermissions"); 86 | $processPage = $this->pages->get("process=$process"); 87 | $this->config->js('PageListPermissions', array( 88 | 'i18n' => array( 89 | 'ajaxError' => __("An error occurred, please try again"), 90 | 'notAllowed' => __("Not allowed for this group"), 91 | ), 92 | 'trashPageID' => $this->config->trashPageID, 93 | 'processPage' => $processPage->url(), 94 | )); 95 | 96 | // inject scripts and styles 97 | $class = $this->className(); 98 | $info = $this->getModuleInfo(); 99 | $version = (int) $info['version']; 100 | if (is_file($this->config->paths->$class . "$class.js")) $this->config->scripts->add($this->config->urls->$class . "$class.js?v=$version"); 101 | if (is_file($this->config->paths->$class . "$class.css")) $this->config->styles->add($this->config->urls->$class . "$class.css?v=$version"); 102 | 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /ProcessPageListPermissions.module: -------------------------------------------------------------------------------- 1 | 'Page List Permissions', 28 | 'summary' => 'This module provides UI for Page List Permissions', 29 | 'author' => 'Teppo Koivula', 30 | 'version' => 28, 31 | 'singular' => true, 32 | 'autoload' => false, 33 | 'requires' => array('PageListPermissions', 'ProcessUserGroups'), 34 | 'permission' => 'user-admin', 35 | ); 36 | } 37 | 38 | /** 39 | * Name of admin page used by this module 40 | * 41 | */ 42 | const PAGE_NAME = 'page-list-access-management'; 43 | 44 | /** 45 | * Names of groups that can never gain edit permission 46 | * 47 | */ 48 | private $edit_disabled_for = array( 49 | 'everyone', 50 | 'logged', 51 | ); 52 | 53 | /** 54 | * Executed when a page with this Process assigned is accessed 55 | * 56 | * @param int $page_id ID of page to render markup for (optional) 57 | */ 58 | public function ___execute($page_id = null) { 59 | 60 | $page_id = $page_id ? (int) $page_id : (int) $this->input->get->id; 61 | if (!$page_id) return false; 62 | 63 | $page = $this->pages->get($page_id); 64 | if (!$page->id) return false; 65 | 66 | $out = ""; 67 | $groups = $page->manage_access ? $page->view_groups->add($page->edit_groups) : null; 68 | $table_class = !$page->manage_access || !count($groups) ? " class='edit-only'" : null; 69 | 70 | if (!$page->manage_access) { 71 | // This page doesn't currently manage it's access 72 | $access_page = $page->inherit_access; 73 | $out .= "

" . sprintf(__("Permissions for this page are defined at %s. Click to define here instead."), "{$access_page->url}") . "

"; 74 | } else if (!count($groups)) { 75 | // No group specific permissions set for this page 76 | $out .= "

" . __("No permissions defined for this page. Click to define permissions now.") . "

"; 77 | } 78 | 79 | // Generate main content table (including both currently selected 80 | // groups and form elements for adding new ones) 81 | $table = ""; 82 | $table .= ""; 83 | $table .= "" . __("Group") . ""; 84 | $table .= "" . __("View") . ""; 85 | $table .= "" . __("Edit") . ""; 86 | $table .= ""; 87 | if ($page->manage_access && count($groups)) { 88 | foreach ($groups as $group) { 89 | $perm = $page->edit_groups->get($group) ? "edit" : "view"; 90 | $edit_disabled = ""; 91 | $class = ""; 92 | if (in_array($group->name, $this->edit_disabled_for)) { 93 | $edit_disabled = " disabled='disabled' title='" . __('Not allowed for this group') . "'"; 94 | $class = " class='edit-disabled'"; 95 | } 96 | $table .= ""; 97 | $table .= "{$group->title}"; 98 | $table .= ""; 99 | $table .= ""; 100 | $table .= ""; 101 | } 102 | } 103 | $selectable_groups = $this->groups->find("id!=$groups, sort=sort"); 104 | $style = count($selectable_groups) ? "" : " style='display: none'"; 105 | $table .= ""; 106 | $table .= ""; 112 | $table .= ""; 113 | $table .= ""; 114 | $table .= ""; 115 | $table .= ""; 116 | $out .= $table; 117 | 118 | // form action attibute 119 | $moduleID = $this->modules->getModuleID($this); 120 | $modulePage = $this->pages->get("template=admin, process=$moduleID, name=" . self::PAGE_NAME); 121 | $action = $modulePage->url . "save/"; 122 | 123 | // hidden input for page id 124 | $out .= ""; 125 | 126 | // textarea for data 127 | $out .= ""; 128 | 129 | // submit button 130 | $submit = $this->modules->get("InputfieldSubmit"); 131 | $submit->attr('value', __("Save changes")); 132 | $submit->attr('disabled', 'disabled'); 133 | $submit->attr('name', ''); 134 | $submit->attr('data-confirm', __("Unsaved changes will be lost. Are you sure you want to close the edit window?")); 135 | $submit->addClass('ui-state-disabled'); 136 | $submit->addClass('edit-only'); 137 | $out .= $submit->render(); 138 | 139 | return "
$out
"; 140 | 141 | } 142 | 143 | /** 144 | * Save permissions for target page 145 | * 146 | */ 147 | public function ___executeSave() { 148 | 149 | $id = (int) $this->input->post->id; 150 | $data = json_decode($this->input->post->data); 151 | if ($id && count($data)) { 152 | $page = wire('pages')->get($id); 153 | if ($page->id) { 154 | $page->view_groups = new PageArray(); 155 | $page->edit_groups = new PageArray(); 156 | foreach ($data as $group_id => $perms) { 157 | $group_id_int = (int) $group_id; 158 | if ($group_id_int) $group = $this->pages->get("template=user-group, id=$group_id_int"); 159 | if (!$group_id_int || !$group->id) throw new WireException("Invalid group: $group_id"); 160 | if ($perms->view) { 161 | $page->view_groups->add($group); 162 | if ($perms->edit && !in_array($group->name, $this->edit_disabled_for)) { 163 | $page->edit_groups->add($group); 164 | } 165 | } 166 | } 167 | // enable manage_access if groups are/were selected 168 | if (count($page->view_groups) || count($page->edit_groups)) $page->manage_access = 1; 169 | // save page (triggers rebuilding of permissions) 170 | $page->save(); 171 | // generate JSON output for JavaScript AJAX POST request 172 | $icon = count($page->view_groups) || count($page->edit_groups) ? 'lock' : 'unlock-alt'; 173 | $status = $page->manage_access ? "active" : "disabled"; 174 | $return = array( 175 | 'data' => $this->execute($page->id), 176 | 'classes' => "status-{$status} fa-{$icon} icon-{$icon}", 177 | ); 178 | header('Content-type: application/json'); 179 | die(json_encode($return)); 180 | } 181 | } else { 182 | throw new WireException("Invalid POST data"); 183 | } 184 | 185 | } 186 | 187 | /** 188 | * Called only when this module is installed 189 | * 190 | * Creates new page with this Process module assigned. 191 | * 192 | */ 193 | public function ___install() { 194 | 195 | // create a page for this module 196 | $page = new Page(); 197 | $page->template = 'admin'; 198 | $page->name = self::PAGE_NAME; 199 | $page->process = $this; 200 | $page->parent = $this->pages->get($this->config->adminRootPageID)->child('name=setup'); 201 | 202 | // make page title match module title 203 | $info = self::getModuleInfo(); 204 | $page->title = $info['title']; 205 | 206 | // hide page from menu and save 207 | $page->addStatus(Page::statusHidden); 208 | $page->save(); 209 | 210 | // tell user that we've created a new page 211 | $this->message("Created Page: {$page->path}"); 212 | 213 | } 214 | 215 | /** 216 | * Called only when this module is uninstalled 217 | * 218 | * Removes page associated with this Process module. 219 | * 220 | */ 221 | public function ___uninstall() { 222 | 223 | // find the page we installed, locating it by the process field (which has the module ID) 224 | // it would probably be sufficient just to locate by name, but this is just to be extra sure. 225 | $moduleID = $this->modules->getModuleID($this); 226 | $page = $this->pages->get("template=admin, process=$moduleID, name=" . self::PAGE_NAME . ", include=hidden"); 227 | 228 | if ($page->id) { 229 | // if we found the page, let the user know and delete it 230 | $this->message("Deleting Page: {$page->path}"); 231 | $page->delete(); 232 | } 233 | 234 | } 235 | 236 | } -------------------------------------------------------------------------------- /PageListPermissions.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // Config settings as defined by PageListPermissions.module 4 | var moduleConfig = config.PageListPermissions; 5 | 6 | // $view objects are cached to avoid new request being sent for each access 7 | // management icon "mouse enter" or "click" event (note: 15 minute timeout) 8 | var cacheTimeout = 15 * 60 * 1000; 9 | var cache = {}; 10 | 11 | // clones is used to store page list links so that when hover/click events 12 | // are triggered for access management icon we can remove click event from 13 | // matching page list link, store original link in clones and later return 14 | // it without too much hassle (returning removed events is complicated..) 15 | var clones = {}; 16 | 17 | // var placeholders and general use jQuery objects (close button) 18 | var $a, $view, $preview, $request, viewData, marginLeft, previewLock; 19 | var $close = $('x'); 20 | 21 | // overlay element "fades out" background and closes $view when clicked 22 | var $overlay = $('
'); 23 | if (!$.support.opacity) $overlay.addClass('transparent'); 24 | $overlay.on('click', function() { closeView() }).prependTo('body'); 25 | 26 | var setData = function() { 27 | // fill in data (hash, later converted to JSON string) with selected 28 | // groups and permissions those groups have (view/edit) 29 | var data = {}; 30 | $('#access-management form tr:has(td)').each(function() { 31 | var group = $(this).find('td:first').data('group-id'); 32 | if (group) { 33 | var perms = { 34 | view: true, 35 | edit: $(this).find('input[data-name=edit]').prop('checked') 36 | }; 37 | data[group] = perms; 38 | } 39 | }); 40 | data = JSON.stringify(data); 41 | $('#access-management form textarea[name=data]').val(data); 42 | // check if data has changed since $view object was created; if it was, 43 | // enable form submit button and make it possible to submit the form 44 | if ($view && viewData && data != viewData) { 45 | $view.find('button').removeAttr('disabled').removeClass('ui-state-disabled'); 46 | } else if ($view && viewData) { 47 | $view.find('button').attr('disabled', 'disabled').addClass('ui-state-disabled'); 48 | } 49 | // return data, so that we can store it when needed (such as when new 50 | // $view object is initiated / created) 51 | return data; 52 | } 53 | 54 | var setCache = function(page, data) { 55 | // store data from AJAX GET request (and page parents) to cache hash 56 | var date = new Date(); 57 | cache[page] = { data: data, parents: [], time: date.getTime() }; 58 | $('.PageListID' + page).parents('.PageList').each(function() { 59 | var $parent = $(this).prev('.PageListItem'); 60 | if ($parent.length && $parent.find('i.access-management').length) { 61 | cache[page].parents.push($parent.find('i.access-management').data('page')); 62 | } 63 | }); 64 | } 65 | 66 | var clearCache = function(page) { 67 | // clear cache for given page and it's children 68 | delete cache[page]; 69 | for (key in cache) { 70 | if ($.inArray(page, cache[key].parents) > -1) { 71 | delete cache[key]; 72 | } 73 | } 74 | } 75 | 76 | var closeView = function() { 77 | // check if there are unsaved changes 78 | var $button = $view.find('button'); 79 | if (!$button.attr('disabled')) { 80 | // changes have been made, confirm before closing view 81 | if (!confirm($button.data('confirm'))) return false; 82 | } 83 | // handle cached elements and data 84 | var viewPage = $view.data('page'); 85 | if (clones[viewPage]) { 86 | clones[viewPage].find('i.access-management:first').removeClass('open'); 87 | $('.PageListID' + viewPage + ' > a:first').replaceWith(clones[viewPage]); 88 | clones[viewPage] = null; 89 | } 90 | // hide (and unset) view object (grab $parent first) 91 | var $parent = $view.parents('.PageListItem:first'); 92 | $view.fadeOut('fast', function() { 93 | $(this).remove(); 94 | $view = null; 95 | }); 96 | viewData = null; 97 | // hide overlay and display visual effect if save occurred 98 | $overlay.fadeOut('fast', function() { 99 | $('body').removeClass('access-management-overlay'); 100 | if ($parent.hasClass('saved')) { 101 | $parent.fadeOut('fast', function() { 102 | $(this).fadeIn('fast', function() { 103 | $(this).removeClass('saved'); 104 | }); 105 | }); 106 | } 107 | }); 108 | } 109 | 110 | $('#PageListContainer') 111 | .on('mouseenter', 'i.access-management', function() { 112 | // on mouse enter show $preview box, which shows page-specific 113 | // permissions but doesn't yet allow user to modify them (that 114 | // is what the $view object opened on click is for) 115 | if (!$(this).hasClass('open')) { 116 | var page = $(this).data('page'); 117 | var $item = $(this).parents('.PageListItem:first'); 118 | var $link = $item.find('> a:first'); 119 | clones[page] = $link.clone(true); 120 | $link.removeAttr('title').unbind('click'); 121 | marginLeft = parseInt($link.css('width')) + 35; 122 | // .detail is the element containing number of page's children 123 | if ($link.find('.detail').length) { 124 | marginLeft += parseInt($link.find('.detail').css('width')); 125 | } 126 | $(this).data('margin-left', marginLeft); 127 | if (!$view) { 128 | var $i = $(this); 129 | $preview = $('
'); 130 | $preview 131 | .css('margin-left', marginLeft) 132 | .prepend('') 133 | .fadeIn('fast'); 134 | var date = new Date(); 135 | if (cache[page] && cache[page]['time'] + cacheTimeout >= date.getTime()) { 136 | // fetch previously loaded data from cache 137 | $preview.append(cache[page].data).appendTo($item); 138 | } else { 139 | // data not found from cache, load now (if prev request 140 | // is still running, attempt to abort and unset it) 141 | if ($request && $request.readyState < 4) { 142 | $request.abort(); 143 | $request = null; 144 | } 145 | $('body').addClass('access-management-loading'); 146 | $request = $.get(moduleConfig.processPage, { id: page }, function(data) { 147 | if (!data) { 148 | alert(moduleConfig.i18n.ajaxError); 149 | $i.trigger('mouseleave'); 150 | return false; 151 | } 152 | setCache(page, data); 153 | // under certain circumstances $preview might not 154 | // exist here; check before trying to append data 155 | if ($preview) $preview.append(data).appendTo($item); 156 | $('body').removeClass('access-management-loading'); 157 | }) 158 | } 159 | } 160 | } 161 | }) 162 | .on('mouseleave', 'i.access-management', function() { 163 | // hide visible preview window (unless locked, i.e. forced visible) 164 | // when mouse cursor is moved off access management icon 165 | if (!previewLock) { 166 | var page = $(this).data('page'); 167 | if (clones[page] && (!$view || $view.data('page') != page)) { 168 | $('.PageListID' + page + ' > a:first').replaceWith(clones[page]); 169 | clones[page] = null; 170 | } 171 | if ($preview) { 172 | $preview.remove(); 173 | $preview = null; 174 | } 175 | } 176 | }) 177 | .on('click', 'i.access-management', function() { 178 | // access management icon was clicked, show edit view with form etc. 179 | // or close it if already open (in case that unsaved changes exist 180 | // confirm and only then close to avoid losing any data) 181 | var page = $(this).data('page'); 182 | var $item = $(this).parents('.PageListItem:first'); 183 | var viewOpen = $(this).hasClass('open'); 184 | if ($view) { 185 | // $view object refers to permissions edit view and in this case 186 | // it has already been opened, so we're going to close it now. We 187 | // don't actually even need to know if it's related to page just 188 | // clicked, it should be removed nevertheless. 189 | closeView(); 190 | } 191 | if (!viewOpen) { 192 | $('i.access-management').removeClass('open'); 193 | $(this).addClass('open'); 194 | var $i = $(this); 195 | // $view object refers to view where permissions can be modified 196 | $view = $('
'); 197 | $view 198 | .css('margin-left', $(this).data('margin-left')) 199 | .prepend('') 200 | .prepend($close); 201 | $('body').addClass('access-management-overlay'); 202 | $overlay.fadeIn('fast'); 203 | var date = new Date(); 204 | if (cache[page] && cache[page]['time'] + cacheTimeout >= date.getTime()) { 205 | // fetch previously loaded data from cache 206 | $view.append(cache[page].data).appendTo($item); 207 | viewData = setData(); 208 | if ($preview) { 209 | $preview.remove(); 210 | $preview = null; 211 | } 212 | } else { 213 | // data not found from cache, load now 214 | previewLock = true; 215 | $('body').addClass('access-management-loading'); 216 | $.get(moduleConfig.processPage, { id: page }, function(data) { 217 | previewLock = false; 218 | if (!data) { 219 | alert(moduleConfig.i18n.ajaxError); 220 | $i.click(); 221 | return false; 222 | } 223 | setCache(page, data); 224 | $view.append(data).appendTo($item); 225 | if ($preview) { 226 | $preview.remove(); 227 | $preview = null; 228 | } 229 | viewData = setData(); 230 | $('body').removeClass('access-management-loading'); 231 | }); 232 | } 233 | } 234 | return false; 235 | }) 236 | .on('change', 'select[data-name=group]', function() { 237 | // groups select value was changed, add group to table and rebuild 238 | // data (unless selected option was empty) 239 | $option = $(this).find('> option:selected'); 240 | if ($option.val()) { 241 | var $parent = $(this).parents('tr:first'); 242 | var $editCB = $parent.find('input[data-name=edit]'); 243 | // certain predefined groups can never gain edit permissions 244 | var editDisabledFor = ['everyone', 'logged']; 245 | var disableEdit = $.inArray($option.data('group-name'), editDisabledFor) !== -1; 246 | // add new group with permission defined by checkboxes next to 247 | // groups dropdown (note: "view" permission is always checked) 248 | var perms = { 249 | view: true, 250 | edit: !disableEdit && $editCB.prop('checked') ? true : false 251 | }; 252 | var $tr = $(document.createElement('tr')); 253 | if (disableEdit) $tr.addClass('edit-disabled'); 254 | $tr 255 | .append('' + $option.text() + '') 256 | .append('') 257 | .append('') 258 | .insertBefore($parent); 259 | $option.remove(); 260 | if ($(this).find('> option').length == 1) $parent.hide(); 261 | // update current data in hidden textarea when changes are made 262 | setData(); 263 | } 264 | }) 265 | .on('change', 'input[type=checkbox]', function() { 266 | // checkbox value in permissions form changed, rebuild data and 267 | // if view permission was removed return group to select menu 268 | if ($(this).data('name') == "view") { 269 | // if view permission is removed from a group, group should also 270 | // be removed and returned to dropdown, where it can be re-added 271 | var $select = $('select[data-name=group]'); 272 | var $parent = $(this).parents('tr:first'); 273 | var $option = $(document.createElement('option')); 274 | $option 275 | .attr('value', $parent.find('> td:first').data('group-id')) 276 | .data('group-name', $parent.find('> td:first').data('group-name')) 277 | .text($parent.find('> td:first').text()) 278 | .appendTo($select); 279 | if ($select.parents('tr:first').not(':visible')) { 280 | $select.parents('tr:first').show(); 281 | } 282 | $parent.remove(); 283 | } 284 | // update current data in hidden textarea when changes are made 285 | setData(); 286 | }) 287 | .on('click', '.access-management-close', function() { 288 | closeView(); 289 | }) 290 | .on('pageMoved', '.PageListItem', function() { 291 | var pageHasIcon = $(this).find('> a > i.access-management:not(.trash)').length; 292 | var pageIsTrash = $(this).parents('.PageList:first').prev('.PageListItem').hasClass('PageListID' + moduleConfig.trashPageID); 293 | var pageChanged = (pageHasIcon && pageIsTrash) || (!pageHasIcon && !pageIsTrash); 294 | var page = $(this).data('pageId'); 295 | clearCache(page); 296 | if (pageChanged) { 297 | var $i = $('.PageListID' + page).find('i.access-management'); 298 | if (pageIsTrash) $i.addClass('trash'); 299 | else $i.removeClass('trash'); 300 | } 301 | }) 302 | .on('submit', 'form', function(event) { 303 | // edit permissions form has been submitted, trigger AJAX save 304 | event.preventDefault(); 305 | var $i = $('i.access-management.open'); 306 | var page = $i.data('page'); 307 | $('body').addClass('access-management-loading'); 308 | $.post(moduleConfig.processPage+'save', $(this).serialize(), function(data) { 309 | if (!data) { 310 | alert(moduleConfig.i18n.ajaxError); 311 | closeView(); 312 | return false; 313 | } 314 | // delete all cached data that might've been affected by save 315 | clearCache(page); 316 | // data.classes contains status and icon classes for access 317 | // management icon and data.data contains new view content 318 | setCache(page, data.data); 319 | $i 320 | .removeClass(function (index, css) { 321 | return (css.match (/\b(fa|icon|status)-\S+/g) || []).join(' '); 322 | }) 323 | .addClass(data.classes) 324 | .parents('.PageListItem:first') 325 | .addClass('saved'); 326 | // copy of current page list link is stored in clones; make sure 327 | // it doesn't contain conflicting access management toggle class 328 | clones[$view.data('page')].find('i:first').attr('class', $i.attr('class')); 329 | $view.find('button').attr('disabled', 'disabled'); 330 | closeView(); 331 | $('body').removeClass('access-management-loading'); 332 | }); 333 | }); 334 | }); 335 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------