├── .gitattributes ├── .gitignore └── CustomPageRoles.module /.gitattributes: -------------------------------------------------------------------------------- 1 | *.doc diff=astextplain 2 | *.DOC diff=astextplain 3 | *.docx diff=astextplain 4 | *.DOCX diff=astextplain 5 | *.dot diff=astextplain 6 | *.DOT diff=astextplain 7 | *.pdf diff=astextplain 8 | *.PDF diff=astextplain 9 | *.rtf diff=astextplain 10 | *.RTF diff=astextplain 11 | 12 | *.jpg binary 13 | *.png binary 14 | *.gif binary 15 | 16 | *.cs text=auto diff=csharp 17 | *.vb text=auto 18 | *.c text=auto 19 | *.cpp text=auto 20 | *.cxx text=auto 21 | *.h text=auto 22 | *.hxx text=auto 23 | *.py text=auto 24 | *.rb text=auto 25 | *.java text=auto 26 | *.html text=auto 27 | *.htm text=auto 28 | *.css text=auto 29 | *.scss text=auto 30 | *.sass text=auto 31 | *.less text=auto 32 | *.js text=auto 33 | *.lisp text=auto 34 | *.clj text=auto 35 | *.sql text=auto 36 | *.php text=auto 37 | *.lua text=auto 38 | *.m text=auto 39 | *.asm text=auto 40 | *.erl text=auto 41 | *.fs text=auto 42 | *.fsx text=auto 43 | *.hs text=auto 44 | 45 | *.csproj text=auto merge=union 46 | *.vbproj text=auto merge=union 47 | *.fsproj text=auto merge=union 48 | *.dbproj text=auto merge=union 49 | *.sln text=auto eol=crlf merge=union 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ################# 3 | ## Eclipse 4 | ################# 5 | 6 | *.pydevproject 7 | .project 8 | .metadata 9 | bin/** 10 | tmp/** 11 | tmp/**/* 12 | *.tmp 13 | *.bak 14 | *.swp 15 | *~.nib 16 | local.properties 17 | .classpath 18 | .settings/ 19 | .loadpath 20 | 21 | # External tool builders 22 | .externalToolBuilders/ 23 | 24 | # Locally stored "Eclipse launch configurations" 25 | *.launch 26 | 27 | # CDT-specific 28 | .cproject 29 | 30 | # PDT-specific 31 | .buildpath 32 | 33 | 34 | ################# 35 | ## Visual Studio 36 | ################# 37 | 38 | ## Ignore Visual Studio temporary files, build results, and 39 | ## files generated by popular Visual Studio add-ons. 40 | 41 | # User-specific files 42 | *.suo 43 | *.user 44 | *.sln.docstates 45 | 46 | # Build results 47 | **/[Dd]ebug/ 48 | **/[Rr]elease/ 49 | *_i.c 50 | *_p.c 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.vspscc 65 | .builds 66 | **/*.dotCover 67 | 68 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 69 | #**/packages/ 70 | 71 | # Visual C++ cache files 72 | ipch/ 73 | *.aps 74 | *.ncb 75 | *.opensdf 76 | *.sdf 77 | 78 | # Visual Studio profiler 79 | *.psess 80 | *.vsp 81 | 82 | # ReSharper is a .NET coding add-in 83 | _ReSharper* 84 | 85 | # Installshield output folder 86 | [Ee]xpress 87 | 88 | # DocProject is a documentation generator add-in 89 | DocProject/buildhelp/ 90 | DocProject/Help/*.HxT 91 | DocProject/Help/*.HxC 92 | DocProject/Help/*.hhc 93 | DocProject/Help/*.hhk 94 | DocProject/Help/*.hhp 95 | DocProject/Help/Html2 96 | DocProject/Help/html 97 | 98 | # Click-Once directory 99 | publish 100 | 101 | # Others 102 | [Bb]in 103 | [Oo]bj 104 | sql 105 | TestResults 106 | *.Cache 107 | ClientBin 108 | stylecop.* 109 | ~$* 110 | *.dbmdl 111 | Generated_Code #added for RIA/Silverlight projects 112 | 113 | # Backup & report files from converting an old project file to a newer 114 | # Visual Studio version. Backup files are not needed, because we have git ;-) 115 | _UpgradeReport_Files/ 116 | Backup*/ 117 | UpgradeLog*.XML 118 | 119 | 120 | 121 | ############ 122 | ## Windows 123 | ############ 124 | 125 | # Windows image file caches 126 | Thumbs.db 127 | 128 | # Folder config file 129 | Desktop.ini 130 | 131 | 132 | ############# 133 | ## Python 134 | ############# 135 | 136 | *.py[co] 137 | 138 | # Packages 139 | *.egg 140 | *.egg-info 141 | dist 142 | build 143 | eggs 144 | parts 145 | bin 146 | var 147 | sdist 148 | develop-eggs 149 | .installed.cfg 150 | 151 | # Installer logs 152 | pip-log.txt 153 | 154 | # Unit test / coverage reports 155 | .coverage 156 | .tox 157 | 158 | #Translations 159 | *.mo 160 | 161 | #Mr Developer 162 | .mr.developer.cfg 163 | 164 | # Mac crap 165 | .DS_Store 166 | -------------------------------------------------------------------------------- /CustomPageRoles.module: -------------------------------------------------------------------------------- 1 | 'Custom Page Roles', 8 | 'summary' => 'Control viewable and editable roles at the page level.', 9 | 'version' => 002, 10 | 'permanent' => false, 11 | 'autoload' => true, 12 | 'singular' => true, 13 | ); 14 | } 15 | 16 | public function init() { 17 | $this->addHookAfter("Page::viewable", $this, 'viewable'); 18 | $this->addHookAfter("Page::editable", $this, 'editable'); 19 | $this->addHookAfter("Page::addable", $this, 'addable'); 20 | //$this->addHookAfter("Pages::find", $this, 'hookPagesFind'); 21 | $this->addHookAfter("ProcessPageEdit::buildForm", $this, 'hookBuildForm'); 22 | } 23 | 24 | /** 25 | * We hide the edit_roles and view_roles from others than superusers and users with page-roles -permission 26 | * 27 | */ 28 | public function hookBuildForm(HookEvent $event) { 29 | $user = wire('user'); 30 | if($user->hasPermission('page-roles')) return; 31 | if($user->isSuperuser()) return; 32 | 33 | $form = $event->return; 34 | $field = $form->get('view_roles'); 35 | if($field) $field->collapsed = Inputfield::collapsedHidden; 36 | $field = $form->get('edit_roles'); 37 | if($field) $field->collapsed = Inputfield::collapsedHidden; 38 | $event->return = $form; 39 | } 40 | 41 | /** 42 | * This filters find results to take page based permissions in account. Not in use currently 43 | * 44 | */ 45 | public function hookPagesFind(HookEvent $event) { 46 | $items = $event->return; 47 | foreach($items as $item) { 48 | if($item->template->flags & Template::flagSystem) { 49 | // don't allow this access control on system templates 50 | } else if (!$item->viewable()) { 51 | $items->remove($item); 52 | } 53 | } 54 | $event->return = $items; 55 | } 56 | 57 | 58 | /** 59 | * Hook called after Page::viewable is called 60 | * 61 | */ 62 | public function viewable(HookEvent $event) { 63 | 64 | // get the vars we need to check access 65 | $page = $event->object; 66 | $user = $this->user; 67 | 68 | // no need to check anything if it's the superuser 69 | if($user->isSuperuser()) return; 70 | 71 | // don't allow this access control on system templates 72 | if($page->template->flags & Template::flagSystem) return; 73 | 74 | if ($page->is(Page::statusUnpublished)) return; 75 | 76 | // get the roles we'll be comparing 77 | $pageRoles = $page->view_roles; 78 | $userRoles = $this->user->roles; 79 | 80 | // If there ain't no view_roles, then fallback to template permissions 81 | if (!$pageRoles) return; 82 | 83 | // if page has a 'view_roles' field, but none defined, then inherit 'viewable' state from parent 84 | if(!count($pageRoles)) { 85 | // determine access from page's parent, recursively 86 | // We check if there is at least one parent with edit_roles 87 | foreach($page->parents() as $p) { 88 | if (count($p->view_roles)) { 89 | $event->return = $p->viewable(); 90 | return; 91 | } 92 | } 93 | 94 | // If not view_roles found from parents, then fallback to template access 95 | return; 96 | } 97 | 98 | // we assume it's not viewable until we can prove otherwise 99 | $viewable = false; 100 | 101 | // loop through the pageRoles and try to match up to user 102 | // we don't need to check permissions in the role because 103 | // all roles are assumed to have page-view permission. 104 | foreach($pageRoles as $role) { 105 | if($role->name == 'guest' || $user->hasRole($role)) { 106 | // guest role was found or the user has the required role 107 | $viewable = true; 108 | } 109 | } 110 | $event->return = $viewable; 111 | } 112 | 113 | 114 | public function editable(HookEvent $event) { 115 | 116 | // get the vars we need to check access 117 | $page = $event->object; 118 | $user = $this->user; 119 | 120 | // no need to check anything if it's the superuser 121 | if($user->isSuperuser()) return; 122 | 123 | // don't allow page based access control on system templates 124 | if($page->template->flags & Template::flagSystem) return; 125 | 126 | // get the roles we'll be comparing 127 | $pageRoles = $page->edit_roles; 128 | $userRoles = $this->user->roles; 129 | 130 | 131 | 132 | // if page has a 'edit_roles' field, but none defined, then inherit 'editable' state from parent(s) 133 | if(!count($pageRoles)) { 134 | // We check if there is at least one parent with edit_roles 135 | foreach($page->parents() as $p) { 136 | if (count($p->edit_roles)) { 137 | $event->return = $p->editable(); 138 | return; 139 | } 140 | } 141 | 142 | // If not edit_roles found from parents, then fallback to template access 143 | return; 144 | } 145 | 146 | // we assume it's not editable until we can prove otherwise 147 | $editable = false; 148 | 149 | // loop through the pageRoles and try to match up to user 150 | // we don't need to check permissions in the role because 151 | // all roles are assumed to have page-view / page-edit permission. => actually we should disallow selecting other than roles which have page-edit in first place 152 | foreach($pageRoles as $role) { 153 | if ($user->hasRole($role)) { 154 | $editable = true; 155 | break; 156 | } 157 | } 158 | 159 | // This would be a setting, a "master role" for client 160 | if ($user->hasPermission("page-roles")) $editable = true; 161 | 162 | $event->return = $editable; 163 | } 164 | 165 | 166 | /** 167 | * Add children permission is granted together with edit, at least for now 168 | * 169 | */ 170 | public function addable(HookEvent $event) { 171 | $page = $event->object; 172 | 173 | // If page is editable and generally can have children, then allow addable 174 | if ($page->editable() && !$page->template->noChildren) { 175 | $event->return = true; 176 | } else { 177 | $event->return = false; 178 | } 179 | } 180 | 181 | /** 182 | * Install the module 183 | * 184 | */ 185 | public function ___install() { 186 | 187 | if($this->fields->get('view_roles')) { 188 | $this->error("You already have a 'view_roles' field."); 189 | return; 190 | } 191 | 192 | if($this->fields->get('edit_roles')) { 193 | $this->error("You already have a 'edit_roles' field."); 194 | return; 195 | } 196 | 197 | 198 | $field = new Field(); 199 | $field->type = $this->modules->get("FieldtypePage"); 200 | $field->name = 'view_roles'; 201 | $field->label = 'Roles that can view this page'; 202 | $field->description = 203 | "Check the roles that may view this page. At least one role must be checked (like one of your roles or superuser), " . 204 | "otherwise this page does not define access. When access is not defined here, it is inherited from the template " . 205 | "or parent(s). If this page's template allows 'view' access to the user, then it will check the parent(s). " . 206 | "Access will inherit from parents that also have a custom page roles field. "; 207 | $field->derefAsPage = 0; 208 | $field->parent_id = $this->config->rolesPageID; 209 | $field->labelFieldName = 'name'; 210 | $field->inputfield = 'InputfieldCheckboxes'; 211 | $field->save(); 212 | 213 | $this->message("Added fields 'view_roles'. Add this field to any templates where you want to control view access."); 214 | 215 | $field = new Field(); 216 | $field->type = $this->modules->get("FieldtypePage"); 217 | $field->name = 'edit_roles'; 218 | $field->label = 'Roles that can edit this page'; 219 | $field->description = 220 | "Check the roles that may edit this page. At least one role must be checked (like one of your roles or superuser), " . 221 | "otherwise this page does not define access. When access is not defined here, it is inherited from the template " . 222 | "or parent(s). If this page's template allows 'view' access to the user, then it will check the parent(s). " . 223 | "Access will inherit from parents that also have a custom page roles field. "; 224 | $field->derefAsPage = 0; 225 | $field->parent_id = $this->config->rolesPageID; 226 | $field->labelFieldName = 'name'; 227 | $field->inputfield = 'InputfieldCheckboxes'; 228 | $field->findPagesCode = ' 229 | $editRoles = $page->getAccessTemplate()->editRoles; 230 | $editRolePages = new PageArray; 231 | foreach($editRoles as $roleId) { 232 | $editRolePages->add($pages->get($roleId)); 233 | } 234 | return $editRolePages; 235 | '; 236 | $field->save(); 237 | 238 | $this->message("Added fields 'edit_roles'. Add this field to any templates where you want to control edit access."); 239 | } 240 | 241 | /** 242 | * Uninstall the module 243 | * 244 | */ 245 | public function ___uninstall() { 246 | $this->message("To complete uninstall, remove the 'view_roles' and 'edit_roles' field from all templates and then delete the fields."); 247 | } 248 | } 249 | --------------------------------------------------------------------------------