├── resources ├── icon.png ├── icon.sketch ├── 2846d35b2cdddd2849c953c1bd8a7260.jpg └── icon.svg ├── translations └── en.php ├── CHANGELOG.md ├── LICENSE.txt ├── releases.json ├── README.md ├── templates └── settings.twig ├── services └── BorisService.php └── BorisPlugin.php /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdna/boris/HEAD/resources/icon.png -------------------------------------------------------------------------------- /resources/icon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdna/boris/HEAD/resources/icon.sketch -------------------------------------------------------------------------------- /resources/2846d35b2cdddd2849c953c1bd8a7260.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdna/boris/HEAD/resources/2846d35b2cdddd2849c953c1bd8a7260.jpg -------------------------------------------------------------------------------- /translations/en.php: -------------------------------------------------------------------------------- 1 | Plugins 16 | 1. The plugin folder should be named `boris` for Craft to see it. GitHub recently started appending `-master` (the branch name) to the name of the folder for zip file downloads. 17 | 18 | Boris works on Craft 2.4.x and Craft 2.5.x. 19 | 20 | ## Boris Overview 21 | 22 | Ever wanted to stop users from deleting an entry? Now you can! Boris will allow you select entries and make them INVINCIBLE. 23 | 24 | Simply head over to the plugin settings page and select the entries you wish to protect 25 | 26 | #### Boris currently supports protecting the following element types 27 | 28 | - Entries 29 | - Categories 30 | 31 | ## Boris Roadmap 32 | 33 | - Make other element types invincible 34 | - Better notifications 35 | 36 | Brought to you by [Nathaniel Hammond - @nfourtythree - webdna](https://webdna.co.uk) 37 | -------------------------------------------------------------------------------- /templates/settings.twig: -------------------------------------------------------------------------------- 1 | {# 2 | /** 3 | * Boris plugin for Craft CMS 4 | * 5 | * Boris Settings.twig 6 | * 7 | * @author Nathaniel Hammond - @nfourtythree - webdna 8 | * @copyright Copyright (c) 2017 Nathaniel Hammond - @nfourtythree - webdna 9 | * @link https://webdna.co.uk 10 | * @package Boris 11 | * @since 1.0.0 12 | */ 13 | #} 14 | 15 | {% import "_includes/forms" as forms %} 16 | 17 | {{ forms.textField( { 18 | label: 'Plugin Name', 19 | instructions: 'If you would like to rename the plugin, this is the place to do it', 20 | id: 'name', 21 | name: 'name', 22 | value: settings[ 'name' ] 23 | } ) 24 | }} 25 | 26 | {{ forms.elementSelectField({ 27 | label: 'Invincible Entries', 28 | id: 'entryIds', 29 | name: 'entryIds', 30 | elementType: craft.elements.getElementType( 'Entry' ), 31 | elements: entries, 32 | criteria: { 'kind': [], 'localeEnabled': null }, 33 | sourceElementId: 1, 34 | jsClass: 'Craft.BaseElementSelectInput', 35 | addButtonLabel: 'Select Entries', 36 | limit: null, 37 | sources: [] 38 | }) }} 39 | 40 | {{ forms.elementSelectField({ 41 | label: 'Invincible Categories', 42 | id: 'categoryIds', 43 | name: 'categoryIds', 44 | elementType: craft.elements.getElementType( 'Category' ), 45 | elements: categories, 46 | criteria: { 'kind': [], 'localeEnabled': null }, 47 | sourceElementId: 1, 48 | jsClass: 'Craft.CategorySelectInput', 49 | addButtonLabel: 'Select Categories', 50 | limit: null, 51 | sources: [] 52 | }) }} 53 | -------------------------------------------------------------------------------- /services/BorisService.php: -------------------------------------------------------------------------------- 1 | 'entries', 21 | 'categoryIds' => 'categories', 22 | ); 23 | 24 | /** 25 | * invincible - check to see if any of the IDs passed are invincible 26 | * @param {array} $ids array of IDs to check 27 | * @param {array} $invincibleIds array of IDs that are invincible 28 | * @return {array} array of invincible titles (indicating if there are any) 29 | */ 30 | public function invincible( $ids, $invincibleIds ) 31 | { 32 | 33 | $invincibleTitles = array(); 34 | 35 | if ( $invincibleIds and !empty( $invincibleIds ) ) { 36 | foreach ( $ids as $id ) { 37 | 38 | if ( in_array( $id, $invincibleIds ) ) { 39 | $element = craft()->elements->getElementById( $id ); 40 | if ( $element ) { 41 | $invincibleTitles[] = $element->title; 42 | } 43 | } 44 | 45 | } 46 | } 47 | 48 | return $invincibleTitles; 49 | } 50 | 51 | /** 52 | * showInvincibleNotice - show notice stating things haven't been deleted 53 | * @param array $titles 54 | */ 55 | public function showInvincibleNotice( $titles = array() ) 56 | { 57 | craft()->userSession->setNotice( Craft::t( 'The following elements are protected: {titles}', array( 'titles' => implode( ', ', $titles ) ) ) ); 58 | } 59 | 60 | public function getTemplateVars( $settings ) 61 | { 62 | $settings = $this->unprepSettings( $settings ); 63 | 64 | $templateVars = array( 65 | 'settings' => $settings, 66 | 'entries' => array(), 67 | 'categories' => array(), 68 | ); 69 | 70 | foreach ( array_keys( $this->elementIdHandles ) as $elementIdHandle ) { 71 | 72 | if ( isset( $settings[ $elementIdHandle ] ) and $settings[ $elementIdHandle ] and !empty( $settings[ $elementIdHandle ] ) ) { 73 | $tmp = array(); 74 | foreach ( $settings[ $elementIdHandle ] as $id ) { 75 | $tmpElement = craft()->elements->getElementById( $id ); 76 | 77 | if ( $tmpElement ) { 78 | $tmp[] = $tmpElement; 79 | } 80 | } 81 | $templateVars[ $this->elementIdHandles[ $elementIdHandle ] ] = $tmp; 82 | } 83 | 84 | } 85 | 86 | return $templateVars; 87 | } 88 | 89 | public function prepSettings( $settings ) 90 | { 91 | foreach ( array_keys( $this->elementIdHandles ) as $elementIdHandle ) { 92 | if ( isset( $settings[ $elementIdHandle ] ) ){ 93 | $settings[ $elementIdHandle ] = serialize( $settings[ $elementIdHandle ] ); 94 | } 95 | } 96 | 97 | return $settings; 98 | } 99 | 100 | public function unprepSettings( $settings ) 101 | { 102 | foreach ( array_keys( $this->elementIdHandles ) as $elementIdHandle ) { 103 | if ( isset( $settings[ $elementIdHandle ] ) ) { 104 | $settings[ $elementIdHandle ] = unserialize( $settings[ $elementIdHandle ] ); 105 | } 106 | } 107 | 108 | return $settings; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /resources/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon 5 | Created with Sketch. 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 | -------------------------------------------------------------------------------- /BorisPlugin.php: -------------------------------------------------------------------------------- 1 | on('entries.saveEntry', function(Event $event) { 22 | * // ... 23 | * }); 24 | * 25 | * or loading any third party Composer packages via: 26 | * 27 | * require_once __DIR__ . '/vendor/autoload.php'; 28 | * 29 | * @return mixed 30 | */ 31 | public function init() 32 | { 33 | parent::init(); 34 | 35 | craft()->on( 'entries.onBeforeDeleteEntry', function( Event $event ) { 36 | 37 | $settings = craft()->boris->unprepSettings( $this->getSettings() ); 38 | 39 | if ( $event->params[ 'entry' ] and ( $settings[ 'entryIds' ] ) and !empty( $settings[ 'entryIds' ] ) ) { 40 | 41 | $titles = craft()->boris->invincible( array( $event->params[ 'entry' ]->id ), $settings[ 'entryIds' ] ); 42 | 43 | if ( count( $titles ) ) { 44 | craft()->boris->showInvincibleNotice( $titles ); 45 | $event->performAction = false; 46 | } 47 | 48 | } 49 | 50 | } ); 51 | 52 | craft()->on( 'categories.onBeforeDeleteCategory', function( Event $event ) { 53 | 54 | $settings = craft()->boris->unprepSettings( $this->getSettings() ); 55 | 56 | if ( $event->params[ 'category' ] and ( $settings[ 'categoryIds' ] ) and count( $settings[ 'categoryIds' ] ) ) { 57 | 58 | $titles = craft()->boris->invincible( array( $event->params[ 'category' ]->id ), $settings[ 'categoryIds' ] ); 59 | 60 | if ( count( $titles ) ) { 61 | craft()->boris->showInvincibleNotice( $titles ); 62 | Craft::app()->getRequest()->redirect( UrlHelper::getCpUrl( 'categories' ) ); 63 | } 64 | 65 | } 66 | 67 | } ); 68 | 69 | craft()->on( 'elements.onBeforePerformAction', function( Event $event ) { 70 | 71 | $settings = craft()->boris->unprepSettings( $this->getSettings() ); 72 | 73 | if ( $event->params[ 'action' ]->classHandle == 'Delete' ) { 74 | 75 | $titles = craft()->boris->invincible( $event->params[ 'criteria' ]->ids(), $settings[ 'entryIds' ] ); 76 | 77 | if ( count( $titles ) ) { 78 | // TODO: Figure out how to pass back notice on performAction 79 | // craft()->boris->showInvincibleNotice( $titles ); 80 | $event->performAction = false; 81 | } 82 | 83 | $titles = craft()->boris->invincible( $event->params[ 'criteria' ]->ids(), $settings[ 'categoryIds' ] ); 84 | 85 | if ( count( $titles ) ) { 86 | // TODO: Figure out how to pass back notice on performAction 87 | craft()->boris->showInvincibleNotice( $titles ); 88 | $event->performAction = false; 89 | } 90 | } 91 | 92 | } ); 93 | } 94 | 95 | /** 96 | * Returns the user-facing name. 97 | * 98 | * @return mixed 99 | */ 100 | public function getName() 101 | { 102 | $settings = $this->getSettings(); 103 | 104 | if ( $settings->name ) { 105 | return $settings->name; 106 | } 107 | 108 | return Craft::t( 'Boris' ); 109 | } 110 | 111 | /** 112 | * Plugins can have descriptions of themselves displayed on the Plugins page by adding a getDescription() method 113 | * on the primary plugin class: 114 | * 115 | * @return mixed 116 | */ 117 | public function getDescription() 118 | { 119 | return Craft::t( 'Make your entries invincible!' ); 120 | } 121 | 122 | /** 123 | * Plugins can have links to their documentation on the Plugins page by adding a getDocumentationUrl() method on 124 | * the primary plugin class: 125 | * 126 | * @return string 127 | */ 128 | public function getDocumentationUrl() 129 | { 130 | return 'https://github.com/webdna/boris/blob/master/README.md'; 131 | } 132 | 133 | /** 134 | * Plugins can now take part in Craft’s update notifications, and display release notes on the Updates page, by 135 | * providing a JSON feed that describes new releases, and adding a getReleaseFeedUrl() method on the primary 136 | * plugin class. 137 | * 138 | * @return string 139 | */ 140 | public function getReleaseFeedUrl() 141 | { 142 | return 'https://raw.githubusercontent.com/webdna/boris/master/releases.json'; 143 | } 144 | 145 | /** 146 | * Returns the version number. 147 | * 148 | * @return string 149 | */ 150 | public function getVersion() 151 | { 152 | return '1.1.2'; 153 | } 154 | 155 | /** 156 | * As of Craft 2.5, Craft no longer takes the whole site down every time a plugin’s version number changes, in 157 | * case there are any new migrations that need to be run. Instead plugins must explicitly tell Craft that they 158 | * have new migrations by returning a new (higher) schema version number with a getSchemaVersion() method on 159 | * their primary plugin class: 160 | * 161 | * @return string 162 | */ 163 | public function getSchemaVersion() 164 | { 165 | return '1.0.0'; 166 | } 167 | 168 | /** 169 | * Returns the developer’s name. 170 | * 171 | * @return string 172 | */ 173 | public function getDeveloper() 174 | { 175 | return 'Nathaniel Hammond - @nfourtythree - webdna'; 176 | } 177 | 178 | /** 179 | * Returns the developer’s website URL. 180 | * 181 | * @return string 182 | */ 183 | public function getDeveloperUrl() 184 | { 185 | return 'https://webdna.co.uk'; 186 | } 187 | 188 | /** 189 | * Returns whether the plugin should get its own tab in the CP header. 190 | * 191 | * @return bool 192 | */ 193 | public function hasCpSection() 194 | { 195 | return false; 196 | } 197 | 198 | /** 199 | * Called right before your plugin’s row gets stored in the plugins database table, and tables have been created 200 | * for it based on its records. 201 | */ 202 | public function onBeforeInstall() 203 | { 204 | } 205 | 206 | /** 207 | * Called right after your plugin’s row has been stored in the plugins database table, and tables have been 208 | * created for it based on its records. 209 | */ 210 | public function onAfterInstall() 211 | { 212 | } 213 | 214 | /** 215 | * Called right before your plugin’s record-based tables have been deleted, and its row in the plugins table 216 | * has been deleted. 217 | */ 218 | public function onBeforeUninstall() 219 | { 220 | } 221 | 222 | /** 223 | * Called right after your plugin’s record-based tables have been deleted, and its row in the plugins table 224 | * has been deleted. 225 | */ 226 | public function onAfterUninstall() 227 | { 228 | } 229 | 230 | /** 231 | * Defines the attributes that model your plugin’s available settings. 232 | * 233 | * @return array 234 | */ 235 | protected function defineSettings() 236 | { 237 | return array( 238 | 'name' => array( AttributeType::String, 'label' => 'Plugin Name', 'default' => 'Boris' ), 239 | 'entryIds' => array( AttributeType::Mixed, 'label' => 'Invincible Entries', 'default' => serialize( array() ) ), 240 | 'categoryIds' => array( AttributeType::Mixed, 'label' => 'Invincible Categories', 'default' => serialize( array() ) ), 241 | ); 242 | } 243 | 244 | /** 245 | * Returns the HTML that displays your plugin’s settings. 246 | * 247 | * @return mixed 248 | */ 249 | public function getSettingsHtml() 250 | { 251 | return craft()->templates->render( 'boris/settings', craft()->boris->getTemplateVars( $this->getSettings() ) ); 252 | } 253 | 254 | /** 255 | * If you need to do any processing on your settings’ post data before they’re saved to the database, you can 256 | * do it with the prepSettings() method: 257 | * 258 | * @param mixed $settings The Widget's settings 259 | * 260 | * @return mixed 261 | */ 262 | public function prepSettings( $settings ) 263 | { 264 | return craft()->boris->prepSettings( $settings ); 265 | } 266 | 267 | } 268 | --------------------------------------------------------------------------------