├── .gitattributes ├── images └── blocks.png ├── templates └── Includes │ ├── Block.ss │ ├── BlockArea.ss │ └── ContentBlock.ss ├── javascript ├── lang │ ├── fi.js │ ├── en.js │ └── hr.js ├── blocks-cms.js └── block-preview.js ├── .editorconfig ├── _config.php ├── phpunit.xml.dist ├── tests ├── ContentBlockTest.php ├── BlockSiteConfigExtensionTest.php ├── fixtures.yml ├── BlockSetTest.php └── BlockTest.php ├── css └── block-preview.css ├── src ├── extensions │ ├── BlockSiteConfigExtension.php │ ├── BlockContentControllerExtension.php │ └── BlocksSiteTreeExtension.php ├── model │ ├── ContentBlock.php │ ├── BlockSet.php │ └── Block.php ├── controllers │ ├── BlockController.php │ └── BlockAdmin.php ├── BlockManager.php └── forms │ └── GridfieldConfigBlockManager.php ├── _config └── blocks.yml ├── .scrutinizer.yml ├── composer.json ├── LICENSE.md ├── .travis.yml ├── tasks └── BlockUpgradeTask.php ├── lang ├── en.yml ├── hr.yml ├── lt.yml ├── fi.yml └── ny.yml └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | /docs export-ignore 2 | /.gitignore export-ignore 3 | -------------------------------------------------------------------------------- /images/blocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dynamic/silverstripe-blocks/master/images/blocks.png -------------------------------------------------------------------------------- /templates/Includes/Block.ss: -------------------------------------------------------------------------------- 1 |
2 |

$Title

3 |
-------------------------------------------------------------------------------- /javascript/lang/fi.js: -------------------------------------------------------------------------------- 1 | ss.i18n.addDictionary('fi', { 2 | 'BLOCKS.ALERTCLASSNAME' : "Laatikon tyyppi päivitetään kun sivu tallennetaan" 3 | }); -------------------------------------------------------------------------------- /javascript/lang/en.js: -------------------------------------------------------------------------------- 1 | ss.i18n.addDictionary('en', { 2 | 'BLOCKS.ALERTCLASSNAME' : "The block type will be updated after the page is saved" 3 | }); -------------------------------------------------------------------------------- /javascript/lang/hr.js: -------------------------------------------------------------------------------- 1 | ss.i18n.addDictionary('hr', { 2 | 'BLOCKS.ALERTCLASSNAME' : "Vrsta bloka će se ažurirati nakon spremanja stranice" 3 | }); 4 | -------------------------------------------------------------------------------- /templates/Includes/BlockArea.ss: -------------------------------------------------------------------------------- 1 |
2 | <% loop BlockArea %> 3 | $BlockHTML 4 | <% end_loop %> 5 |
-------------------------------------------------------------------------------- /templates/Includes/ContentBlock.ss: -------------------------------------------------------------------------------- 1 |
2 | <% if $Title %>

$Title

<% end_if %> 3 | <% if $Content %>
$Content
<% end_if %> 4 |
5 | -------------------------------------------------------------------------------- /javascript/blocks-cms.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $.entwine("ss", function($) { 3 | $(".cms-edit-form :input[name=ClassName].block-type").entwine({ 4 | onchange: function() { 5 | alert(ss.i18n._t('BLOCKS.ALERTCLASSNAME')); 6 | } 7 | }); 8 | 9 | }); 10 | })(jQuery); 11 | 12 | 13 | -------------------------------------------------------------------------------- /javascript/block-preview.js: -------------------------------------------------------------------------------- 1 | (function($){ 2 | 3 | $(function(){ 4 | $('.block_area').each(function(){ 5 | var self = $(this); 6 | var label = '' + self.data('areaid') + ' Block Area'; 7 | self.append('
' + label + '
'); 8 | }); 9 | }); 10 | 11 | }(jQuery)); -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # For more information about the properties used in 2 | # this file, please see the EditorConfig documentation: 3 | # http://editorconfig.org/ 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 4 9 | indent_style = tab 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | 16 | [*.yml] 17 | indent_size = 2 18 | indent_style = space 19 | -------------------------------------------------------------------------------- /_config.php: -------------------------------------------------------------------------------- 1 | update('LeftAndMain', 'extra_requirements_javascript', array(BLOCKS_DIR.'/javascript/blocks-cms.js')); 11 | Config::modify()->update('BlockAdmin', 'menu_icon', BLOCKS_DIR.'/images/blocks.png'); 12 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | blocks/tests 4 | 5 | 6 | 7 | 8 | blocks/src/ 9 | 10 | blocks/tests/ 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/ContentBlockTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('Content Blocks', $object->plural_name()); 20 | } 21 | } -------------------------------------------------------------------------------- /css/block-preview.css: -------------------------------------------------------------------------------- 1 | .block_area{ 2 | position: relative; 3 | min-height: 100px; 4 | } 5 | 6 | .block_preview-overlay, .block_preview_label{ 7 | position: absolute; 8 | top: 0; 9 | right: 0; 10 | bottom: 0; 11 | left: 0; 12 | } 13 | 14 | .block_preview_overlay{ 15 | background: #fff; 16 | opacity: 0.8; 17 | z-index: 20; 18 | } 19 | 20 | .block_preview_label{ 21 | border: 2px dashed #ff4444; 22 | z-index: 21; 23 | text-align: center; 24 | } 25 | 26 | .block_preview_label span{ 27 | display: inline-block; 28 | background: #ff4444; 29 | color: #fff; 30 | font-size: 14px; 31 | padding: 10px; 32 | position: relative; 33 | top: -2px; 34 | } -------------------------------------------------------------------------------- /src/extensions/BlockSiteConfigExtension.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class BlockSiteConfigExtension extends DataExtension 15 | { 16 | private static $many_many = [ 17 | "Blocks" => Block::class, 18 | ]; 19 | 20 | /** 21 | * 22 | **/ 23 | public function updateCMSFields(FieldList $fields) 24 | { 25 | $fields->removeByName('Blocks'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /_config/blocks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Name: 'blocks' 3 | --- 4 | SilverStripe\CMS\Model\SiteTree: 5 | extensions: 6 | - SheaDawson\Blocks\Extensions\BlocksSiteTreeExtension 7 | SilverStripe\CMS\Controllers\ContentController: 8 | extensions: 9 | - SheaDawson\Blocks\Extensions\BlocksContentControllerExtension 10 | url_handlers: 11 | 'block/$ID!': 'handleBlock' 12 | SilverStripe\CMS\Controllers\SiteConfig: 13 | extensions: 14 | - SheaDawson\Blocks\Extensions\BlockSiteConfigExtension 15 | SheaDawson\Blocks\Model\Block: 16 | extensions: 17 | - SilverStripe\Versioned\Versioned('Stage','Live') 18 | SilverStripe\Core\Injector\Injector: 19 | blockManager: SheaDawson\Blocks\BlockManager 20 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | inherit: true 2 | 3 | #Copied from https://www.adayinthelifeof.nl/2013/11/20/external-code-coverage-with-travis-scrutinizer/ 4 | tools: 5 | external_code_coverage: 6 | timeout: 600 7 | php_code_sniffer: 8 | config: 9 | standard: PSR2 10 | php_cs_fixer: 11 | extensions: 12 | # Default: 13 | - php 14 | fixers: [] 15 | enabled: false 16 | filter: 17 | paths: [tests/*,src/*] 18 | excluded_paths: [] 19 | coding_style: 20 | php: 21 | indentation: 22 | general: 23 | use_tabs: false 24 | 25 | checks: 26 | php: 27 | code_rating: true 28 | duplication: true 29 | 30 | filter: 31 | paths: [tests/*,src/*] -------------------------------------------------------------------------------- /tests/BlockSiteConfigExtensionTest.php: -------------------------------------------------------------------------------- 1 | getCMSFields(); 19 | $extension = new BlockSiteConfigExtension(); 20 | $extension->updateCMSFields($fields); 21 | 22 | $this->assertInstanceOf(FieldList::class, $fields); 23 | $this->assertNull($fields->dataFieldByName('Blocks')); 24 | } 25 | } -------------------------------------------------------------------------------- /tests/fixtures.yml: -------------------------------------------------------------------------------- 1 | SilverStripe\Security\Group: 2 | administrators: 3 | Title: Administrators 4 | content_authors: 5 | Title: "Content Authors" 6 | SilverStripe\Security\Permission: 7 | administrators: 8 | Code: ADMIN 9 | Type: 1 10 | Group: =>SilverStripe\Security\Group.administrators 11 | SilverStripe\Security\Member: 12 | admin: 13 | FirstName: Default 14 | Surname: Admin 15 | Email: administrator 16 | Groups: =>SilverStripe\Security\Group.administrators 17 | author: 18 | FirstName: "Content" 19 | Surname: "Author" 20 | Email: "content" 21 | Groups: =>SilverStripe\Security\Group.content_authors 22 | default: 23 | FirstName: Default 24 | Surname: Member 25 | Email: member 26 | SheaDawson\Blocks\Model\Block: 27 | default: 28 | Title: "Default Block" 29 | SheaDawson\Blocks\Model\BlockSet: 30 | default: 31 | Title: "Default BlockSet" -------------------------------------------------------------------------------- /src/model/ContentBlock.php: -------------------------------------------------------------------------------- 1 | 'HTMLText', 31 | ]; 32 | 33 | public function fieldLabels($includeRelations = true) 34 | { 35 | return array_merge( 36 | parent::fieldLabels($includeRelations), 37 | [ 38 | 'Content' => _t('Block.Content', 'Content'), 39 | ] 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sheadawson/silverstripe-blocks", 3 | "description": "An alternative to the SilverStripe Widgets module.", 4 | "type": "silverstripe-module", 5 | "keywords": [ 6 | "silverstripe", 7 | "blocks", 8 | "widgets" 9 | ], 10 | "homepage": "http://github.com/sheadawson/silverstripe-blocks", 11 | "authors": [ 12 | { 13 | "name": "Shea Dawson", 14 | "email": "shea@silverstripe.com.au" 15 | } 16 | ], 17 | "require": { 18 | "composer/installers": "*", 19 | "silverstripe/framework": "^4.0", 20 | "silverstripe/cms": "^4.0", 21 | "symbiote/silverstripe-gridfieldextensions": "^3.0", 22 | "symbiote/silverstripe-multivaluefield": "^5.0", 23 | "unclecheese/betterbuttons": "2.x-dev" 24 | }, 25 | "suggest": { 26 | "unisolutions/silverstripe-copybutton": "Duplicate Blocks in Block Admin", 27 | "micschk/silverstripe-gridfieldsitetreebuttons": "Edit pages directly from a Block's 'used on page' list" 28 | }, 29 | "minimum-stability": "dev", 30 | "prefer-stable": true, 31 | "extra": { 32 | "installer-name": "blocks", 33 | "branch-alias": { 34 | "dev-master": "2.0.x-dev" 35 | } 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "SheaDawson\\Blocks\\": "src/" 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/extensions/BlockContentControllerExtension.php: -------------------------------------------------------------------------------- 1 | owner->data()->canEdit() && $this->owner->getRequest()->getVar('block_preview') == 1) { 20 | Requirements::javascript(THIRDPARTY_DIR.'/jquery/jquery.js'); 21 | Requirements::javascript(BLOCKS_DIR.'/javascript/block-preview.js'); 22 | Requirements::css(BLOCKS_DIR.'/css/block-preview.css'); 23 | } 24 | } 25 | 26 | /** 27 | * Handles blocks attached to a page 28 | * Assumes URLs in the following format: /block/. 29 | * 30 | * @return RequestHandler 31 | */ 32 | public function handleBlock() 33 | { 34 | if ($id = $this->owner->getRequest()->param('ID')) { 35 | $blocks = $this->owner->data()->getBlockList(null, true, true, true); 36 | if ($block = $blocks->find('ID', $id)) { 37 | return $block->getController(); 38 | } 39 | } 40 | 41 | return $block->getController(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, LiveSource All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name LiveSource nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: php 3 | 4 | sudo: false 5 | 6 | addons: 7 | apt: 8 | packages: 9 | - tidy 10 | 11 | before_install: 12 | - pip install --user codecov 13 | 14 | env: 15 | global: 16 | - DB=MYSQL CORE_RELEASE=4.0 17 | - MODULE_PATH=blocks 18 | - COVERAGE=0 19 | 20 | matrix: 21 | include: 22 | - php: 7.0 23 | env: DB=SQLITE 24 | - php: 7.0 25 | env: DB=PGSQL 26 | - php: 7.0 27 | env: COVERAGE=1 28 | - php: 5.6 29 | allow_failures: 30 | - php: 7.0 31 | env: DB=SQLITE 32 | 33 | 34 | before_script: 35 | - phpenv rehash 36 | - composer self-update || true 37 | - git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support 38 | - php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss 39 | - cd ~/builds/ss 40 | - mv "$MODULE_PATH/phpunit.xml.dist" . 41 | 42 | script: 43 | # Execute tests with no coverage. This is the fastest option 44 | - "if [ \"$COVERAGE\" = \"0\" ]; then vendor/bin/phpunit $MODULE_PATH/tests/; fi" 45 | 46 | # Execute tests with coverage. Do this for a small 47 | - "if [ \"$COVERAGE\" = \"1\" ]; then vendor/bin/phpunit --coverage-clover=coverage.clover $MODULE_PATH/tests/; fi" 48 | 49 | after_script: 50 | - "if [ \"$COVERAGE\" = \"1\" ]; then mv coverage.clover ~/build/$TRAVIS_REPO_SLUG/; fi" 51 | - cd ~/build/$TRAVIS_REPO_SLUG 52 | - wget https://scrutinizer-ci.com/ocular.phar 53 | - "if [ \"$COVERAGE\" = \"1\" ]; then travis_retry codecov && travis_retry php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi" 54 | 55 | -------------------------------------------------------------------------------- /src/controllers/BlockController.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class BlockController extends Controller 14 | { 15 | /** 16 | * @var Block 17 | */ 18 | protected $block; 19 | 20 | /** 21 | * @param Block $block 22 | */ 23 | public function __construct($block = null) 24 | { 25 | if ($block) { 26 | $this->block = $block; 27 | $this->failover = $block; 28 | } 29 | 30 | parent::__construct(); 31 | } 32 | 33 | public function index() 34 | { 35 | return; 36 | } 37 | 38 | /** 39 | * @param string $action 40 | * 41 | * @return string 42 | */ 43 | public function Link($action = null) 44 | { 45 | $id = ($this->block) ? $this->block->ID : null; 46 | $segment = Controller::join_links('block', $id, $action); 47 | 48 | if ($page = Director::get_current_page()) { 49 | return $page->Link($segment); 50 | } 51 | 52 | return Controller::curr()->Link($segment); 53 | } 54 | 55 | /** 56 | * @return string - link to page this block is on 57 | */ 58 | public function pageLink() 59 | { 60 | $parts = explode('/block/', $this->Link()); 61 | 62 | return isset($parts[0]) ? $parts[0] : null; 63 | } 64 | 65 | /** 66 | * @return Block 67 | */ 68 | public function getBlock() 69 | { 70 | return $this->block; 71 | } 72 | 73 | /** 74 | * CSS Classes to apply to block element in template. 75 | * 76 | * @return string $classes 77 | */ 78 | public function CSSClasses($stopAtClass = 'DataObject') 79 | { 80 | return $this->getBlock()->CSSClasses($stopAtClass); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tasks/BlockUpgradeTask.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class BlockUpgradeTask extends BuildTask 18 | { 19 | public function run($request) 20 | { 21 | 22 | // update block/set titles 23 | // Name field has been reverted back to Title 24 | // DB::query("update Block set Name = Title"); 25 | // DB::query("update BlockSet set Name = Title"); 26 | 27 | // update block areas 28 | 29 | DB::query(' 30 | update SiteTree_Blocks 31 | left join Block on SiteTree_Blocks.BlockID = Block.ID 32 | set BlockArea = Block.Area 33 | where BlockID = Block.ID 34 | '); 35 | 36 | // update block sort 37 | 38 | DB::query(' 39 | update SiteTree_Blocks 40 | left join Block on SiteTree_Blocks.BlockID = Block.ID 41 | set Sort = Block.Weight 42 | where BlockID = Block.ID 43 | '); 44 | 45 | echo 'BlockAreas, Sort updated
'; 46 | 47 | // migrate global blocks 48 | 49 | $sc = SiteConfig::current_site_config(); 50 | if ($sc->Blocks()->Count()) { 51 | $set = BlockSet::get()->filter('Title', 'Global')->first(); 52 | if (!$set) { 53 | $set = BlockSet::create([ 54 | 'Title' => 'Global', 55 | ]); 56 | $set->write(); 57 | } 58 | foreach ($sc->Blocks() as $block) { 59 | if (!$set->Blocks()->find('ID', $block->ID)) { 60 | $set->Blocks()->add($block, [ 61 | 'Sort' => $block->Weight, 62 | 'BlockArea' => $block->Area, 63 | ]); 64 | echo "Block #$block->ID added to Global block set
"; 65 | } 66 | } 67 | } 68 | 69 | // publish blocks 70 | $blocks = Block::get()->filter('Published', 1); 71 | foreach ($blocks as $block) { 72 | $block->publish('Stage', 'Live'); 73 | echo "Published Block #$block->ID
"; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lang/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | Block: 3 | Any: (any) 4 | BlockArea: 'Block Area' 5 | BlockSetsAsString: 'Block Sets: {sets}' 6 | BlockType: 'Block Type' 7 | Content: Content 8 | CreateBlock: 'Create a Block' 9 | DeleteBlock: 'Delete a Block' 10 | EditBlock: 'Edit a Block' 11 | ExtraCSSClasses: 'Extra CSS Classes' 12 | IsPublishedField: Published 13 | PLURALNAME: Blocks 14 | PagesAsString: 'Pages: {pages}' 15 | PermissionCategory: Blocks 16 | PublishBlock: 'Publish a Block' 17 | SINGULARNAME: Block 18 | Title: Title 19 | TitleRequired: 'Block Title is required' 20 | UsageListAsString: 'Used on' 21 | ViewerGroups: 'Viewer Groups' 22 | BlockAdmin: 23 | MENUTITLE: Blocks 24 | BlockSet: 25 | ApplyBlockSetToSelectedPageParentsAsWellAsChildren: 'Apply block set to selected page parents as well as children' 26 | CreateBlockSet: 'Create a Block Set' 27 | DeleteBlockSet: 'Delete a Block Set' 28 | EditBlockSet: 'Edit a Block Set' 29 | OnlyApplyToChildrenOfThesePages: 'Only apply to children of these Pages:' 30 | OnlyApplyToThesePageTypes: 'Only apply to these Page Types:' 31 | OnlyApplyToThesePageTypesDescription: 'Selected Page Types will inherit this Block Set automatically. Leave all unchecked to apply to all page types.' 32 | PLURALNAME: 'Block Sets' 33 | SINGULARNAME: 'Block Set' 34 | Settings: Settings 35 | YouCanAddBlocksToThisSetOnceYouHaveSavedIt: 'You can add Blocks to this set once you have saved it for the first time' 36 | BlocksSiteTreeExtension: 37 | BlocksInheritedFromBlockSets: 'Blocks Inherited from Block Sets' 38 | DisableInheritedBlocks: 'Disable Inherited Blocks' 39 | DisableInheritedBlocksDescription: 'Select any inherited blocks that you would not like displayed on this page.' 40 | InheritBlocksFromBlockSets: 'Inherit Blocks from Block Sets' 41 | InheritedBlocksEditLink: 'Tip: Inherited blocks can be edited in the {link_start}Block Admin area{link_end}' 42 | NoBlockAreasConfigured: 'This page type has no Block Areas configured.' 43 | NoInheritedBlocksToDisable: 'This page has no inherited blocks to disable.' 44 | PreviewBlockAreasLink: 'Preview Block Areas for this page' 45 | ContentBlock: 46 | PLURALNAME: 'Content Blocks' 47 | SINGULARNAME: 'Content Block' 48 | GridFieldConfigBlockManager: 49 | AboveOrBelow: 'Above or Below' 50 | -------------------------------------------------------------------------------- /lang/hr.yml: -------------------------------------------------------------------------------- 1 | hr: 2 | Block: 3 | Any: (bilo koji) 4 | BlockArea: 'Područje bloka' 5 | BlockSetsAsString: 'Blok setovi: {sets}' 6 | BlockType: 'Vrsta bloka' 7 | Content: Sadržaj 8 | CreateBlock: 'Kreiraj blok' 9 | DeleteBlock: 'Obriši blok' 10 | EditBlock: 'Uredi block' 11 | ExtraCSSClasses: 'Dodatne CSS klase' 12 | IsPublishedField: Objavljen 13 | PLURALNAME: Blokovi 14 | PagesAsString: 'Stranice: {pages}' 15 | PermissionCategory: Blokovi 16 | PublishBlock: 'Objavi blok' 17 | SINGULARNAME: Blok 18 | Title: Naslov 19 | TitleRequired: 'Naslov bloka je obavezan' 20 | UsageListAsString: 'Korišten na' 21 | ViewerGroups: 'Grupe za pregled' 22 | BlockAdmin: 23 | MENUTITLE: Blokovi 24 | BlockSet: 25 | ApplyBlockSetToSelectedPageParentsAsWellAsChildren: 'Primjeni blok set na označene stranice roditelja kao i djece' 26 | CreateBlockSet: 'Kreiraj blok set' 27 | DeleteBlockSet: 'Obriši blok set' 28 | EditBlockSet: 'Uredi blok set' 29 | OnlyApplyToChildrenOfThesePages: 'Primjeni samo na djecu ovih stranica:' 30 | OnlyApplyToThesePageTypes: 'Primjeni samo na ove tipove stranica:' 31 | OnlyApplyToThesePageTypesDescription: 'Odabrani tipovi stranica će naslijediti ovaj blok set automatski. Ostavite sve neoznačeno kako bi primjenili na sve tipove stranica.' 32 | PLURALNAME: 'Blok setovi' 33 | SINGULARNAME: 'Blok set' 34 | Settings: Postavke 35 | YouCanAddBlocksToThisSetOnceYouHaveSavedIt: 'Možete dodati blokove na ovaj set nakon što ga spremite po prvi puta' 36 | BlocksSiteTreeExtension: 37 | BlocksInheritedFromBlockSets: 'Blokovi nasljeđeni iz blok setova' 38 | DisableInheritedBlocks: 'Onemogući nasljeđivanje blokova' 39 | DisableInheritedBlocksDescription: 'Označi samo nasljeđene blokove koje ne želite prikazati na ovoj stranici.' 40 | InheritBlocksFromBlockSets: 'Nasljeđeni blokovi iz blok setova' 41 | InheritedBlocksEditLink: 'Savjet: Nasljeđeni blokovi mogu se uređivati {link_start}Blok admin sekciji{link_end}' 42 | NoBlockAreasConfigured: 'Ovaj tip stranice nema konfiguriranih blok područja.' 43 | NoInheritedBlocksToDisable: 'Ova stranica nema nasljećenih blokova za onemogućavanje.' 44 | PreviewBlockAreasLink: 'Pregled blok područja za ovu stranicu' 45 | ContentBlock: 46 | PLURALNAME: 'Sadržajni blokovi' 47 | SINGULARNAME: 'Sadržajni blok' 48 | GridFieldConfigBlockManager: 49 | AboveOrBelow: 'Iznad ili ispod' 50 | -------------------------------------------------------------------------------- /lang/lt.yml: -------------------------------------------------------------------------------- 1 | lt: 2 | Block: 3 | Any: '(bet koks)' 4 | BlockArea: 'Bloko vieta' 5 | BlockSetsAsString: 'Bloko rinkinys: {sets}' 6 | BlockType: 'Bloko tipas' 7 | Content: 'Turinys' 8 | CreateBlock: 'Kurti bloką' 9 | DeleteBlock: 'Trinti bloką' 10 | EditBlock: 'Redaguoti bloką' 11 | ExtraCSSClasses: 'Papildomos CSS klasės' 12 | IsPublishedField: 'Publikuojamas' 13 | PLURALNAME: 'Blokai' 14 | PagesAsString: 'Puslapiai: {pages}' 15 | PermissionCategory: 'Blokai' 16 | PublishBlock: 'Publikuoti bloką' 17 | SINGULARNAME: 'Blokas' 18 | Title: 'Antraštė' 19 | TitleRequired: 'Bloko antraštė yra privaloma' 20 | UsageListAsString: 'Naudojamas' 21 | ViewerGroups: 'Peržiūros grupės' 22 | BlockAdmin: 23 | MENUTITLE: 'Blokai' 24 | BlockSet: 25 | ApplyBlockSetToSelectedPageParentsAsWellAsChildren: 'Taikyti bloko rinkinį pasirinktiems tėvyniams ir jų vaikiniams puslapiams' 26 | CreateBlockSet: 'Kurti bloko rinkinį' 27 | DeleteBlockSet: 'Trinti bloko rinkinį' 28 | EditBlockSet: 'Redaguoti bloko rinkinį' 29 | OnlyApplyToChildrenOfThesePages: 'Taikyti tik vaikiniams šiems puslapiams:' 30 | OnlyApplyToThesePageTypes: 'Taikyti tik šiems puslapių tipams:' 31 | OnlyApplyToThesePageTypesDescription: 'Pasirinkti puslapių tipai automatiškai paveldės šį bloko rinkinį. Palikite visus nepažymėtus, kad priskirtu visiems puslapių tipams.' 32 | PLURALNAME: 'Blokų rinkiniai' 33 | SINGULARNAME: 'Blokų rinkinys' 34 | Settings: 'Nustatymai' 35 | YouCanAddBlocksToThisSetOnceYouHaveSavedIt: 'Jūs galite pridėti bloką į šį rinkinį vieną kartą išsaugojus pirmą kartą' 36 | BlocksSiteTreeExtension: 37 | BlocksInheritedFromBlockSets: 'Paveldėti blokai iš rinkinio.' 38 | DisableInheritedBlocks: 'Išjungti blokų paveldėjimą' 39 | DisableInheritedBlocksDescription: 'Pasirinkite paveldėtus blokus, kuriuos nenorite rodyti šiame puslapyje.' 40 | InheritBlocksFromBlockSets: 'Paveldėti blokus iš rinkinio.' 41 | InheritedBlocksEditLink: 'Patarimas: paveldėtus blokus galimą redaguoti {link_start}Bloko administravimo dalyje{link_end}' 42 | NoBlockAreasConfigured: 'Šis puslapio tipas neturi sukonfiguruotų bloko sričių.' 43 | NoInheritedBlocksToDisable: 'Šis puslapis neturi paveldėtu blokų, kuriuos galėtu išjungti.' 44 | PreviewBlockAreasLink: 'Peržiūrėti bloko sritis šiame puslapyje.' 45 | ContentBlock: 46 | PLURALNAME: 'Turinio blokai' 47 | SINGULARNAME: 'Turinio blokas' 48 | GridFieldConfigBlockManager: 49 | AboveOrBelow: 'Virš arba po' 50 | -------------------------------------------------------------------------------- /lang/fi.yml: -------------------------------------------------------------------------------- 1 | fi: 2 | Block: 3 | Any: '(mikä tahansa)' 4 | BlockArea: 'Sijaintialue' 5 | BlockSetsAsString: 'Laatikkoryhmät: {sets}' 6 | BlockType: 'Laatikon tyyppi' 7 | Content: Sisältö 8 | CreateBlock: 'Luo laatikko' 9 | DeleteBlock: 'Poista laatikko' 10 | EditBlock: 'Muokkaa laatikkoa' 11 | ExtraCSSClasses: 'Lisä-CSS-luokat' 12 | IsPublishedField: Julkaistu 13 | PLURALNAME: Laatikot 14 | PagesAsString: 'Sivut: {pages}' 15 | PermissionCategory: Laatikot 16 | PublishBlock: 'Julkaise laatikko' 17 | SINGULARNAME: Laatikko 18 | Title: Otsikko 19 | TitleRequired: 'Laatikolle tulee syöttää otsikko' 20 | UsageListAsString: 'Käytössä' 21 | ViewerGroups: 'Oikeudet' 22 | BlockAdmin: 23 | MENUTITLE: Laatikot 24 | BlockSet: 25 | ApplyBlockSetToSelectedPageParentsAsWellAsChildren: 'Apply block set to selected page parents as well as children' 26 | CreateBlockSet: 'Luo laatikkoryhmä' 27 | DeleteBlockSet: 'Poista laatikkoryhmä' 28 | EditBlockSet: 'Muokkaa laatikkoryhmää' 29 | OnlyApplyToChildrenOfThesePages: 'Käytä vain näiden sivujen alasivuilla:' 30 | OnlyApplyToThesePageTypes: 'Käytä vain näissä sivutyypeissä:' 31 | OnlyApplyToThesePageTypesDescription: 'Valitut sivutyypit tulevat käyttämään tätä laatikkoryhmää automaattisesti. Jätä kaikki valitsematta käyttääksesi kaikilla sivutyypeillä.' 32 | PLURALNAME: 'Laatikkoryhmät' 33 | SINGULARNAME: 'Laatikkoryhmä' 34 | Settings: Asetukset 35 | YouCanAddBlocksToThisSetOnceYouHaveSavedIt: 'Voit lisätä laatikoita tähän ryhmään sitten kun olet tallentanut ryhmän ensimmäisen kerran.' 36 | BlocksSiteTreeExtension: 37 | BlocksInheritedFromBlockSets: 'Laatikkoryhmistä perityt laatikot' 38 | DisableInheritedBlocks: 'Jätä pois nämä perityt laatikot' 39 | DisableInheritedBlocksDescription: 'Voit valita mitä tahansa perittyjä laatikoita, joita et halua käyttää tällä sivulla.' 40 | InheritBlocksFromBlockSets: 'Laatikkoryhmistä perittävät laatikot' 41 | InheritedBlocksEditLink: 'Perittyjä laatikoita voi muokata {link_start}Laatikot-osiossa{link_end}' 42 | NoBlockAreasConfigured: 'Tälle sivutyypille ei ole määritetty lainkaan laatikoiden sijaintialueita asetustiedostossa.' 43 | NoInheritedBlocksToDisable: 'Tämä sivu ei peri mitään laatikoita.' 44 | PreviewBlockAreasLink: 'Näytä laatikoiden sijaintialueet tällä sivulla' 45 | ContentBlock: 46 | PLURALNAME: 'Sisältölaatikot' 47 | SINGULARNAME: 'Sisältölaatikko' 48 | GridFieldConfigBlockManager: 49 | AboveOrBelow: 'Ylä- tai alapuolella' 50 | -------------------------------------------------------------------------------- /lang/ny.yml: -------------------------------------------------------------------------------- 1 | nl: 2 | Block: 3 | Any: '(alle)' 4 | BlockArea: 'Blokgebied' 5 | BlockSetsAsString: 'Blokreeks: {sets}' 6 | BlockType: 'Bloktype' 7 | Content: 'Inhoud' 8 | CreateBlock: 'Creër een Blok' 9 | DeleteBlock: 'Verwijder een Blok' 10 | EditBlock: 'Bewerk een Blok' 11 | ExtraCSSClasses: 'Extra CSS Classes' 12 | IsPublishedField: 'Gepubliceerd' 13 | PLURALNAME: 'Blokken' 14 | PagesAsString: "Pagina's: {Pagina's}" 15 | PermissionCategory: 'Blokken' 16 | PublishBlock: 'Publiceer een Blok' 17 | SINGULARNAME: 'Blok' 18 | Title: 'Titel' 19 | TitleRequired: 'Blok Titel is verplicht' 20 | UsageListAsString: 'Gebruikt op' 21 | ViewerGroups: 'Viewer Groepen' 22 | BlockAdmin: 23 | MENUTITLE: 'Blokken' 24 | BlockSet: 25 | ApplyBlockSetToSelectedPageParentsAsWellAsChildren: "Blokreeks toepassen op geselecteerde pagina's én onderliggende pagina's" 26 | CreateBlockSet: 'Creër een Blokreeks' 27 | DeleteBlockSet: 'Verwijder een Blokreeks' 28 | EditBlockSet: 'Bewerk een Blokreeks' 29 | OnlyApplyToChildrenOfThesePages: "Alleen van toepassing op onderliggende pagina's van deze pagina's:" 30 | OnlyApplyToThesePageTypes: 'Alleen van toepassing op deze paginatypen:' 31 | OnlyApplyToThesePageTypesDescription: 'Geselecteerde paginatypen zullen deze Blokreeks automatisch overnemen. Laat alle uitgeschakeld om toe te passen op alle paginatypen.' 32 | PLURALNAME: 'Blokreeks' 33 | SINGULARNAME: 'Blokreeksen' 34 | Settings: 'Instellingen' 35 | YouCanAddBlocksToThisSetOnceYouHaveSavedIt: 'U kunt blokken toevoegen aan deze reeks zodra u het voor de eerste keer heeft opgeslagen' 36 | BlocksSiteTreeExtension: 37 | BlocksInheritedFromBlockSets: 'Blokken overgenomen van Blokreeksen' 38 | DisableInheritedBlocks: 'Overgenomen Blokken uitschakelen' 39 | DisableInheritedBlocksDescription: 'Selecteer alle overgenomen blokken welke u niet wilt tonen op deze pagina.' 40 | InheritBlocksFromBlockSets: 'Neem Blokken over van Blokreeksen' 41 | InheritedBlocksEditLink: 'Tip: Overgenomen blokken kunnen worden bewerkt in de {link_start}Blokadminstrator{link_end}' 42 | NoBlockAreasConfigured: 'Dit paginatype heeft geen Blokgebieden geconfigureerd.' 43 | NoInheritedBlocksToDisable: 'Deze pagina heeft geen overgenomen Blokken om uit te schakelen.' 44 | PreviewBlockAreasLink: 'Bekijk Blokgebieden voor deze pagina' 45 | ContentBlock: 46 | PLURALNAME: 'Inhoudblokken' 47 | SINGULARNAME: 'Inhoudblok' 48 | GridFieldConfigBlockManager: 49 | AboveOrBelow: 'Boven of onder' 50 | -------------------------------------------------------------------------------- /src/controllers/BlockAdmin.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class BlockAdmin extends ModelAdmin 17 | { 18 | private static $managed_models = [ 19 | Block::class, 20 | Blockset::class, 21 | ]; 22 | 23 | private static $url_segment = "block-admin"; 24 | 25 | private static $menu_title = "Blocks"; 26 | 27 | public $showImportForm = false; 28 | 29 | private static $dependencies = [ 30 | "blockManager" => '%$blockManager', 31 | ]; 32 | 33 | public $blockManager; 34 | 35 | /** 36 | * @return array 37 | **/ 38 | public function getManagedModels() 39 | { 40 | $models = parent::getManagedModels(); 41 | 42 | // remove blocksets if not in use (set in config): 43 | if (!$this->blockManager->getUseBlockSets()) { 44 | unset($models['BlockSet']); 45 | } 46 | 47 | return $models; 48 | } 49 | 50 | /** 51 | * @return Form 52 | **/ 53 | public function getEditForm($id = null, $fields = null) 54 | { 55 | Versioned::set_stage('Stage'); 56 | $form = parent::getEditForm($id, $fields); 57 | 58 | if ($blockGridField = $form->Fields()->fieldByName('Block')) { 59 | $blockGridField->setConfig(GridFieldConfigBlockManager::create(true, true, false)); 60 | $config = $blockGridField->getConfig(); 61 | $dcols = $config->getComponentByType('GridFieldDataColumns'); 62 | $dfields = $dcols->getDisplayFields($blockGridField); 63 | unset($dfields['BlockArea']); 64 | $dcols->setDisplayFields($dfields); 65 | } 66 | 67 | return $form; 68 | } 69 | 70 | public function getSearchContext() 71 | { 72 | $context = parent::getSearchContext(); 73 | $fields = $context->getFields(); 74 | $subclasses = $this->blockManager->getBlockClasses(); 75 | if ($fields->dataFieldByName('q[ClassName]') && sizeof($subclasses) > 1) { 76 | $fields->dataFieldByName('q[ClassName]')->setSource($subclasses); 77 | $fields->dataFieldByName('q[ClassName]')->setEmptyString('(any)'); 78 | } else { 79 | $fields->removeByName('q[ClassName]'); 80 | } 81 | return $context; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/BlockSetTest.php: -------------------------------------------------------------------------------- 1 | objFromFixture(BlockSet::class, 'default'); 23 | $fields = $object->getCMSFields(); 24 | $this->assertInstanceOf(FieldList::class, $fields); 25 | } 26 | 27 | /** 28 | * 29 | */ 30 | public function testCanView() 31 | { 32 | $object = $this->objFromFixture(BlockSet::class, 'default'); 33 | $admin = $this->objFromFixture(Member::class, 'admin'); 34 | $this->assertTrue($object->canView($admin)); 35 | $member = $this->objFromFixture(Member::class, 'default'); 36 | $this->assertTrue($object->canView($member)); 37 | } 38 | 39 | /** 40 | * 41 | */ 42 | public function testCanEdit() 43 | { 44 | $object = $this->objFromFixture(BlockSet::class, 'default'); 45 | $admin = $this->objFromFixture(Member::class, 'admin'); 46 | $this->assertTrue($object->canEdit($admin)); 47 | $member = $this->objFromFixture(Member::class, 'default'); 48 | $this->assertFalse($object->canEdit($member)); 49 | } 50 | 51 | /** 52 | * 53 | */ 54 | public function testCanDelete() 55 | { 56 | $object = $this->objFromFixture(BlockSet::class, 'default'); 57 | $admin = $this->objFromFixture(Member::class, 'admin'); 58 | $this->assertTrue($object->canDelete($admin)); 59 | $member = $this->objFromFixture(Member::class, 'default'); 60 | $this->assertFalse($object->canDelete($member)); 61 | } 62 | 63 | /** 64 | * 65 | */ 66 | public function testCanCreate() 67 | { 68 | $object = $this->objFromFixture(BlockSet::class, 'default'); 69 | $admin = $this->objFromFixture(Member::class, 'admin'); 70 | $this->assertTrue($object->canCreate($admin)); 71 | $member = $this->objFromFixture(Member::class, 'default'); 72 | $this->assertFalse($object->canCreate($member)); 73 | } 74 | 75 | /** 76 | * 77 | */ 78 | public function testProvidePermissions() 79 | { 80 | $object = singleton(BlockSet::class); 81 | $expected = [ 82 | 'BLOCKSET_EDIT' => [ 83 | 'name' => _t('BlockSet.EditBlockSet','Edit a Block Set'), 84 | 'category' => _t('Block.PermissionCategory', 'Blocks'), 85 | ], 86 | 'BLOCKSET_DELETE' => [ 87 | 'name' => _t('BlockSet.DeleteBlockSet','Delete a Block Set'), 88 | 'category' => _t('Block.PermissionCategory', 'Blocks'), 89 | ], 90 | 'BLOCKSET_CREATE' => [ 91 | 'name' => _t('BlockSet.CreateBlockSet','Create a Block Set'), 92 | 'category' => _t('Block.PermissionCategory', 'Blocks'), 93 | ], 94 | ]; 95 | 96 | $this->assertEquals($expected, $object->providePermissions()); 97 | } 98 | } -------------------------------------------------------------------------------- /tests/BlockTest.php: -------------------------------------------------------------------------------- 1 | objFromFixture(Block::class, 'default'); 23 | $this->assertEquals($object->getTypeForGridfield(), $object->singular_name()); 24 | } 25 | 26 | /** 27 | * 28 | */ 29 | public function testGetCMSFields() 30 | { 31 | $object = $this->objFromFixture(Block::class, 'default'); 32 | $fields = $object->getCMSFields(); 33 | $this->assertInstanceOf(FieldList::class, $fields); 34 | } 35 | 36 | /** 37 | * 38 | */ 39 | public function testCanView() 40 | { 41 | $object = $this->objFromFixture(Block::class, 'default'); 42 | $admin = $this->objFromFixture(Member::class, 'admin'); 43 | $this->assertTrue($object->canView($admin)); 44 | $member = $this->objFromFixture(Member::class, 'default'); 45 | $this->assertTrue($object->canView($member)); 46 | } 47 | 48 | /** 49 | * 50 | */ 51 | public function testCanEdit() 52 | { 53 | $object = $this->objFromFixture(Block::class, 'default'); 54 | $admin = $this->objFromFixture(Member::class, 'admin'); 55 | $this->assertTrue($object->canEdit($admin)); 56 | $member = $this->objFromFixture(Member::class, 'default'); 57 | $this->assertFalse($object->canEdit($member)); 58 | } 59 | 60 | /** 61 | * 62 | */ 63 | public function testCanDelete() 64 | { 65 | $object = $this->objFromFixture(Block::class, 'default'); 66 | $admin = $this->objFromFixture(Member::class, 'admin'); 67 | $this->assertTrue($object->canDelete($admin)); 68 | $member = $this->objFromFixture(Member::class, 'default'); 69 | $this->assertFalse($object->canDelete($member)); 70 | } 71 | 72 | /** 73 | * 74 | */ 75 | public function testCanCreate() 76 | { 77 | $object = $this->objFromFixture(Block::class, 'default'); 78 | $admin = $this->objFromFixture(Member::class, 'admin'); 79 | $this->assertTrue($object->canCreate($admin)); 80 | $member = $this->objFromFixture(Member::class, 'default'); 81 | $this->assertFalse($object->canCreate($member)); 82 | } 83 | 84 | /** 85 | * 86 | */ 87 | public function testProvidePermissions() 88 | { 89 | $object = singleton(Block::class); 90 | $expected = [ 91 | 'BLOCK_EDIT' => [ 92 | 'name' => _t('Block.EditBlock', 'Edit a Block'), 93 | 'category' => _t('Block.PermissionCategory', 'Blocks'), 94 | ], 95 | 'BLOCK_DELETE' => [ 96 | 'name' => _t('Block.DeleteBlock', 'Delete a Block'), 97 | 'category' => _t('Block.PermissionCategory', 'Blocks'), 98 | ], 99 | 'BLOCK_CREATE' => [ 100 | 'name' => _t('Block.CreateBlock', 'Create a Block'), 101 | 'category' => _t('Block.PermissionCategory', 'Blocks'), 102 | ], 103 | 'BLOCK_PUBLISH' => [ 104 | 'name' => _t('Block.PublishBlock', 'Publish a Block'), 105 | 'category' => _t('Block.PermissionCategory', 'Blocks'), 106 | ], 107 | ]; 108 | 109 | $this->assertEquals($expected, $object->providePermissions()); 110 | } 111 | } -------------------------------------------------------------------------------- /src/BlockManager.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class BlockManager extends ViewableData 19 | { 20 | /** 21 | * Use default ContentBlock class. 22 | * 23 | * @var bool 24 | **/ 25 | private static $use_default_blocks = true; 26 | 27 | /** 28 | * Show a block area preview button in CMS 29 | * 30 | * @var bool 31 | **/ 32 | private static $block_area_preview = true; 33 | 34 | public function __construct() 35 | { 36 | parent::__construct(); 37 | } 38 | 39 | /** 40 | * Gets an array of all areas defined for blocks. 41 | * 42 | * @param bool $keyAsValue 43 | * 44 | * @return array $areas 45 | **/ 46 | public function getAreas($keyAsValue = true) 47 | { 48 | $areas = $this->config()->get('areas'); 49 | 50 | $areas = $keyAsValue ? ArrayLib::valuekey(array_keys($areas)) : $areas; 51 | if (count($areas)) { 52 | foreach ($areas as $k => $v) { 53 | $areas[$k] = $keyAsValue ? FormField::name_to_label($k) : $v; 54 | } 55 | } 56 | 57 | return $areas; 58 | } 59 | 60 | /** 61 | * Gets an array of all areas defined that are compatible with pages of type $class. 62 | * 63 | * @param string $class 64 | * 65 | * @return array $areas 66 | **/ 67 | public function getAreasForPageType($class) 68 | { 69 | $areas = $this->getAreas(false); 70 | 71 | if (!$areas) { 72 | return false; 73 | } 74 | 75 | foreach ($areas as $area => $config) { 76 | if (!is_array($config)) { 77 | continue; 78 | } 79 | 80 | if (isset($config['except'])) { 81 | $except = $config['except']; 82 | if (is_array($except) 83 | ? in_array($class, $except) 84 | : $except == $class 85 | ) { 86 | unset($areas[$area]); 87 | continue; 88 | } 89 | } 90 | 91 | if (isset($config['only'])) { 92 | $only = $config['only']; 93 | if (is_array($only) 94 | ? !in_array($class, $only) 95 | : $only != $class 96 | ) { 97 | unset($areas[$area]); 98 | continue; 99 | } 100 | } 101 | } 102 | 103 | if (count($areas)) { 104 | foreach ($areas as $k => $v) { 105 | $areas[$k] = _t('Block.BlockAreaName.'.$k, FormField::name_to_label($k)); 106 | } 107 | 108 | return $areas; 109 | } else { 110 | return $areas; 111 | } 112 | } 113 | 114 | public function getBlockClasses() 115 | { 116 | $classes = ArrayLib::valuekey(ClassInfo::subclassesFor("SheaDawson\Blocks\model\Block")); 117 | array_shift($classes); 118 | foreach ($classes as $k => $v) { 119 | $classes[$k] = singleton($k)->singular_name(); 120 | } 121 | 122 | $config = $this->config()->get('options'); 123 | 124 | if (isset($config['use_default_blocks']) && !$config['use_default_blocks']) { 125 | unset($classes['ContentBlock']); 126 | } else if (!$config['use_default_blocks']) { 127 | unset($classes['ContentBlock']); 128 | } 129 | 130 | $disabledArr = Config::inst()->get("BlockManager", 'disabled_blocks') ? Config::inst()->get("BlockManager", 'disabled_blocks') : []; 131 | if (isset($config['disabled_blocks'])) { 132 | $disabledArr = array_merge($disabledArr, $config['disabled_blocks']); 133 | } 134 | if (count($disabledArr)) { 135 | foreach ($disabledArr as $k => $v) { 136 | unset($classes[$v]); 137 | } 138 | } 139 | 140 | return $classes; 141 | } 142 | 143 | /* 144 | * Usage of BlockSets configurable from yaml 145 | */ 146 | public function getUseBlockSets() 147 | { 148 | $config = $this->config()->get('options'); 149 | 150 | return isset($config['use_blocksets']) ? $config['use_blocksets'] : true; 151 | } 152 | 153 | /* 154 | * Exclusion of blocks from page types defined in yaml 155 | */ 156 | public function getExcludeFromPageTypes() 157 | { 158 | $config = $this->config()->get('options'); 159 | 160 | return isset($config['exclude_from_page_types']) ? $config['exclude_from_page_types'] : []; 161 | } 162 | 163 | /* 164 | * getWhiteListedPageTypes optionally configured by the developer 165 | */ 166 | public function getWhiteListedPageTypes() 167 | { 168 | $config = $this->config()->get('options'); 169 | return isset($config['pagetype_whitelist']) ? $config['pagetype_whitelist'] : []; 170 | } 171 | 172 | /* 173 | * getBlackListedPageTypes optionally configured by the developer 174 | * Includes blacklisted page types defined in the old exclude_from_page_types array 175 | */ 176 | public function getBlackListedPageTypes() 177 | { 178 | $config = $this->config()->get('options'); 179 | $legacy = isset($config['exclude_from_page_types']) ? $config['exclude_from_page_types'] : []; 180 | $current = isset($config['pagetype_blacklist']) ? $config['pagetype_blacklist'] : []; 181 | return array_merge($legacy, $current); 182 | } 183 | 184 | /* 185 | * Usage of extra css classes configurable from yaml 186 | */ 187 | public function getUseExtraCSSClasses() 188 | { 189 | $config = $this->config()->get('options'); 190 | 191 | return isset($config['use_extra_css_classes']) ? $config['use_extra_css_classes'] : false; 192 | } 193 | 194 | /* 195 | * Prefix for the default CSSClasses 196 | */ 197 | public function getPrefixDefaultCSSClasses() 198 | { 199 | $config = $this->config()->get('options'); 200 | 201 | return isset($config['prefix_default_css_classes']) ? $config['prefix_default_css_classes'] : false; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/forms/GridfieldConfigBlockManager.php: -------------------------------------------------------------------------------- 1 | 29 | */ 30 | class GridFieldConfigBlockManager extends GridFieldConfig 31 | { 32 | public $blockManager; 33 | 34 | public function __construct($canAdd = true, $canEdit = true, $canDelete = true, $editableRows = false, $aboveOrBelow = false) 35 | { 36 | parent::__construct(); 37 | 38 | $this->blockManager = Injector::inst()->get('SheaDawson\\Blocks\\BlockManager'); 39 | $controllerClass = Controller::curr()->class; 40 | // Get available Areas (for page) or all in case of ModelAdmin 41 | if ($controllerClass == 'CMSPageEditController') { 42 | $currentPage = Controller::curr()->currentPage(); 43 | $areasFieldSource = $this->blockManager->getAreasForPageType($currentPage->ClassName); 44 | } else { 45 | $areasFieldSource = $this->blockManager->getAreas(); 46 | } 47 | 48 | // EditableColumns only makes sense on Saveable parenst (eg Page), or inline changes won't be saved 49 | if ($editableRows) { 50 | $this->addComponent($editable = new GridFieldEditableColumns()); 51 | $displayfields = array( 52 | 'TypeForGridfield' => array('title' => _t('Block.BlockType', 'Block Type'), 'field' => 'SilverStripe\\Forms\\LiteralField'), 53 | 'Title' => array('title' => _t('Block.Title', 'Title'), 'field' => 'Silverstripe\\Forms\\ReadonlyField'), 54 | 'BlockArea' => array( 55 | 'title' => _t('Block.BlockArea', 'Block Area'), 56 | 'callback' => function () use ($areasFieldSource) { 57 | $areasField = DropdownField::create('BlockArea', 'Block Area', $areasFieldSource); 58 | if (count($areasFieldSource) > 1) { 59 | $areasField->setHasEmptyDefault(true); 60 | } 61 | return $areasField; 62 | }, 63 | ), 64 | 'isPublishedIcon' => array('title' => _t('Block.IsPublishedField', 'Published'), 'field' => 'SilverStripe\\Forms\\LiteralField'), 65 | 'UsageListAsString' => array('title' => _t('Block.UsageListAsString', 'Used on'), 'field' => 'SilverStripe\\Forms\\LiteralField'), 66 | ); 67 | 68 | if ($aboveOrBelow) { 69 | $displayfields['AboveOrBelow'] = array( 70 | 'title' => _t('GridFieldConfigBlockManager.AboveOrBelow', 'Above or Below'), 71 | 'callback' => function () { 72 | return DropdownField::create('AboveOrBelow', _t('GridFieldConfigBlockManager.AboveOrBelow', 'Above or Below'), BlockSet::config()->get('above_or_below_options')); 73 | }, 74 | ); 75 | } 76 | $editable->setDisplayFields($displayfields); 77 | } else { 78 | $this->addComponent($dcols = new GridFieldDataColumns()); 79 | 80 | $displayfields = array( 81 | 'TypeForGridfield' => array('title' => _t('Block.BlockType', 'Block Type'), 'field' => 'SilverStripe\\Forms\\LiteralField'), 82 | 'Title' => _t('Block.Title', 'Title'), 83 | 'BlockArea' => _t('Block.BlockArea', 'Block Area'), 84 | 'isPublishedIcon' => array('title' => _t('Block.IsPublishedField', 'Published'), 'field' => 'SilverStripe\\Forms\\LiteralField'), 85 | 'UsageListAsString' => _t('Block.UsageListAsString', 'Used on'), 86 | ); 87 | $dcols->setDisplayFields($displayfields); 88 | $dcols->setFieldCasting(array('UsageListAsString' => 'HTMLText->Raw')); 89 | } 90 | 91 | 92 | $this->addComponent(new GridFieldButtonRow('before')); 93 | $this->addComponent(new GridFieldToolbarHeader()); 94 | $this->addComponent(new GridFieldDetailForm()); 95 | $this->addComponent($sort = new GridFieldSortableHeader()); 96 | $this->addComponent($filter = new GridFieldFilterHeader()); 97 | $this->addComponent(new GridFieldDetailForm()); 98 | if ($controllerClass == 'BlockAdmin' && class_exists('GridFieldCopyButton')) { 99 | $this->addComponent(new GridFieldCopyButton()); 100 | } 101 | 102 | $filter->setThrowExceptionOnBadDataType(false); 103 | $sort->setThrowExceptionOnBadDataType(false); 104 | 105 | if ($canAdd) { 106 | $multiClass = new GridFieldAddNewMultiClass(); 107 | $classes = $this->blockManager->getBlockClasses(); 108 | $multiClass->setClasses($classes); 109 | $this->addComponent($multiClass); 110 | //$this->addComponent(new GridFieldAddNewButton()); 111 | } 112 | 113 | if ($canEdit) { 114 | $this->addComponent(new GridFieldEditButton()); 115 | } 116 | 117 | if ($canDelete) { 118 | $this->addComponent(new GridFieldDeleteAction(true)); 119 | } 120 | 121 | return $this; 122 | } 123 | 124 | /** 125 | * Add the GridFieldAddExistingSearchButton component to this grid config. 126 | * 127 | * @return $this 128 | **/ 129 | public function addExisting() 130 | { 131 | $classes = $this->blockManager->getBlockClasses(); 132 | 133 | $this->addComponent($add = new GridFieldAddExistingSearchButton()); 134 | $add->setSearchList(Block::get()->filter(array( 135 | 'ClassName' => array_keys($classes), 136 | ))); 137 | 138 | return $this; 139 | } 140 | 141 | /** 142 | * Add the GridFieldBulkManager component to this grid config. 143 | * 144 | * @return $this 145 | **/ 146 | public function addBulkEditing() 147 | { 148 | if (class_exists('GridFieldBulkManager')) { 149 | $this->addComponent(new GridFieldBulkManager()); 150 | } 151 | 152 | return $this; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/model/BlockSet.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | class BlockSet extends DataObject implements PermissionProvider 29 | { 30 | /** 31 | * @var array 32 | **/ 33 | private static $table_name = "BlockSet"; 34 | 35 | /** 36 | * @var array 37 | **/ 38 | private static $db = [ 39 | 'Title' => 'Varchar(255)', 40 | 'PageTypes' => MultiValueField::class, 41 | 'IncludePageParent' => 'Boolean', 42 | ]; 43 | 44 | /** 45 | * @var array 46 | **/ 47 | private static $many_many = [ 48 | "Blocks" => Block::class, 49 | "PageParents" => SiteTree::class, 50 | ]; 51 | 52 | /** 53 | * @var array 54 | **/ 55 | private static $many_many_extraFields = [ 56 | 'Blocks' => [ 57 | 'Sort' => 'Int', 58 | 'BlockArea' => 'Varchar', 59 | 'AboveOrBelow' => 'Varchar', 60 | ], 61 | ]; 62 | 63 | /** 64 | * @var array 65 | **/ 66 | private static $above_or_below_options = [ 67 | 'Above' => 'Above Page Blocks', 68 | 'Below' => 'Below Page Blocks', 69 | ]; 70 | 71 | public function getCMSFields() 72 | { 73 | $fields = parent::getCMSFields(); 74 | 75 | $fields->removeFieldFromTab('Root', 'PageParents'); 76 | 77 | $fields->addFieldToTab('Root.Main', HeaderField::create('SettingsHeading', _t('BlockSet.Settings', 'Settings')), 'Title'); 78 | $fields->addFieldToTab('Root.Main', MultiValueCheckboxField::create('PageTypes', _t('BlockSet.OnlyApplyToThesePageTypes', 'Only apply to these Page Types:'), $this->pageTypeOptions()) 79 | ->setDescription(_t('BlockSet.OnlyApplyToThesePageTypesDescription', 'Selected Page Types will inherit this Block Set automatically. Leave all unchecked to apply to all page types.'))); 80 | $fields->addFieldToTab('Root.Main', TreeMultiselectField::create('PageParents', _t('BlockSet.OnlyApplyToChildrenOfThesePages', 'Only apply to children of these Pages:'), 'SilverStripe\\CMS\\Model\\SiteTree')); 81 | $fields->addFieldToTab('Root.Main', CheckboxField::create('IncludePageParent', _t('BlockSet.ApplyBlockSetToSelectedPageParentsAsWellAsChildren','Apply block set to selected page parents as well as children'))); 82 | 83 | if (!$this->ID) { 84 | $fields->addFieldToTab('Root.Main', LiteralField::create('NotSaved', "

"._t('BlockSet.YouCanAddBlocksToThisSetOnceYouHaveSavedIt', 'You can add Blocks to this set once you have saved it for the first time').'

')); 85 | 86 | return $fields; 87 | } 88 | 89 | $fields->removeFieldFromTab('Root', 'Blocks'); 90 | 91 | /** 92 | * @todo - change relation editor back to the custom block manager config and fix issues when 'creating' Blocks from a BlockSet. 93 | */ 94 | $gridConfig = GridFieldConfig_RelationEditor::create(); 95 | $gridConfig->addComponent(new GridFieldOrderableRows('Sort')); 96 | $gridConfig->addComponent(new GridFieldDeleteAction()); 97 | 98 | $gridSource = $this->Blocks()->Sort('Sort'); 99 | 100 | $fields->addFieldToTab('Root.Blocks', HeaderField::create('BlocksHeading', _t('Block.PLURALNAME', 'Blocks'))); 101 | $fields->addFieldToTab('Root.Blocks', GridField::create('Blocks', _t('Block.PLURALNAME', 'Blocks'), $gridSource, $gridConfig)); 102 | 103 | return $fields; 104 | } 105 | 106 | /** 107 | * Returns a sorted array suitable for a dropdown with pagetypes and their translated name. 108 | * 109 | * @return array 110 | */ 111 | protected function pageTypeOptions() 112 | { 113 | $pageTypes = []; 114 | $classes = ArrayLib::valueKey(SiteTree::page_type_classes()); 115 | unset($classes['VirtualPage']); 116 | unset($classes['ErrorPage']); 117 | unset($classes['RedirectorPage']); 118 | foreach ($classes as $pageTypeClass) { 119 | $pageTypes[$pageTypeClass] = singleton($pageTypeClass)->i18n_singular_name(); 120 | } 121 | asort($pageTypes); 122 | 123 | return $pageTypes; 124 | } 125 | 126 | /** 127 | * Returns a list of pages this BlockSet features on. 128 | * 129 | * @return DataList 130 | */ 131 | public function Pages() 132 | { 133 | $pages = SiteTree::get(); 134 | $types = $this->PageTypes->getValue(); 135 | if (count($types)) { 136 | $pages = $pages->filter('ClassName', $types); 137 | } 138 | 139 | $parents = $this->PageParents()->column('ID'); 140 | if (count($parents)) { 141 | $pages = $pages->filter('ParentID', $parents); 142 | } 143 | 144 | return $pages; 145 | } 146 | 147 | public function canView($member = null) 148 | { 149 | return true; 150 | } 151 | 152 | public function canEdit($member = null) 153 | { 154 | return Permission::check('ADMIN', 'any', $member) || Permission::check('BLOCK_EDIT', 'any', $member); 155 | } 156 | 157 | public function canDelete($member = null) 158 | { 159 | return Permission::check('ADMIN', 'any', $member) || Permission::check('BLOCK_DELETE', 'any', $member); 160 | } 161 | 162 | public function canCreate($member = null, $context = []) 163 | { 164 | return Permission::check('ADMIN', 'any', $member) || Permission::check('BLOCK_CREATE', 'any', $member); 165 | } 166 | 167 | public function providePermissions() 168 | { 169 | return [ 170 | 'BLOCKSET_EDIT' => [ 171 | 'name' => _t('BlockSet.EditBlockSet','Edit a Block Set'), 172 | 'category' => _t('Block.PermissionCategory', 'Blocks'), 173 | ], 174 | 'BLOCKSET_DELETE' => [ 175 | 'name' => _t('BlockSet.DeleteBlockSet','Delete a Block Set'), 176 | 'category' => _t('Block.PermissionCategory', 'Blocks'), 177 | ], 178 | 'BLOCKSET_CREATE' => [ 179 | 'name' => _t('BlockSet.CreateBlockSet','Create a Block Set'), 180 | 'category' => _t('Block.PermissionCategory', 'Blocks'), 181 | ], 182 | ]; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SilverStripe Blocks 2 | 3 | [![Build Status](https://travis-ci.org/sheadawson/silverstripe-blocks.svg?branch=master)](https://travis-ci.org/sheadawson/silverstripe-blocks) 4 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/sheadawson/silverstripe-blocks/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/sheadawson/silverstripe-blocks/?branch=master) 5 | [![codecov](https://codecov.io/gh/sheadawson/silverstripe-blocks/branch/master/graph/badge.svg)](https://codecov.io/gh/sheadawson/silverstripe-blocks) 6 | 7 | The Blocks modules aims to provide developers with a flexible foundation for defining reusable blocks of content or widgets that can be managed in the CMS. 8 | 9 | ## Notice 10 | 11 | This module is no longer maintained. If you would like to adopt it and give it a good home please submit your interest and I will be happy to discuss. 12 | 13 | ## Features 14 | 15 | * Blocks are Versioned 16 | * Blocks with Forms possible (through `BlockController`) 17 | * Drag and Drop re-ordering of Blocks 18 | * Duplicate Blocks 19 | * BlockSets for global blocks 20 | * Allow exclusion of any page types from using Blocks 21 | * Allow disabling of default/example block type - ContentBlock 22 | * Allow disabling of specific blocks 23 | 24 | 25 | ## Upgrading from 0.x 26 | 27 | See [the upgrade guide](docs/upgrading.md) 28 | 29 | ## Requirements 30 | 31 | * SilverStripe CMS ^4.0 32 | * [GridFieldExtensions](https://github.com/silverstripe-australia/silverstripe-gridfieldextensions) 33 | * [MultivalueField](https://github.com/nyeholt/silverstripe-multivaluefield) 34 | * [GridField BetterButtons](https://github.com/unclecheese/silverstripe-gridfield-betterbuttons) 35 | 36 | ## Recommended 37 | * [GridField Copybutton](https://github.com/unisolutions/silverstripe-copybutton) (duplication of blocks, from BlockAdmin) 38 | * [GridField Sitetree Buttons](https://github.com/micschk/silverstripe-gridfieldsitetreebuttons) (Edit pages directly from a Block's 'used on page' list) 39 | 40 | ## Installation 41 | 42 | ```sh 43 | composer require sheadawson/silverstripe-blocks 44 | ``` 45 | 46 | Install via composer, run `dev/build` 47 | 48 | ## Quickstart 49 | 50 | ### 1. Define Block Areas and Settings for your project in `mysite/_config/config.yml` 51 | 52 | ``` yml 53 | SheaDawson\Blocks\BlockManager: 54 | areas: 55 | Sidebar: true # a Sidebar area will be available on all page types 56 | BeforeContent: 57 | only: HomePage # a BeforeContent area will be available only on HomePage page types 58 | AfterContent: 59 | except: HomePage # a AfterContent area will be available on all page types except HomePage 60 | Footer: true # a Footer area will be available on all page types 61 | options: 62 | #use_blocksets: false # Whether to use BlockSet functionality (default if undeclared: true) 63 | #use_extra_css_classes: true # Whether to allow cms users to add extra css classes to blocks (default if undeclared: false) 64 | #prefix_default_css_classes: 'myprefix--' # prefix the automatically generated CSSClasses based on class name (default if undeclared: false) 65 | #pagetype_whitelist: # Enable the Blocks tab only pages of these types (optional) 66 | # - HomePage 67 | #pagetype_blacklist: # Disable the Blocks tab on pages of these types (optional) 68 | # - ContactPage 69 | #disabled_blocks: #allows you to disable specific blocks (optional) 70 | # - ContentBlock 71 | #use_default_blocks: false # Disable/enable the default Block types (ContentBlock) (default if undeclared: true) 72 | #block_area_preview: false # Disable block area preview button in CMS (default if undeclared: true) 73 | ``` 74 | 75 | Remember to run `?flush=1` after modifying your `.yml` config to make sure it gets applied. 76 | 77 | ### 2. Add Block Areas to your templates 78 | 79 | Adding the `BeforeContent` and `AfterContent` blocks would look something like 80 | 81 | ```html 82 |
83 |

$Title

84 | $BlockArea(BeforeContent) 85 |
$Content
86 | $BlockArea(AfterContent) 87 |
88 | ``` 89 | 90 | `$BlockArea(BeforeContent)` will loop over and display all blocks assigned to the `BeforeContent` area on the current page 91 | 92 | You can limit a block area to a maximum number of blocks using the second limit parameter 93 | 94 | ```html 95 |
96 | $BlockArea(NewsBlocks, 3) 97 |
98 | ``` 99 | 100 | ### 3. Add Blocks to a page in the CMS 101 | 102 | You will now be able to add Blocks to Pages via the CMS page edit view and in the Blocks model admin. You can also define 103 | "BlockSets" in the Blocks model admin. BlockSets can be used to apply a common collection of blocks to pages that match the criteria you define on the set. 104 | 105 | This module ships with a basic `ContentBlock`, but this can be disabled through the `BlockManager::use_default_blocks config. 106 | 107 | 108 | ## Help 109 | 110 | 111 | ### Restrict Blocks to viewer groups or logged in users 112 | 113 | When editing a block, you can restrict who can see it in the frontend by selecting "logged in users" or "users from these groups" under the Viewer Groups tab. 114 | 115 | ### Templates 116 | 117 | There are 2 types of templates you should be aware of. 118 | 119 | ### BlockArea Template 120 | 121 | The `BlockArea` template is responsible for looping over and rendering all blocks in that area. You can override this by 122 | creating a copy of the default `BlockArea.ss` and placing it in your `templates/Includes` folder. 123 | 124 | It's likely that your block areas may require different templates. You can achieve this by creating a `BlockArea_{AreaName}.ss` template. 125 | 126 | ### Block Template 127 | 128 | Each subclass of Block requires it's own template with the same name as the class. So, `SlideshowBlock.php` would have a 129 | `SlideshowBlock.ss` template. If your block requires different templates depending on the `BlockArea` it's in, you can 130 | create `SlideshowBlock_{AreaName}.ss` 131 | 132 | The current page scope can be accessed from Block templates with `$CurrentPage`. 133 | 134 | ### Block Area Preview 135 | 136 | To aid website admins in identifying the areas they can apply blocks to, a "Preview Block Areas for this page" button 137 | is available in the cms. This opens the frontend view of the page in a new tab with `?block_preview=1`. 138 | In Block Preview mode, Block Areas in the template are highlighted and labeled. 139 | 140 | There is some markup required in your BlockArea templates to facilitate this: The css class `block-area` and the 141 | `data-areaid='$AreaID'` attribute. 142 | 143 | ```html 144 |
145 | <% loop BlockArea %> 146 | $BlockHTML 147 | <% end_loop %> 148 |
149 | ``` 150 | 151 | ### Form Blocks 152 | 153 | As of v1.0 Blocks can now handle forms. See this gist for as an example: 154 | 155 | * [Block with Form example](https://gist.github.com/sheadawson/e584b0771f6b124701b4) 156 | 157 | ### Remove the Blocks button from the main CMS menu 158 | 159 | The BlockAdmin section is not always needed to be used. If you wish, you can remove the button from the menu by inserting this to `mysite/_config.php`: 160 | 161 | ``` php 162 | CMSMenu::remove_menu_item('BlockAdmin'); 163 | ``` 164 | 165 | ### Block icons 166 | 167 | Until this module properly supports icons, you can define icons by creating a `getTypeForGridfield` method in your block. 168 | Here's an example that uses font awesome: 169 | 170 | 171 | ```php 172 | public function getIcon() 173 | { 174 | return ''; 175 | } 176 | public function getTypeForGridfield() 177 | { 178 | $icon = $this->getIcon(); 179 | if ($icon) { 180 | $obj = HTMLText::create(); 181 | $obj->setValue($icon); 182 | return $obj; 183 | } else { 184 | return parent::getTypeForGridfield(); 185 | } 186 | } 187 | ``` 188 | 189 | ## Translatable Blocks 190 | 191 | For creating Blocks with translatable content, using the [translatble module](https://github.com/silverstripe/silverstripe-translatable), see [this gist](https://gist.github.com/thezenmonkey/6e6730023af553f12e3ab762ace3b08a) for a kick start. 192 | 193 | ## Screenshots 194 | 195 | ![](docs/images/overview-1.0.png) 196 | Overview 197 | 198 | ![](docs/images/preview-1.0.png) 199 | Preview of block locations 200 | 201 | ![](docs/images/edit-1.0.png) 202 | Edit a block 203 | 204 | ![](docs/images/existing-1.0.png) 205 | Add an existing block 206 | 207 | ## TODO 208 | 209 | - [ ] Re-add: Sorting primarily by Area (in order of declaration in config), on Pages (removed in favor of dr'ndr sorting) 210 | - [ ] Add icon/pic to base Block as method of recognition when dealing with lots of different blocks 211 | -------------------------------------------------------------------------------- /src/extensions/BlocksSiteTreeExtension.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | class BlocksSiteTreeExtension extends SiteTreeExtension 29 | { 30 | private static $db = [ 31 | 'InheritBlockSets' => 'Boolean', 32 | ]; 33 | 34 | private static $many_many = [ 35 | "Blocks" => Block::class, 36 | "DisabledBlocks" => Block::class, 37 | ]; 38 | 39 | public static $many_many_extraFields = [ 40 | 'Blocks' => [ 41 | 'Sort' => 'Int', 42 | 'BlockArea' => 'Varchar', 43 | ], 44 | ]; 45 | 46 | private static $defaults = [ 47 | 'InheritBlockSets' => 1, 48 | ]; 49 | 50 | private static $dependencies = [ 51 | 'blockManager' => '%$blockManager', 52 | ]; 53 | 54 | public $blockManager; 55 | 56 | 57 | /** 58 | * Check if the Blocks CMSFields should be displayed for this Page 59 | * 60 | * @return boolean 61 | **/ 62 | public function showBlocksFields() 63 | { 64 | $whiteList = $this->blockManager->getWhiteListedPageTypes(); 65 | $blackList = $this->blockManager->getBlackListedPageTypes(); 66 | 67 | if (in_array($this->owner->ClassName, $blackList)) { 68 | return false; 69 | } 70 | 71 | if (count($whiteList) && !in_array($this->owner->ClassName, $whiteList)) { 72 | return false; 73 | } 74 | 75 | if (!Permission::check('BLOCK_EDIT')) { 76 | return false; 77 | } 78 | 79 | return true; 80 | } 81 | 82 | /** 83 | * Block manager for Pages. 84 | * */ 85 | public function updateCMSFields(FieldList $fields) 86 | { 87 | if ($fields->fieldByName('Root.Blocks') || !$this->showBlocksFields()) { 88 | return; 89 | } 90 | 91 | $areas = $this->blockManager->getAreasForPageType($this->owner->ClassName); 92 | 93 | if ($areas && count($areas)) { 94 | $fields->addFieldToTab('Root', new Tab('Blocks', _t('Block.PLURALNAME', 'Blocks'))); 95 | if (BlockManager::config()->get('block_area_preview')) { 96 | $fields->addFieldToTab('Root.Blocks', 97 | LiteralField::create('PreviewLink', $this->areasPreviewButton())); 98 | } 99 | 100 | // Blocks related directly to this Page 101 | $gridConfig = GridFieldConfigBlockManager::create(true, true, true, true) 102 | ->addExisting($this->owner->class) 103 | //->addBulkEditing() 104 | // ->addComponent(new GridFieldOrderableRows()) // Comment until below TODO is complete. 105 | ; 106 | 107 | 108 | // TODO it seems this sort is not being applied... 109 | $gridSource = $this->owner->Blocks(); 110 | // ->sort(array( 111 | // "FIELD(SiteTree_Blocks.BlockArea, '" . implode("','", array_keys($areas)) . "')" => '', 112 | // 'SiteTree_Blocks.Sort' => 'ASC', 113 | // 'Name' => 'ASC' 114 | // )); 115 | 116 | $fields->addFieldToTab('Root.Blocks', GridField::create('Blocks', _t('Block.PLURALNAME', 'Blocks'), $gridSource, $gridConfig)); 117 | 118 | 119 | // Blocks inherited from BlockSets 120 | if ($this->blockManager->getUseBlockSets()) { 121 | $inheritedBlocks = $this->getBlocksFromAppliedBlockSets(null, true); 122 | 123 | if ($inheritedBlocks->count()) { 124 | $activeInherited = $this->getBlocksFromAppliedBlockSets(null, false); 125 | 126 | if ($activeInherited->count()) { 127 | $fields->addFieldsToTab('Root.Blocks', [ 128 | GridField::create('InheritedBlockList', _t('BlocksSiteTreeExtension.BlocksInheritedFromBlockSets', 'Blocks Inherited from Block Sets'), $activeInherited, 129 | GridFieldConfigBlockManager::create(false, false, false)), 130 | LiteralField::create('InheritedBlockListTip', "

"._t('BlocksSiteTreeExtension.InheritedBlocksEditLink', 'Tip: Inherited blocks can be edited in the {link_start}Block Admin area{link_end}', '', ['link_start' => '', 'link_end' => '']).'

'), 131 | ]); 132 | } 133 | 134 | $fields->addFieldToTab('Root.Blocks', 135 | ListboxField::create('DisabledBlocks', _t('BlocksSiteTreeExtension.DisableInheritedBlocks', 'Disable Inherited Blocks'), 136 | $inheritedBlocks->map('ID', 'Title'), null, null, true) 137 | ->setDescription(_t('BlocksSiteTreeExtension.DisableInheritedBlocksDescription', 'Select any inherited blocks that you would not like displayed on this page.')) 138 | ); 139 | } else { 140 | $fields->addFieldToTab('Root.Blocks', 141 | ReadonlyField::create('DisabledBlocksReadOnly', _t('BlocksSiteTreeExtension.DisableInheritedBlocks', 'Disable Inherited Blocks'), 142 | _t('BlocksSiteTreeExtension.NoInheritedBlocksToDisable','This page has no inherited blocks to disable.'))); 143 | } 144 | 145 | $fields->addFieldToTab('Root.Blocks', 146 | CheckboxField::create('InheritBlockSets', _t('BlocksSiteTreeExtension.InheritBlocksFromBlockSets', 'Inherit Blocks from Block Sets'))); 147 | } 148 | } 149 | } 150 | 151 | /** 152 | * Called from templates to get rendered blocks for the given area. 153 | * 154 | * @param string $area 155 | * @param int $limit Limit the items to this number, or null for no limit 156 | */ 157 | public function BlockArea($area, $limit = null) 158 | { 159 | if ($this->owner->ID <= 0) { 160 | return; 161 | } // blocks break on fake pages ie Security/login 162 | 163 | $list = $this->getBlockList($area); 164 | 165 | foreach ($list as $block) { 166 | if (!$block->canView()) { 167 | $list->remove($block); 168 | } 169 | } 170 | 171 | if ($limit !== null) { 172 | $list = $list->limit($limit); 173 | } 174 | 175 | $data = []; 176 | $data['HasBlockArea'] = ($this->owner->canEdit() && isset($_REQUEST['block_preview']) && $_REQUEST['block_preview']) || $list->Count() > 0; 177 | $data['BlockArea'] = $list; 178 | $data['AreaID'] = $area; 179 | 180 | $data = $this->owner->customise($data); 181 | 182 | $template = ['BlockArea_'.$area]; 183 | 184 | if (SSViewer::hasTemplate($template)) { 185 | return $data->renderWith($template); 186 | } else { 187 | return $data->renderWith('BlockArea'); 188 | } 189 | } 190 | 191 | public function HasBlockArea($area) 192 | { 193 | if ($this->owner->canEdit() && isset($_REQUEST['block_preview']) && $_REQUEST['block_preview']) { 194 | return true; 195 | } 196 | 197 | $list = $this->getBlockList($area); 198 | 199 | foreach ($list as $block) { 200 | if (!$block->canView()) { 201 | $list->remove($block); 202 | } 203 | } 204 | 205 | return $list->Count() > 0; 206 | } 207 | 208 | /** 209 | * Get a merged list of all blocks on this page and ones inherited from BlockSets. 210 | * 211 | * @param string|null $area filter by block area 212 | * @param bool $includeDisabled Include blocks that have been explicitly excluded from this page 213 | * i.e. blocks from block sets added to the "disable inherited blocks" list 214 | * 215 | * @return ArrayList 216 | * */ 217 | public function getBlockList($area = null, $includeDisabled = false) 218 | { 219 | $includeSets = $this->blockManager->getUseBlockSets() && $this->owner->InheritBlockSets; 220 | $blocks = ArrayList::create(); 221 | 222 | // get blocks directly linked to this page 223 | $nativeBlocks = $this->owner->Blocks()->sort('Sort'); 224 | if ($area) { 225 | $nativeBlocks = $nativeBlocks->filter('BlockArea', $area); 226 | } 227 | 228 | if ($nativeBlocks->count()) { 229 | foreach ($nativeBlocks as $block) { 230 | $blocks->add($block); 231 | } 232 | } 233 | 234 | // get blocks from BlockSets 235 | if ($includeSets) { 236 | $blocksFromSets = $this->getBlocksFromAppliedBlockSets($area, $includeDisabled); 237 | if ($blocksFromSets->count()) { 238 | // merge set sources 239 | foreach ($blocksFromSets as $block) { 240 | if (!$blocks->find('ID', $block->ID)) { 241 | if ($block->AboveOrBelow == 'Above') { 242 | $blocks->unshift($block); 243 | } else { 244 | $blocks->push($block); 245 | } 246 | } 247 | } 248 | } 249 | } 250 | 251 | return $blocks; 252 | } 253 | 254 | /** 255 | * Get Any BlockSets that apply to this page. 256 | * 257 | * @return ArrayList 258 | * */ 259 | public function getAppliedSets() 260 | { 261 | $list = ArrayList::create(); 262 | if (!$this->owner->InheritBlockSets) { 263 | return $list; 264 | } 265 | 266 | $sets = BlockSet::get()->where("(PageTypesValue IS NULL) OR (PageTypesValue LIKE '%:\"{$this->owner->ClassName}%')"); 267 | $ancestors = $this->owner->getAncestors()->column('ID'); 268 | 269 | foreach ($sets as $set) { 270 | $restrictedToParerentIDs = $set->PageParents()->column('ID'); 271 | if (count($restrictedToParerentIDs)) { 272 | // check whether the set should include selected parent, in which case check whether 273 | // it was in the restricted parents list. If it's not, or if include parentpage 274 | // wasn't selected, we check the ancestors of this page. 275 | if ($set->IncludePageParent && in_array($this->owner->ID, $restrictedToParerentIDs)) { 276 | $list->add($set); 277 | } else { 278 | if (count($ancestors)) { 279 | foreach ($ancestors as $ancestor) { 280 | if (in_array($ancestor, $restrictedToParerentIDs)) { 281 | $list->add($set); 282 | continue; 283 | } 284 | } 285 | } 286 | } 287 | } else { 288 | $list->add($set); 289 | } 290 | } 291 | 292 | return $list; 293 | } 294 | 295 | /** 296 | * Get all Blocks from BlockSets that apply to this page. 297 | * 298 | * @return ArrayList 299 | * */ 300 | public function getBlocksFromAppliedBlockSets($area = null, $includeDisabled = false) 301 | { 302 | $blocks = ArrayList::create(); 303 | $sets = $this->getAppliedSets(); 304 | 305 | if (!$sets->count()) { 306 | return $blocks; 307 | } 308 | 309 | foreach ($sets as $set) { 310 | $setBlocks = $set->Blocks()->sort('Sort DESC'); 311 | 312 | if (!$includeDisabled) { 313 | $setBlocks = $setBlocks->exclude('ID', $this->owner->DisabledBlocks()->column('ID')); 314 | } 315 | 316 | if ($area) { 317 | $setBlocks = $setBlocks->filter('BlockArea', $area); 318 | } 319 | 320 | $blocks->merge($setBlocks); 321 | } 322 | 323 | $blocks->removeDuplicates(); 324 | 325 | return $blocks; 326 | } 327 | 328 | /** 329 | * Get's the link for a block area preview button. 330 | * 331 | * @return string 332 | * */ 333 | public function areasPreviewLink() 334 | { 335 | return Controller::join_links($this->owner->Link(), '?block_preview=1'); 336 | } 337 | 338 | /** 339 | * Get's html for a block area preview button. 340 | * 341 | * @return string 342 | * */ 343 | public function areasPreviewButton() 344 | { 345 | return ""._t('BlocksSiteTreeExtension.PreviewBlockAreasLink', 'Preview Block Areas for this page').''; 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /src/model/Block.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | class Block extends DataObject implements PermissionProvider 35 | { 36 | 37 | private static $table_name = 'Block'; 38 | 39 | /** 40 | * @var array 41 | */ 42 | private static $db = [ 43 | 'Title' => 'Varchar(255)', 44 | 'CanViewType' => "Enum('Anyone, LoggedInUsers, OnlyTheseUsers', 'Anyone')", 45 | 'ExtraCSSClasses' => 'Varchar' 46 | ]; 47 | 48 | /** 49 | * @var array 50 | */ 51 | private static $many_many = [ 52 | "ViewerGroups" => Group::class, 53 | ]; 54 | 55 | /** 56 | * @var array 57 | */ 58 | private static $belongs_many_many = [ 59 | "Pages" => SiteTree::class, 60 | "BlockSets" => BlockSet::class, 61 | ]; 62 | 63 | private static $summary_fields = [ 64 | 'singular_name', 65 | 'Title', 66 | 'isPublishedField', 67 | 'UsageListAsString', 68 | ]; 69 | 70 | private static $searchable_fields = [ 71 | 'Title', 72 | 'ClassName', 73 | ]; 74 | 75 | public function fieldLabels($includerelations = true) 76 | { 77 | $labels = parent::fieldLabels($includerelations); 78 | 79 | $labels = array_merge($labels, [ 80 | 'singular_name' => _t('Block.BlockType', 'Block Type'), 81 | 'Title' => _t('Block.Title', 'Title'), 82 | 'isPublishedField' => _t('Block.IsPublishedField', 'Published'), 83 | 'UsageListAsString' => _t('Block.UsageListAsString', 'Used on'), 84 | 'ExtraCSSClasses' => _t('Block.ExtraCSSClasses', 'Extra CSS Classes'), 85 | 'ClassName' => _t('Block.BlockType', 'Block Type'), 86 | ]); 87 | 88 | return $labels; 89 | } 90 | 91 | public function getDefaultSearchContext() 92 | { 93 | $context = parent::getDefaultSearchContext(); 94 | 95 | $results = $this->blockManager->getBlockClasses(); 96 | if (sizeof($results) > 1) { 97 | $classfield = new DropdownField('ClassName', _t('Block.BlockType', 'Block Type')); 98 | $classfield->setSource($results); 99 | $classfield->setEmptyString(_t('Block.Any', '(any)')); 100 | $context->addField($classfield); 101 | } 102 | 103 | return $context; 104 | } 105 | 106 | /** 107 | * @var array 108 | */ 109 | private static $default_sort = ['Title' => 'ASC']; 110 | 111 | /** 112 | * @var array 113 | */ 114 | private static $dependencies = [ 115 | 'blockManager' => '%$blockManager', 116 | ]; 117 | 118 | /** 119 | * @var BlockManager 120 | */ 121 | public $blockManager; 122 | 123 | /** 124 | * @var BlockController 125 | */ 126 | protected $controller; 127 | 128 | /** 129 | * @return mixed 130 | */ 131 | public function getTypeForGridfield() 132 | { 133 | return $this->singular_name(); 134 | } 135 | 136 | public function getCMSFields() 137 | { 138 | $self = $this; 139 | $this->beforeUpdateCMSFields(function($fields) use($self) { 140 | /** @var FieldList $fields */ 141 | Requirements::add_i18n_javascript(BLOCKS_DIR . '/javascript/lang'); 142 | 143 | // this line is a temporary patch until I can work out why this dependency isn't being 144 | // loaded in some cases... 145 | if (!$self->blockManager) { 146 | $self->blockManager = singleton("SheaDawson\\Blocks\\BlockManager"); 147 | } 148 | 149 | // ClassNmae - block type/class field 150 | $classes = $self->blockManager->getBlockClasses(); 151 | $fields->addFieldToTab('Root.Main', DropdownField::create('ClassName', _t('Block.BlockType', 'Block Type'), $classes)->addExtraClass('block-type'), 'Title'); 152 | 153 | // BlockArea - display areas field if on page edit controller 154 | if (Controller::curr()->class == 'CMSPageEditController') { 155 | $currentPage = Controller::curr()->currentPage(); 156 | $areas = $self->blockManager->getAreasForPageType($currentPage->ClassName); 157 | $fields->addFieldToTab( 158 | 'Root.Main', 159 | $blockAreaField = DropdownField::create('ManyMany[BlockArea]', _t('Block.BlockArea', 'Block Area'), $areas), 160 | 'ClassName' 161 | ); 162 | 163 | 164 | 165 | if (count($areas) > 1) { 166 | $blockAreaField->setEmptyString('(Select one)'); 167 | } 168 | 169 | if (BlockManager::config()->get('block_area_preview')) { 170 | $blockAreaField->setRightTitle($currentPage->areasPreviewButton()); 171 | } 172 | } 173 | 174 | $fields->removeFieldFromTab('Root', 'BlockSets'); 175 | $fields->removeFieldFromTab('Root', 'Pages'); 176 | 177 | // legacy fields, will be removed in later release 178 | $fields->removeByName('Weight'); 179 | $fields->removeByName('Area'); 180 | $fields->removeByName('Published'); 181 | 182 | if ($self->blockManager->getUseExtraCSSClasses()) { 183 | $fields->addFieldToTab('Root.Main', $fields->dataFieldByName('ExtraCSSClasses'), 'Title'); 184 | } else { 185 | $fields->removeByName('ExtraCSSClasses'); 186 | } 187 | 188 | // Viewer groups 189 | $fields->removeFieldFromTab('Root', 'ViewerGroups'); 190 | $groupsMap = Group::get()->map('ID', 'Breadcrumbs')->toArray(); 191 | asort($groupsMap); 192 | $viewersOptionsField = new OptionsetField( 193 | 'CanViewType', 194 | _t('SiteTree.ACCESSHEADER', 'Who can view this page?') 195 | ); 196 | $viewerGroupsField = ListboxField::create('ViewerGroups', _t('SiteTree.VIEWERGROUPS', 'Viewer Groups')) 197 | ->setSource($groupsMap) 198 | ->setAttribute( 199 | 'data-placeholder', 200 | _t('SiteTree.GroupPlaceholder', 'Click to select group') 201 | ); 202 | $viewersOptionsSource = []; 203 | $viewersOptionsSource['Anyone'] = _t('SiteTree.ACCESSANYONE', 'Anyone'); 204 | $viewersOptionsSource['LoggedInUsers'] = _t('SiteTree.ACCESSLOGGEDIN', 'Logged-in users'); 205 | $viewersOptionsSource['OnlyTheseUsers'] = _t('SiteTree.ACCESSONLYTHESE', 'Only these people (choose from list)'); 206 | $viewersOptionsField->setSource($viewersOptionsSource)->setValue('Anyone'); 207 | 208 | $fields->addFieldToTab('Root', new Tab('ViewerGroups', _t('Block.ViewerGroups', 'Viewer Groups'))); 209 | $fields->addFieldsToTab('Root.ViewerGroups', [ 210 | $viewersOptionsField, 211 | $viewerGroupsField, 212 | ]); 213 | 214 | // Disabled for now, until we can list ALL pages this block is applied to (inc via sets) 215 | // As otherwise it could be misleading 216 | // Show a GridField (list only) with pages which this block is used on 217 | // $fields->removeFieldFromTab('Root.Pages', 'Pages'); 218 | // $fields->addFieldsToTab('Root.Pages', 219 | // new GridField( 220 | // 'Pages', 221 | // 'Used on pages', 222 | // $self->Pages(), 223 | // $gconf = GridFieldConfig_Base::create())); 224 | // enhance gridfield with edit links to pages if GFEditSiteTreeItemButtons is available 225 | // a GFRecordEditor (default) combined with BetterButtons already gives the possibility to 226 | // edit versioned records (Pages), but STbutton loads them in their own interface instead 227 | // of GFdetailform 228 | // if(class_exists('GridFieldEditSiteTreeItemButton')) { 229 | // $gconf->addComponent(new GridFieldEditSiteTreeItemButton()); 230 | // } 231 | }); 232 | return parent::getCMSFields(); 233 | } 234 | 235 | /** 236 | * Renders this block with appropriate templates 237 | * looks for templates that match BlockClassName_AreaName 238 | * falls back to BlockClassName. 239 | * 240 | * @return string 241 | **/ 242 | public function forTemplate() 243 | { 244 | if ($this->BlockArea) { 245 | $template = [$this->class.'_'.$this->BlockArea]; 246 | 247 | if (SSViewer::hasTemplate($template)) { 248 | return $this->renderWith($template); 249 | } 250 | } 251 | 252 | return $this->renderWith($this->ClassName, $this->getController()); 253 | } 254 | 255 | /** 256 | * @return string 257 | */ 258 | public function BlockHTML() 259 | { 260 | return $this->forTemplate(); 261 | } 262 | 263 | /* 264 | * Checks if deletion was an actual deletion, not just unpublish 265 | * If so, remove relations 266 | */ 267 | public function onAfterDelete() 268 | { 269 | parent::onAfterDelete(); 270 | if (Versioned::get_stage() == 'Stage') { 271 | $this->Pages()->removeAll(); 272 | $this->BlockSets()->removeAll(); 273 | } 274 | } 275 | 276 | /** 277 | * Remove relations onAfterDuplicate. 278 | */ 279 | public function onAfterDuplicate() 280 | { 281 | // remove relations to pages & blocksets duplicated from the original item 282 | $this->Pages()->removeAll(); 283 | $this->BlockSets()->removeAll(); 284 | } 285 | 286 | /* 287 | * Base permissions 288 | */ 289 | public function canView($member = null) 290 | { 291 | if (!$member || !(is_a($member, 'Member')) || is_numeric($member)) { 292 | $member = Security::getCurrentUser()->ID; 293 | } 294 | 295 | // admin override 296 | if ($member && Permission::checkMember($member, ['ADMIN', 'SITETREE_VIEW_ALL'])) { 297 | return true; 298 | } 299 | 300 | // Standard mechanism for accepting permission changes from extensions 301 | $extended = $this->extendedCan('canView', $member); 302 | if ($extended !== null) { 303 | return $extended; 304 | } 305 | 306 | // check for empty spec 307 | if (!$this->CanViewType || $this->CanViewType == 'Anyone') { 308 | return true; 309 | } 310 | 311 | // check for any logged-in users 312 | if ($this->CanViewType == 'LoggedInUsers' && $member) { 313 | return true; 314 | } 315 | 316 | // check for specific groups 317 | if ($member && is_numeric($member)) { 318 | $member = Member::get()->byID($member); 319 | } 320 | if ($this->CanViewType == 'OnlyTheseUsers' && $member && $member->inGroups($this->ViewerGroups())) { 321 | return true; 322 | } 323 | 324 | return false; 325 | } 326 | 327 | public function canEdit($member = null) 328 | { 329 | return Permission::check('ADMIN', 'any', $member) || Permission::check('BLOCK_EDIT', 'any', $member); 330 | } 331 | 332 | public function canDelete($member = null) 333 | { 334 | return Permission::check('ADMIN', 'any', $member) || Permission::check('BLOCK_DELETE', 'any', $member); 335 | } 336 | 337 | public function canCreate($member = null, $context = []) 338 | { 339 | return Permission::check('ADMIN', 'any', $member) || Permission::check('BLOCK_CREATE', 'any', $member); 340 | } 341 | 342 | public function canPublish($member = null) 343 | { 344 | return Permission::check('ADMIN', 'any', $member) || Permission::check('BLOCK_PUBLISH', 'any', $member); 345 | } 346 | 347 | public function providePermissions() 348 | { 349 | return [ 350 | 'BLOCK_EDIT' => [ 351 | 'name' => _t('Block.EditBlock', 'Edit a Block'), 352 | 'category' => _t('Block.PermissionCategory', 'Blocks'), 353 | ], 354 | 'BLOCK_DELETE' => [ 355 | 'name' => _t('Block.DeleteBlock', 'Delete a Block'), 356 | 'category' => _t('Block.PermissionCategory', 'Blocks'), 357 | ], 358 | 'BLOCK_CREATE' => [ 359 | 'name' => _t('Block.CreateBlock', 'Create a Block'), 360 | 'category' => _t('Block.PermissionCategory', 'Blocks'), 361 | ], 362 | 'BLOCK_PUBLISH' => [ 363 | 'name' => _t('Block.PublishBlock', 'Publish a Block'), 364 | 'category' => _t('Block.PermissionCategory', 'Blocks'), 365 | ], 366 | ]; 367 | } 368 | 369 | public function onAfterWrite() 370 | { 371 | parent::onAfterWrite(); 372 | if ($this->hasExtension('FilesystemPublisher')) { 373 | $this->republish($this); 374 | } 375 | } 376 | 377 | /** 378 | * Get a list of URL's to republish when this block changes 379 | * if using StaticPublisher module. 380 | */ 381 | public function pagesAffectedByChanges() 382 | { 383 | $pages = $this->Pages(); 384 | $urls = []; 385 | foreach ($pages as $page) { 386 | $urls[] = $page->Link(); 387 | } 388 | 389 | return $urls; 390 | } 391 | 392 | /* 393 | * Get a list of Page and Blockset titles this block is used on 394 | */ 395 | public function UsageListAsString() 396 | { 397 | $pages = implode(', ', $this->Pages()->column('URLSegment')); 398 | $sets = implode(', ', $this->BlockSets()->column('Title')); 399 | $_t_pages = _t('Block.PagesAsString', 'Pages: {pages}', '', ['pages' => $pages]); 400 | $_t_sets = _t('Block.BlockSetsAsString', 'Block Sets: {sets}', '', ['sets' => $sets]); 401 | if ($pages && $sets) { 402 | return "$_t_pages
$_t_sets"; 403 | } 404 | if ($pages) { 405 | return $_t_pages; 406 | } 407 | if ($sets) { 408 | return $_t_sets; 409 | } 410 | } 411 | 412 | /** 413 | * Check if this block has been published. 414 | * 415 | * @return bool True if this page has been published. 416 | */ 417 | public function isPublished() 418 | { 419 | if (!$this->exists()) { 420 | return false; 421 | } 422 | 423 | return (DB::query("SELECT \"ID\" FROM \"Block_Live\" WHERE \"ID\" = $this->ID")->value()) 424 | ? 1 425 | : 0; 426 | } 427 | 428 | /** 429 | * Check if this block has been published. 430 | * 431 | * @return bool True if this page has been published. 432 | */ 433 | public function isPublishedNice() 434 | { 435 | $field = DBBoolean::create('isPublished'); 436 | $field->setValue($this->isPublished()); 437 | 438 | return $field->Nice(); 439 | } 440 | 441 | /** 442 | * @return DBHTMLText 443 | */ 444 | public function isPublishedIcon() 445 | { 446 | $obj = DBHTMLText::create(); 447 | if ($this->isPublished()) { 448 | $obj->setValue(''); 449 | } else { 450 | $obj->setValue(''); 451 | } 452 | return $obj; 453 | } 454 | 455 | /** 456 | * CSS Classes to apply to block element in template. 457 | * 458 | * @return string $classes 459 | */ 460 | public function CSSClasses($stopAtClass = 'DataObject') 461 | { 462 | $classes = strtolower(parent::CSSClasses($stopAtClass)); 463 | 464 | if (!empty($classes) && ($prefix = $this->blockManager->getPrefixDefaultCSSClasses())) { 465 | $classes = $prefix.str_replace(' ', " {$prefix}", $classes); 466 | } 467 | 468 | if ($this->blockManager->getUseExtraCSSClasses()) { 469 | $classes = $this->ExtraCSSClasses ? $classes." $this->ExtraCSSClasses" : $classes; 470 | } 471 | 472 | return $classes; 473 | } 474 | 475 | /** 476 | * Access current page scope from Block templates with $CurrentPage 477 | * 478 | * @return Controller 479 | */ 480 | public function getCurrentPage() 481 | { 482 | return Controller::curr(); 483 | } 484 | 485 | /** 486 | * @throws Exception 487 | * 488 | * @return BlockController 489 | */ 490 | public function getController() 491 | { 492 | if ($this->controller) { 493 | return $this->controller; 494 | } 495 | foreach (array_reverse(ClassInfo::ancestry($this->class)) as $blockClass) { 496 | $controllerClass = "{$blockClass}Controller"; 497 | if (class_exists($controllerClass)) { 498 | break; 499 | } 500 | } 501 | if (!class_exists($controllerClass)) { 502 | throw new ValidationException("Could not find controller class for $this->classname"); 503 | } 504 | 505 | $this->controller = Injector::inst()->create($controllerClass, $this); 506 | $this->controller->init(); 507 | 508 | return $this->controller; 509 | } 510 | } 511 | --------------------------------------------------------------------------------