Most of the times group access in SilverStripe is sufficient for controlling user access, but for this project I had a specific situation where every user needs to have a dedicated page. In order to avoid unnecessary editing and creating groups for each individual user, I decided to extend SiteTree and create a page with per user access control.
So, I’ve created a new page type, since I only needed it for a single page type, but you can extend/decorate SiteTree any way you like.
Let’s get to the code. This goes to mysite/code/FilePage.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
<?php class FilePage extends Page { static $db = array( "CanViewTypeExtended" => "Enum('Anyone, LoggedInUsers, OnlyTheseUsers, OnlyTheseMembers, Inherit', 'Inherit')", ); static $has_one = array( "ViewerMember" => "Member", ); public function getSettingsFields(){ global $project; $fields = parent::getSettingsFields(); Requirements::javascript($project.'/javascript/CMSMain.EditFormMember.js'); //Not sure where to put this :) $members = Member::map_in_groups(); $field = new DropdownField("ViewerMemberID", "Viewer Members", $members); $field->setEmptyString('(Select one)'); $fields->addFieldToTab("Root.Settings", $field, 'CanEditType'); $viewersOptionsField = $fields->removeByName('CanViewType'); $viewersOptions = new OptionsetField( "CanViewTypeExtended", _t('SiteTree.ACCESSHEADER', "Who can view this page?") ); $viewersOptionsSource = array(); $viewersOptionsSource["Inherit"] = _t('SiteTree.INHERIT', "Inherit from parent page"); $viewersOptionsSource["Anyone"] = _t('SiteTree.ACCESSANYONE', "Anyone"); $viewersOptionsSource["LoggedInUsers"] = _t('SiteTree.ACCESSLOGGEDIN', "Logged-in users"); $viewersOptionsSource["OnlyTheseUsers"] = _t('SiteTree.ACCESSONLYTHESE', "Only these people (choose from list)"); $viewersOptionsSource["OnlyTheseMembers"] = _t('SiteTree.ACCESSONLYTHESEMEMBERS', "Only these members (choose from list)"); $viewersOptions->setSource($viewersOptionsSource); $fields->addFieldToTab("Root.Settings", $viewersOptions, 'ViewerGroups'); return $fields; } // Override the SiteTree canView function to implement the new variable public function canView($member = null) { if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) { $member = Member::currentUserID(); } // admin override if($member && Permission::checkMember($member, array("ADMIN", "SITETREE_VIEW_ALL"))) return true; // Standard mechanism for accepting permission changes from extensions $extended = $this->extendedCan('canView', $member); if($extended !== null) return $extended; // check for empty spec if(!$this->CanViewTypeExtended || $this->CanViewTypeExtended == 'Anyone') return true; // check for inherit if($this->CanViewTypeExtended == 'Inherit') { if($this->ParentID) return $this->Parent()->canView($member); else return $this->getSiteConfig()->canView($member); } // check for any logged-in users if($this->CanViewTypeExtended == 'LoggedInUsers' && $member) { return true; } // check for specific groups if($member && is_numeric($member)) $member = DataObject::get_by_id('Member', $member); if( $this->CanViewTypeExtended == 'OnlyTheseUsers' && $member && $member->inGroups($this->ViewerGroups()) ) return true; //Check for specific Member if( $this->CanViewTypeExtended == 'OnlyTheseMembers' && $member && $member->ID == $this->ViewerMemberID ) return true; return false; } } |
So, let’s break it down a bit:
I’ve created a new variable – CanViewTypeExtended to replace SiteTree’s CanViewType, since I couldn’t find the way to add an option (If you know a way, feel free to drop a line). It replicates SiteTree’s CanViewType with OnlyTheseMembers option added, which is our per user access type.
Then, we have has one ViewerMember, which holds actual user ID for single user (I’ve limited it to one user, since using any more would be a group).
The rest is pretty basic, mostly copied from SiteTree with a bit of additions, getSettingsFields is a standard function for updating the Settings tab in CMS. There we have first included the JavaScript file, which will be shown later, and is only for decoration – showing and hiding fields based on selection.
Then we have created Member selection field, to pick a member to which the access will be granted, and replicated creating CanViewType field from SiteTree.php with addition of our new OnlyTheseMembers option.
After this is saved, all that is left is to check user permissions in canView() method. Since we don’t use CanViewType any more, but have replaced it with CanViewTypeExtended, the entire function is copied from SiteTree.php, except for the last part which grants the access if current member is our selected member:
if($this->CanViewTypeExtended == ‘OnlyTheseMembers’ && $member && $member->ID == $this->ViewerMemberID)
return true;
So, here’s the remaining js file, which goes to mysite/javascript/CMSMain.EditFormMember.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
/** * File: CMSMain.EditFormMember.js */ (function($) { $.entwine('ss', function($){ /** * Class: .cms-edit-form #CanViewTypeExtended * * Toggle display of Member dropdown in "access" tab, * based on selection of radiobuttons. */ $('.cms-edit-form #CanViewTypeExtended').entwine({ // Constructor: onmatch onmatch: function() { // TODO Decouple var dropdownMembers = $('#ViewerMemberID'); var dropdownGroups = $('#ViewerGroups'); this.find('.optionset :input').bind('change', function(e) { var wrapper = $(this).closest('.middleColumn').parent('div'); var wrapper2 = dropdownMembers.closest('.middleColumn').parent('div'); if(e.target.value == 'OnlyTheseMembers') { wrapper.addClass('remove-splitter'); dropdownMembers['show'](); dropdownGroups['hide'](); }else if(e.target.value == 'OnlyTheseUsers') { wrapper.addClass('remove-splitter'); dropdownMembers['hide'](); dropdownGroups['show'](); }else{ wrapper.removeClass('remove-splitter'); dropdownMembers['hide'](); dropdownGroups['hide'](); } }); // initial state var currentVal = this.find('input[name=' + this.attr('id') + ']:checked').val(); dropdownMembers[currentVal == 'OnlyTheseMembers' ? 'show' : 'hide'](); dropdownGroups[currentVal == 'OnlyTheseUsers' ? 'show' : 'hide'](); this._super(); }, onunmatch: function() { this._super(); } }); }); }(jQuery)); |