├── _config └── config.yml ├── .github ├── FUNDING.yml ├── workflows │ └── ci.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── templates ├── Dynamic │ └── Elements │ │ └── Blog │ │ └── Elements │ │ ├── ElementBlogWidgets.ss │ │ ├── ElementBlogPagination.ss │ │ ├── ElementBlogPosts.ss │ │ └── ElementBlogOverview.ss └── Includes │ └── ElementBlogSummary.ss ├── code-of-conduct.md ├── phpstan.neon.dist ├── lang └── en.yml ├── phpcs.xml.dist ├── .editorconfig ├── phpunit.xml.dist ├── CONTRIBUTING.md ├── phpstan-baseline.neon ├── LICENSE.md ├── composer.json ├── src └── Elements │ ├── ElementBlogPagination.php │ ├── ElementBlogPosts.php │ └── ElementBlogOverview.php ├── README.md └── CHANGELOG.md /_config/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Name: dynamicelementsblogconfig 3 | --- 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [dynamic] 4 | -------------------------------------------------------------------------------- /templates/Dynamic/Elements/Blog/Elements/ElementBlogWidgets.ss: -------------------------------------------------------------------------------- 1 | <% if $ShowWidgets && $SideBarView %> 2 | 5 | <% end_if %> 6 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | When having discussions about this module in issues or pull request please adhere to the [SilverStripe Community Code of Conduct](https://docs.silverstripe.org/en/contributing/code_of_conduct). 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | ci: 10 | name: CI 11 | uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1 12 | -------------------------------------------------------------------------------- /templates/Dynamic/Elements/Blog/Elements/ElementBlogPagination.ss: -------------------------------------------------------------------------------- 1 | <% if $ShowPagination && $PaginatedList.Exists %> 2 | 7 | <% end_if %> 8 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.neon 3 | 4 | parameters: 5 | level: 1 6 | treatPhpDocTypesAsCertain: false 7 | paths: 8 | - src 9 | excludePaths: 10 | # Exclude tests directory - these use fake framework classes that PHPStan cannot resolve 11 | - tests 12 | -------------------------------------------------------------------------------- /lang/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | Dynamic\Elements\Blog\Elements\ElementBlogPosts: 3 | BlockType: Blog Posts 4 | BlogLabel: 'Featured Blog' 5 | CategoryLabel: 'Category' 6 | DESCRIPTION: 'Show recent posts from a featured blog.' 7 | LimitLabel: 'Posts to show' 8 | PLURALNAME: Blog Post Elements 9 | PLURALS: 10 | one: 'A Blog Post Element' 11 | other: '{count} Blog Post Elements' 12 | SINGULARNAME: Blog Post Element -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | CodeSniffer ruleset for SilverStripe coding conventions. 4 | 5 | src 6 | tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # For more information about the properties used in this file, 2 | # 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 = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [{*.yml,package.json}] 14 | indent_size = 2 15 | 16 | # The indent size used in the package.json file cannot be changed: 17 | # https://github.com/npm/npm/pull/3180#issuecomment-16336516 18 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tests 6 | 7 | 8 | 9 | 10 | src/ 11 | 12 | tests/ 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: 'FEATURE ' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /templates/Includes/ElementBlogSummary.ss: -------------------------------------------------------------------------------- 1 |
2 |
3 | <% if $FeaturedImage %> 4 | 5 | $Image.Title.ATT 6 | 7 | <% end_if %> 8 |
9 |

$Title

10 | <% include SilverStripe\\Blog\\EntryMeta %> 11 | <% if $Summary %>
$Summary
<% end_if %> 12 |

Read more

13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /templates/Dynamic/Elements/Blog/Elements/ElementBlogPosts.ss: -------------------------------------------------------------------------------- 1 | <% if $Title && $ShowTitle %>

$Title

<% end_if %> 2 | <% if $Content %>
$Content
<% end_if %> 3 | 4 | <% if $PostsList %> 5 |
6 | <% loop $PostsList %> 7 | <% include ElementBlogSummary %> 8 | <% end_loop %> 9 |
10 |
11 |
12 |

13 | 14 | View all posts 15 | 16 |

17 |
18 | <% else %> 19 |

No recent posts.

20 | <% end_if %> 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: 'BUG ' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | - Maintenance on this module is a shared effort of those who use it 3 | - To contribute improvements to the code, ensure you raise a pull request and discuss with the module maintainers 4 | - Please follow the SilverStripe [code contribution guidelines](https://docs.silverstripe.org/en/contributing/code/) and [Module Standard](https://docs.silverstripe.org/en/developer_guides/extending/modules/#module-standard) 5 | - Supply documentation that followS the [GitHub Flavored Markdown](https://help.github.com/articles/markdown-basics/) conventions 6 | - When having discussions about this module in issues or pull request please adhere to the [SilverStripe Community Code of Conduct](https://docs.silverstripe.org/en/contributing/code_of_conduct/) 7 | 8 | 9 | ## Contributor license agreement 10 | By supplying code to this module in patches, tickets and pull requests, you agree to assign copyright 11 | of that code to Dynamic, on the condition that these code changes are released under the 12 | same BSD license as the original module. We ask for this so that the ownership in the license is clear 13 | and unambiguous. By releasing this code under a permissive license such as BSD, this copyright assignment 14 | won't prevent you from using the code in any way you see fit. -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - 4 | message: "#^Call to an undefined static method SilverStripe\\\\Blog\\\\Model\\\\BlogPost\\:\\:get\\(\\)\\.$#" 5 | count: 1 6 | path: src/Elements/ElementBlogOverview.php 7 | 8 | - 9 | message: "#^Call to an undefined static method SilverStripe\\\\Blog\\\\Model\\\\Blog\\:\\:get\\(\\)\\.$#" 10 | count: 2 11 | path: src/Elements/ElementBlogPosts.php 12 | 13 | - 14 | message: "#^Call to an undefined static method SilverStripe\\\\Blog\\\\Model\\\\BlogPost\\:\\:get\\(\\)\\.$#" 15 | count: 1 16 | path: src/Elements/ElementBlogPosts.php 17 | 18 | - 19 | message: "#^Class Page not found\\.$#" 20 | count: 1 21 | path: tests/Elements/ElementBlogOverviewFunctionalTest.php 22 | 23 | - 24 | message: "#^Instantiated class PageController not found\\.$#" 25 | count: 1 26 | path: tests/Elements/ElementBlogOverviewFunctionalTest.php 27 | 28 | - 29 | message: "#^Class Page not found\\.$#" 30 | count: 1 31 | path: tests/Elements/ElementBlogOverviewTest.php 32 | 33 | - 34 | message: "#^Call to an undefined static method SilverStripe\\\\Blog\\\\Model\\\\BlogPost\\:\\:get\\(\\)\\.$#" 35 | count: 2 36 | path: tests/Elements/ElementBlogPostsTest.php 37 | 38 | - 39 | message: "#^Call to an undefined static method SilverStripe\\\\Blog\\\\Model\\\\BlogPost\\:\\:get\\(\\)\\.$#" 40 | count: 1 41 | path: tests/Fake/Page.php 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Dynamic, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. 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. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | 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. 13 | -------------------------------------------------------------------------------- /templates/Dynamic/Elements/Blog/Elements/ElementBlogOverview.ss: -------------------------------------------------------------------------------- 1 |
2 | <% if $ShowTitle %> 3 |

4 | <% if $ArchiveYear %> 5 | <%t SilverStripe\\Blog\\Model\\Blog.Archive 'Archive' %>: 6 | <% if $ArchiveDay %> 7 | $ArchiveDate.Nice 8 | <% else_if $ArchiveMonth %> 9 | $ArchiveDate.format('MMMM, y') 10 | <% else %> 11 | $ArchiveDate.format('y') 12 | <% end_if %> 13 | <% else_if $CurrentTag %> 14 | <%t SilverStripe\\Blog\\Model\\Blog.Tag 'Tag' %>: $CurrentTag.Title 15 | <% else_if $CurrentCategory %> 16 | <%t SilverStripe\\Blog\\Model\\Blog.Category 'Category' %>: $CurrentCategory.Title 17 | <% else %> 18 | $Title 19 | <% end_if %> 20 |

21 | <% end_if %> 22 | 23 | <% if $Content %> 24 | $Content 25 | <% end_if %> 26 | 27 | <% if $PaginatedList.Exists %> 28 | <% loop $PaginatedList %> 29 | <% include SilverStripe\\Blog\\PostSummary %> 30 | <% end_loop %> 31 | <% else %> 32 |

<%t SilverStripe\\Blog\\Model\\Blog.NoPosts 'There are no posts' %>

33 | <% end_if %> 34 |
35 | 36 | <% if $ShowPagination && $PaginatedList.Exists %> 37 | 42 | <% end_if %> 43 | 44 | <% if $ShowWidgets && $SideBarView %> 45 | 48 | <% end_if %> 49 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dynamic/silverstripe-elemental-blog", 3 | "description": "Show recent posts from a featured blog.", 4 | "license": "BSD-3-Clause", 5 | "type": "silverstripe-vendormodule", 6 | "keywords": [ 7 | "silverstripe", 8 | "blog", 9 | "element", 10 | "block" 11 | ], 12 | "authors": [ 13 | { 14 | "name": "Dynamic", 15 | "email": "dev@dynamicagency.com", 16 | "homepage": "https://www.dynamicagency.com" 17 | } 18 | ], 19 | "funding": [ 20 | { 21 | "type": "github", 22 | "url": "https://github.com/sponsors/dynamic" 23 | } 24 | ], 25 | "require": { 26 | "php": "^8.3", 27 | "dnadesign/silverstripe-elemental": "^6.0", 28 | "sheadawson/silverstripe-dependentdropdownfield": "^4.0", 29 | "silverstripe/blog": "^5.0" 30 | }, 31 | "require-dev": { 32 | "silverstripe/recipe-testing": "^4", 33 | "squizlabs/php_codesniffer": "^3.0", 34 | "silverstripe/standards": "^1" 35 | }, 36 | "minimum-stability": "dev", 37 | "prefer-stable": true, 38 | "autoload": { 39 | "psr-4": { 40 | "Dynamic\\Elements\\Blog\\": "src/", 41 | "Dynamic\\Elements\\Blog\\Tests\\": "tests/" 42 | } 43 | }, 44 | "config": { 45 | "allow-plugins": { 46 | "composer/installers": true, 47 | "silverstripe/vendor-plugin": true, 48 | "silverstripe/recipe-plugin": true 49 | }, 50 | "process-timeout": 600 51 | }, 52 | "scripts": { 53 | "lint": "vendor/bin/phpcs src/ tests/", 54 | "lint-clean": "vendor/bin/phpcbf src/ tests/" 55 | }, 56 | "extra": { 57 | "branch-alias": { 58 | "dev-master": "4.0.x-dev" 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Elements/ElementBlogPagination.php: -------------------------------------------------------------------------------- 1 | 'Int', 43 | 'Content' => 'HTMLText', 44 | ]; 45 | 46 | /** 47 | * @var array 48 | */ 49 | private static array $has_one = [ 50 | 'Blog' => Blog::class, 51 | 'Category' => BlogCategory::class, 52 | ]; 53 | 54 | /** 55 | * @var array 56 | */ 57 | private static array $defaults = [ 58 | 'Limit' => 3, 59 | ]; 60 | 61 | /** 62 | * @return FieldList 63 | */ 64 | public function getCMSFields() 65 | { 66 | $this->beforeUpdateCMSFields(function (FieldList $fields) { 67 | $fields->dataFieldByName('Content') 68 | ->setRows(8); 69 | 70 | $fields->dataFieldByName('Limit') 71 | ->setTitle(_t(__CLASS__ . 'LimitLabel', 'Posts to show')); 72 | 73 | if (class_exists(Blog::class)) { 74 | $fields->insertBefore( 75 | 'Limit', 76 | $fields->dataFieldByName('BlogID') 77 | ->setTitle(_t(__CLASS__ . 'BlogLabel', 'Featured Blog')) 78 | ->setEmptyString('') 79 | ); 80 | 81 | $dataSource = function ($val) { 82 | if ($val) { 83 | $blog = Blog::get()->byID($val); 84 | if ($blog) { 85 | return $blog->Categories()->map('ID', 'Title'); 86 | } 87 | return []; 88 | } 89 | return []; 90 | }; 91 | 92 | $fields->insertAfter( 93 | 'BlogID', 94 | DependentDropdownField::create('CategoryID', _t( 95 | __CLASS__ . 'CategoryLabel', 96 | 'Category' 97 | ), $dataSource) 98 | ->setDepends($fields->dataFieldByName('BlogID')) 99 | ->setHasEmptyDefault(true) 100 | ->setEmptyString('') 101 | ); 102 | } 103 | }); 104 | 105 | return parent::getCMSFields(); 106 | } 107 | 108 | /** 109 | * @return ArrayList|DataList 110 | */ 111 | public function getPostsList() 112 | { 113 | /** @var ArrayList $posts */ 114 | $posts = ArrayList::create(); 115 | 116 | if ($this->BlogID && $this->CategoryID && $category = BlogCategory::get()->byID($this->CategoryID)) { 117 | $posts = $category->BlogPosts(); 118 | } elseif ($this->BlogID && $blog = Blog::get()->byID($this->BlogID)) { 119 | $posts = $blog->getBlogPosts(); 120 | } else { 121 | $posts = BlogPost::get()->sort('PublishDate DESC'); 122 | } 123 | 124 | $this->extend('updateGetPostsList', $posts); 125 | 126 | return $posts->limit($this->Limit); 127 | } 128 | 129 | 130 | /** 131 | * @return DBHTMLText 132 | */ 133 | public function getSummary() 134 | { 135 | $count = $this->getPostsList()->count(); 136 | $label = _t( 137 | BlogPost::class . '.PLURALS', 138 | 'A Blog Post|{count} Blog Posts', 139 | [ 'count' => $count ] 140 | ); 141 | return DBField::create_field('HTMLText', $label)->Summary(20); 142 | } 143 | 144 | /** 145 | * @return array 146 | */ 147 | protected function provideBlockSchema() 148 | { 149 | $blockSchema = parent::provideBlockSchema(); 150 | $blockSchema['content'] = $this->getSummary(); 151 | return $blockSchema; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SilverStripe Elemental Blog 2 | 3 | Recent Blog Posts Element for the SilverStripe Elemental module. 4 | 5 | ![CI](https://github.com/dynamic/silverstripe-elemental-blog/workflows/CI/badge.svg) 6 | [![Sponsors](https://img.shields.io/badge/Sponsor-Dynamic-ff69b4?logo=github-sponsors&logoColor=white)](https://github.com/sponsors/dynamic) 7 | 8 | [![Latest Stable Version](https://poser.pugx.org/dynamic/silverstripe-elemental-blog/v/stable)](https://packagist.org/packages/dynamic/silverstripe-elemental-blog) 9 | [![Total Downloads](https://poser.pugx.org/dynamic/silverstripe-elemental-blog/downloads)](https://packagist.org/packages/dynamic/silverstripe-elemental-blog) 10 | [![Latest Unstable Version](https://poser.pugx.org/dynamic/silverstripe-elemental-blog/v/unstable)](https://packagist.org/packages/dynamic/silverstripe-elemental-blog) 11 | [![License](https://poser.pugx.org/dynamic/silverstripe-elemental-blog/license)](https://packagist.org/packages/dynamic/silverstripe-elemental-blog) 12 | 13 | ## Requirements 14 | 15 | * SilverStripe ^6 16 | * PHP ^8.1 17 | * silverstripe/blog: ^5.0 18 | * dnadesign/silverstripe-elemental: ^6.0 19 | 20 | ## Installation 21 | 22 | `composer require dynamic/silverstripe-elemental-blog` 23 | 24 | ## Upgrading from version 3.0 25 | 26 | SilverStripe Elemental Blog 4.0 is compatible with SilverStripe 6. Key changes: 27 | 28 | - Updated to SilverStripe CMS 6 29 | - Requires PHP 8.1 or higher 30 | - Updated `silverstripe/blog` to ^5.0 (SS6 compatible) 31 | - Updated `dnadesign/silverstripe-elemental` to ^6.0 (SS6 compatible) 32 | - **Removed widget support** - The SilverStripe widget module is no longer supported in SS6 33 | - `ElementBlogWidgets` block has been removed 34 | - Widget-related configuration options have been removed 35 | - If you were using widget functionality, you will need to migrate to alternative solutions 36 | 37 | For more information about SilverStripe 6, see the [SilverStripe 6 Upgrade Guide](https://docs.silverstripe.org/en/6/upgrading/). 38 | 39 | ## License 40 | See [License](license.md) 41 | 42 | ## Usage 43 | 44 | There are three blocks available for you to use. It is likely that you will not want all of them to be available to 45 | content authors, so it is recommended that you review what the purpose of each block is, and then add the ones you don't 46 | need to `disallowed_elements`. 47 | 48 | The three blocks are: 49 | 50 | * [ElementBlogPosts](#elementblogposts) 51 | * [ElementBlogOverview](#elementblogoverview) 52 | * [ElementBlogPagination](#elementblogpagination) 53 | 54 | ### ElementBlogPosts 55 | 56 | A block to show a list of recent posts by a featured blog. Ideal for home pages or dashboards. 57 | 58 | ### ElementBlogOverview 59 | 60 | The purpose of this block is to replicate the output that was originally being given by the Blog module's `Layout` 61 | template. 62 | 63 | **Including:** 64 | 65 | - Title (including Category/Archive/etc titles) 66 | - Content 67 | - Blog Posts 68 | - Pagination 69 | 70 | ![Overview Block - Single](./docs/en/_images/overview-block-single.png) 71 | 72 | You will likely want to override the very basic default template that has been provided, you can do so by overriding the 73 | template found with the namespace `Dynamic\Elements\Blog\Elements\ElementBlogOverview.ss`. 74 | 75 | #### Controlling pagination & widgets for this block 76 | 77 | **Pagination config:** 78 | 79 | * `pagination_field_default`: `1` (pagination is enabled by default) 80 | * `show_pagination_field`: `true` (content authors have the ability to turn pagination on or off) 81 | 82 | With the default configuration, when an author creates a new Overview block, they will be presented with a checkbox 83 | to "Show pagination" (which will be ticked by default). If you do **not** want your authors to be able to disable 84 | pagination, then you can update the `show_pagination_field` config to `false`. 85 | 86 | ```yaml 87 | Dynamic\Elements\Blog\Elements\ElementBlogOverview: 88 | show_pagination_field: false 89 | ``` 90 | 91 | If you would like pagination to be turned **off** by default, then you can update the `pagination_field_default` to `0`. 92 | 93 | ```yaml 94 | Dynamic\Elements\Blog\Elements\ElementBlogOverview: 95 | pagination_field_default: 0 96 | ``` 97 | 98 | #### Using this block on Page types other than `Blog` 99 | 100 | **Please consider:** While the Overview block does support you using it on other page types, it is primarily designed to 101 | be used on Blog page types. This is because it is `Blog` and `BlogController` that provide the relevant info to this 102 | block. 103 | 104 | Please consider whether you want this block to be available to other page types, and if you don't, you might want to 105 | add this block to `disallowed_elements` on your other page types. EG: 106 | 107 | ```yaml 108 | App\Model\Page\MyPage: 109 | disallowed_elements: 110 | - Dynamic\Elements\Blog\Elements\ElementBlogOverview 111 | ``` 112 | 113 | If you do wish this block to be available on other page types, then please review the contents on the class to see how 114 | you can dictate what data should be provided to this block. 115 | 116 | ## ElementBlogPagination 117 | 118 | You might decide that you would like Pagination to be displayed quite separately to the Overview block. This can be 119 | achieved by using `ElementBlogPagination` as a separate block. 120 | 121 | **Please consider:** Like the Overview Block, please consider removing this block from any/all Page types that you do 122 | not want it available on. EG, if you don't want to use it at all, you can disallow it for all pages by default: 123 | 124 | ```yaml 125 | Page: 126 | disallowed_elements: 127 | - Dynamic\Elements\Blog\Elements\ElementBlogPagination 128 | ``` 129 | 130 | ![Overview Block Separated](./docs/en/_images/overview-block-separated.png) 131 | 132 | ## Getting more elements 133 | 134 | See [Elemental modules by Dynamic](https://github.com/orgs/dynamic/repositories?q=elemental&type=all&language=&sort=) 135 | 136 | ## Configuration 137 | 138 | See [SilverStripe Elemental Configuration](https://github.com/dnadesign/silverstripe-elemental#configuration) 139 | 140 | ## Maintainers 141 | * [Dynamic](http://www.dynamicagency.com) () 142 | 143 | ## Bugtracker 144 | Bugs are tracked in the issues section of this repository. Before submitting an issue please read over 145 | existing issues to ensure yours is unique. 146 | 147 | If the issue does look like a new bug: 148 | 149 | - Create a new issue 150 | - Describe the steps required to reproduce your issue, and the expected outcome. Unit tests, screenshots 151 | and screencasts can help here. 152 | - Describe your environment as detailed as possible: SilverStripe version, Browser, PHP version, 153 | Operating System, any installed SilverStripe modules. 154 | 155 | Please report security issues to the module maintainers directly. Please don't file security issues in the bugtracker. 156 | 157 | ## Development and contribution 158 | If you would like to make contributions to the module please ensure you raise a pull request and discuss with the module maintainers. -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## What's Changed 4 | * Docs: Update documentation for Overview block by @chrispenny in https://github.com/dynamic/silverstripe-elemental-blog/pull/40 5 | * MNT: Require PHP ^7.4 or ^8 by @chrispenny in https://github.com/dynamic/silverstripe-elemental-blog/pull/41 6 | * REFACTOR Elemental 5 by @jsirish in https://github.com/dynamic/silverstripe-elemental-blog/pull/43 7 | * REFACTOR update element names by @jsirish in https://github.com/dynamic/silverstripe-elemental-blog/pull/45 8 | * README updates by @jsirish in https://github.com/dynamic/silverstripe-elemental-blog/pull/44 9 | 10 | 11 | **Full Changelog**: https://github.com/dynamic/silverstripe-elemental-blog/compare/2.3.0...3.0.0 12 | 13 | ## What's Changed 14 | * Add new blocks supporting Pagination and Widgets by @chrispenny in #28 15 | ### New Contributors 16 | @chrispenny made their first contribution in #28 17 | 18 | Full Changelog: https://github.com/dynamic/silverstripe-elemental-blog/compare/2.2.0...2.3.0 19 | 20 | ## [2.2.0](https://github.com/dynamic/silverstripe-elemental-blog/tree/2.2.0) (2022-07-15) 21 | 22 | ## What's Changed 23 | * UPDATE workflows ci by @muskie9 in https://github.com/dynamic/silverstripe-elemental-blog/pull/32 24 | * REFACTOR allow PHP 8 and phpunit 9 by @jsirish in https://github.com/dynamic/silverstripe-elemental-blog/pull/31 25 | * BUGFIX Composer - update info by @jsirish in https://github.com/dynamic/silverstripe-elemental-blog/pull/34 26 | * CI remove travis and scrutinizer by @jsirish in https://github.com/dynamic/silverstripe-elemental-blog/pull/35 27 | * ENHANCEMENT Bootstrap template by @jsirish in https://github.com/dynamic/silverstripe-elemental-blog/pull/37 28 | 29 | 30 | **Full Changelog**: https://github.com/dynamic/silverstripe-elemental-blog/compare/2.1.3...2.2.0 31 | ## [2.1.2](https://github.com/dynamic/silverstripe-elemental-blog/tree/2.1.2) (2019-07-29) 32 | [Full Changelog](https://github.com/dynamic/silverstripe-elemental-blog/compare/1.0.2...2.1.2) 33 | 34 | **Merged pull requests:** 35 | 36 | - UPDATE repository updates [\#25](https://github.com/dynamic/silverstripe-elemental-blog/pull/25) ([muskie9](https://github.com/muskie9)) 37 | - Better handle edge cases where ElementBlogPosts targets a missing blog page [\#23](https://github.com/dynamic/silverstripe-elemental-blog/pull/23) ([maxime-rainville](https://github.com/maxime-rainville)) 38 | 39 | ## [1.0.2](https://github.com/dynamic/silverstripe-elemental-blog/tree/1.0.2) (2019-05-15) 40 | [Full Changelog](https://github.com/dynamic/silverstripe-elemental-blog/compare/2.1.1...1.0.2) 41 | 42 | **Fixed bugs:** 43 | 44 | - BUG check if blog query returns blog before additional queries/filters [\#21](https://github.com/dynamic/silverstripe-elemental-blog/issues/21) 45 | 46 | **Merged pull requests:** 47 | 48 | - BUGFIX ensure blog query returns blog before additional queries/filters [\#22](https://github.com/dynamic/silverstripe-elemental-blog/pull/22) ([muskie9](https://github.com/muskie9)) 49 | - Change ElementBlogPosts $icon static [\#20](https://github.com/dynamic/silverstripe-elemental-blog/pull/20) ([LABCAT](https://github.com/LABCAT)) 50 | 51 | ## [2.1.1](https://github.com/dynamic/silverstripe-elemental-blog/tree/2.1.1) (2019-03-21) 52 | [Full Changelog](https://github.com/dynamic/silverstripe-elemental-blog/compare/2.1.0...2.1.1) 53 | 54 | **Closed issues:** 55 | 56 | - REQUIREMENTS remove @dev references for composer requirements [\#18](https://github.com/dynamic/silverstripe-elemental-blog/issues/18) 57 | 58 | **Merged pull requests:** 59 | 60 | - UPDATE requirements to not include @dev [\#19](https://github.com/dynamic/silverstripe-elemental-blog/pull/19) ([muskie9](https://github.com/muskie9)) 61 | - create lang file, update labels [\#17](https://github.com/dynamic/silverstripe-elemental-blog/pull/17) ([jsirish](https://github.com/jsirish)) 62 | 63 | ## [2.1.0](https://github.com/dynamic/silverstripe-elemental-blog/tree/2.1.0) (2019-02-14) 64 | [Full Changelog](https://github.com/dynamic/silverstripe-elemental-blog/compare/2.0.1...2.1.0) 65 | 66 | **Merged pull requests:** 67 | 68 | - Removed implied recipe dependency [\#16](https://github.com/dynamic/silverstripe-elemental-blog/pull/16) ([chillu](https://github.com/chillu)) 69 | 70 | ## [2.0.1](https://github.com/dynamic/silverstripe-elemental-blog/tree/2.0.1) (2019-01-18) 71 | [Full Changelog](https://github.com/dynamic/silverstripe-elemental-blog/compare/2.0.0...2.0.1) 72 | 73 | **Fixed bugs:** 74 | 75 | - disable validation in favor of fallback [\#15](https://github.com/dynamic/silverstripe-elemental-blog/pull/15) ([jsirish](https://github.com/jsirish)) 76 | 77 | ## [2.0.0](https://github.com/dynamic/silverstripe-elemental-blog/tree/2.0.0) (2019-01-17) 78 | [Full Changelog](https://github.com/dynamic/silverstripe-elemental-blog/compare/1.0.1...2.0.0) 79 | 80 | **Merged pull requests:** 81 | 82 | - update branch alias, composer requirements, readme [\#14](https://github.com/dynamic/silverstripe-elemental-blog/pull/14) ([jsirish](https://github.com/jsirish)) 83 | - remove branch alias, update requirements [\#13](https://github.com/dynamic/silverstripe-elemental-blog/pull/13) ([jsirish](https://github.com/jsirish)) 84 | - Update for Elemental 4.0.0 [\#12](https://github.com/dynamic/silverstripe-elemental-blog/pull/12) ([obj63mc](https://github.com/obj63mc)) 85 | 86 | ## [1.0.1](https://github.com/dynamic/silverstripe-elemental-blog/tree/1.0.1) (2018-08-28) 87 | [Full Changelog](https://github.com/dynamic/silverstripe-elemental-blog/compare/1.0.0...1.0.1) 88 | 89 | **Merged pull requests:** 90 | 91 | - elemental requirements [\#11](https://github.com/dynamic/silverstripe-elemental-blog/pull/11) ([jsirish](https://github.com/jsirish)) 92 | 93 | ## [1.0.0](https://github.com/dynamic/silverstripe-elemental-blog/tree/1.0.0) (2018-07-30) 94 | **Implemented enhancements:** 95 | 96 | - ENHANCEMENT Category filter option [\#9](https://github.com/dynamic/silverstripe-elemental-blog/issues/9) 97 | - Getter for pulling content [\#2](https://github.com/dynamic/silverstripe-elemental-blog/issues/2) 98 | 99 | **Fixed bugs:** 100 | 101 | - Getter for pulling content [\#2](https://github.com/dynamic/silverstripe-elemental-blog/issues/2) 102 | 103 | **Closed issues:** 104 | 105 | - template needs to be namespaced [\#6](https://github.com/dynamic/silverstripe-elemental-blog/issues/6) 106 | 107 | **Merged pull requests:** 108 | 109 | - Added ability to have a specific category show [\#10](https://github.com/dynamic/silverstripe-elemental-blog/pull/10) ([mak001](https://github.com/mak001)) 110 | - Updated namespacing to reflect other modules [\#8](https://github.com/dynamic/silverstripe-elemental-blog/pull/8) ([mak001](https://github.com/mak001)) 111 | - composer - update requirements to @dev [\#5](https://github.com/dynamic/silverstripe-elemental-blog/pull/5) ([jsirish](https://github.com/jsirish)) 112 | - README - update badges [\#4](https://github.com/dynamic/silverstripe-elemental-blog/pull/4) ([jsirish](https://github.com/jsirish)) 113 | - bugfix - getPostsList\(\) - check if BlogID is set [\#3](https://github.com/dynamic/silverstripe-elemental-blog/pull/3) ([jsirish](https://github.com/jsirish)) 114 | - rename module to silverstripe-elemental-blog [\#1](https://github.com/dynamic/silverstripe-elemental-blog/pull/1) ([jsirish](https://github.com/jsirish)) 115 | 116 | 117 | 118 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* 119 | -------------------------------------------------------------------------------- /src/Elements/ElementBlogOverview.php: -------------------------------------------------------------------------------- 1 | 'HTMLText', 29 | 'ShowPagination' => 'Boolean(0)', 30 | ]; 31 | 32 | private static string $icon = 'font-icon-p-articles'; 33 | 34 | private static string $table_name = 'ElementBlogOverview'; 35 | 36 | private static string $singular_name = 'Blog Overview'; 37 | 38 | private static string $plural_name = 'Blog Overview Blocks'; 39 | 40 | private static string $description = 'Block displaying Blog Posts with pagination'; 41 | 42 | /** 43 | * We use this default_title for the Block name in the CMS. Feel free to update it via config 44 | */ 45 | private static string $default_title = 'Blog Overview'; 46 | 47 | /** 48 | * The main purpose of this Block is to replace the standard Blog Layout functionality (including pagination, 49 | * filters, and widgets). In order to do that out of the box, this Block must be used on a Blog page (as it is the 50 | * Blog page and BlogController that provides the feedback we require) 51 | * 52 | * By default, this Block will return null in all the areas where it expects to find a Blog/BlogController if it 53 | * does not. However, if you enable this Block to be used elsewhere, there are extension points/etc available for 54 | * you to return/update the DataList/Paginated list in other ways 55 | */ 56 | private static bool $allow_use_outside_of_blog = false; 57 | 58 | /** 59 | * Depending on your config, there is potentially no reason for a content author to enter this Block to make any 60 | * edits, if that is the case for you, then it likely makes sense that you just set a title for them by default 61 | */ 62 | private static bool $set_default_title = false; 63 | 64 | /** 65 | * You can set this to false if you would prefer that we do not display the default Title field that Elemental 66 | * provides to users 67 | */ 68 | private static bool $show_title_field = true; 69 | 70 | /** 71 | * You can set this to false if you would prefer that we do not display the HTMLEditor/$Content field to users 72 | */ 73 | private static bool $show_content_field = true; 74 | 75 | /** 76 | * By default, we show the "Show Pagination" field in the CMS (since it is part of the default supported features). 77 | * You may, however, prefer that content authors display pagination for the Blog using the specific 78 | * `PaginationBlock`, and if that's the case, you'll likely want to set this to false via config, as it will no 79 | * longer be relevant for your content authors 80 | */ 81 | private static bool $show_pagination_field = true; 82 | 83 | /** 84 | * Default value for ShowPagination. If set, this block will also output the pagination for your Blog. You can 85 | * update this value via config 86 | */ 87 | private static int $pagination_field_default = 1; 88 | 89 | /** 90 | * This can be updated via config if (for whatever reason) you do not wish to show this message field in the CMS 91 | */ 92 | private static bool $show_info_message_field = true; 93 | 94 | /** 95 | * Default value used for the message field in the CMS 96 | */ 97 | private static string $info_message_field_default = 'This block automatically displays Blog Posts and pagination'; 98 | 99 | /** 100 | * Cached value for BlogPosts from the Blog page 101 | * 102 | * @var DataList|BlogPost[]|null 103 | */ 104 | private $blogPosts; 105 | 106 | /** 107 | * Cached value for the PaginatedList from BlogController 108 | * 109 | * @var PaginatedList|null 110 | */ 111 | private $paginatedList; 112 | 113 | /** 114 | * Cached value for our CacheKey. It's not all that cheap to generate it, so, we should only do it once per 115 | * request 116 | * 117 | * @var string|null 118 | */ 119 | private $cacheKey; 120 | 121 | /** 122 | * @codeCoverageIgnore 123 | * @return FieldList 124 | */ 125 | public function getCMSFields(): FieldList 126 | { 127 | $fields = parent::getCMSFields(); 128 | 129 | // Removing scaffold fields so that they can be added more explicitly (and allowing for update via extension 130 | // points) 131 | $fields->removeByName([ 132 | 'Content', 133 | 'ShowPagination', 134 | ]); 135 | 136 | // Check whether we want to display the default Title field 137 | if (!static::config()->get('show_content_field')) { 138 | $fields->removeByName('Title'); 139 | } 140 | 141 | // Check whether we want to display our Content WYSIWYG field 142 | if (static::config()->get('show_content_field')) { 143 | $contentField = HTMLEditorField::create('Content'); 144 | 145 | // An opportunity for you to update this field before it is added (EG: you might want to add a description) 146 | $this->invokeWithExtensions('updateContentField', $contentField); 147 | 148 | $fields->addFieldToTab( 149 | 'Root.Main', 150 | $contentField 151 | ); 152 | } 153 | 154 | // Check whether we want to allow the author to determine whether or not the Block outputs with pagination 155 | if (static::config()->get('show_pagination_field')) { 156 | $showPaginationField = CheckboxField::create('ShowPagination'); 157 | 158 | // An opportunity for you to update this field before it is added (EG: you might want to add a description) 159 | $this->invokeWithExtensions('updateShowPaginationField', $showPaginationField); 160 | 161 | $fields->addFieldToTab( 162 | 'Root.Main', 163 | $showPaginationField 164 | ); 165 | } 166 | 167 | // Check whether want to display this message 168 | if (static::config()->get('show_info_message_field')) { 169 | $messageField = LiteralField::create( 170 | 'BlockInfoMessage', 171 | sprintf( 172 | '

%s

', 173 | static::config()->get('info_message_field_default') 174 | ) 175 | ); 176 | 177 | // An opportunity for you to update this field before it is added 178 | $this->invokeWithExtensions('updateMessageField', $messageField); 179 | 180 | $fields->addFieldToTab( 181 | 'Root.Main', 182 | $messageField 183 | ); 184 | } 185 | 186 | return $fields; 187 | } 188 | 189 | public function populateDefaults(): void 190 | { 191 | parent::populateDefaults(); 192 | 193 | // Set the Title by default, if you have specified for us to 194 | if (static::config()->get('set_default_title')) { 195 | $this->Title = static::config()->get('default_title'); 196 | } 197 | 198 | // Always set the default for these fields 199 | $this->ShowPagination = static::config()->get('pagination_field_default'); 200 | } 201 | 202 | /** 203 | * We'll allow the passing of a Controller for a couple of reasons: 204 | * 1) Provides a new way for you to dictate how a PaginatedList might be provided 205 | * 2) Makes testing much easier... 206 | * 207 | * @param Controller|null $controller 208 | * @return PaginatedList|null 209 | * @throws ValidationException 210 | */ 211 | public function getPaginatedList(?Controller $controller = null): ?PaginatedList 212 | { 213 | // Return our cached value, if one exists 214 | if ($this->paginatedList !== null) { 215 | return $this->paginatedList; 216 | } 217 | 218 | if ($controller === null) { 219 | /** @var BlogController $controller */ 220 | $controller = Controller::curr(); 221 | } 222 | 223 | // Ideally, we want to fetch the PaginatedList from the BlogController, but, if this Block is not being used on 224 | // a Blog page, then that will not be (immediately) possible. You have three options: 225 | // 1) You can implement a method `PaginatedList` on your Controller, and provide it with the appropriate data 226 | // 2) You can use the `updatePaginatedList` extension point to apply your filters/limits to the PaginatedList 227 | // that will be provided to it 228 | // 3) Maybe you should be using ElementBlogPosts Block instead 229 | 230 | // The active Controller is what we expect, so we can simply return the PaginatedList from there. Early exit for 231 | // increased readability 232 | if ($controller instanceof BlogController) { 233 | // Store this PaginatedList as our cached value and provide an extension point 234 | $this->setPaginatedList($controller->PaginatedList()); 235 | 236 | return $this->paginatedList; 237 | } 238 | 239 | // You have specified that this Block *cannot* be used outside of the Blog, so that also means that we 240 | // expect a Controller to have been present 241 | if (!static::config()->get('allow_use_outside_of_blog')) { 242 | return null; 243 | } 244 | 245 | if ($controller !== null && $controller->hasMethod('PaginatedList')) { 246 | // If you know that you're going to be using this Block on another Page type, then you can implement the 247 | // getBlogPostPaginatedList() method there, and we'll use that 248 | $paginatedList = $controller->PaginatedList(); 249 | } else { 250 | // Since you have specified that this Block *can* be used outside of the Blog, we'll create a PaginatedList 251 | // containing all BlogPosts in the DB, and you can then manipulate that List as you wish through the 252 | // extension point 253 | $paginatedList = PaginatedList::create($this->getBlogPosts()); 254 | } 255 | 256 | // Store this PaginatedList as our cached value and provide an extension point 257 | $this->setPaginatedList($paginatedList); 258 | 259 | return $this->paginatedList; 260 | } 261 | 262 | /** 263 | * @return DataList 264 | * @throws ValidationException 265 | */ 266 | public function getBlogPosts(): ?DataList 267 | { 268 | // Return our cached value, if one exists 269 | if ($this->blogPosts !== null) { 270 | return $this->blogPosts; 271 | } 272 | 273 | /** @var Blog $page */ 274 | $page = $this->getPage(); 275 | 276 | // Ideally, we want to fetch the BlogPosts that were specifically posted under a Parent Blog page, but, if this 277 | // Block is not being used on a Blog page, then that is not possible. Instead, we will just return all Blog 278 | // Posts in the DB. You can then update this DataList (maybe with additional filters/limits/etc) by using the 279 | // `updateBlogPosts` extension point 280 | if ($page instanceof Blog) { 281 | // Store this DataList as our cached value and provide an extension point 282 | $this->setBlogPosts($page->getBlogPosts()); 283 | 284 | return $this->blogPosts; 285 | } 286 | 287 | // You have specified that this Block *cannot* be used outside of the Blog, so that also means that we 288 | // expect a parent Page to have been present and of type Blog 289 | if (!static::config()->get('allow_use_outside_of_blog')) { 290 | return null; 291 | } 292 | 293 | if ($page !== null && $page->hasMethod('getBlogPosts')) { 294 | // If you know that you're going to be using this Block on another Page type, then you can implement the 295 | // getBlogPosts() method there, and we'll use that 296 | $blogPosts = $page->getBlogPosts(); 297 | } else { 298 | // Since you have specified that this Block *can* be used outside of the Blog, we'll create a DataList 299 | // containing all BlogPosts in the DB, and you can then manipulate that List as you wish through the 300 | // extension point 301 | $blogPosts = BlogPost::get(); 302 | } 303 | 304 | // Store this DataList as our cached value and provide an extension point 305 | $this->setBlogPosts($blogPosts); 306 | 307 | return $this->blogPosts; 308 | } 309 | 310 | /** 311 | * @param PaginatedList|null $paginatedList 312 | */ 313 | protected function setPaginatedList(?PaginatedList $paginatedList): void 314 | { 315 | // Provided is an opportunity for you to update this PaginatedList 316 | $this->invokeWithExtensions('updatePaginatedList', $paginatedList); 317 | 318 | // Store this List as our cached value 319 | $this->paginatedList = $paginatedList; 320 | } 321 | 322 | /** 323 | * @param DataList|null $blogPosts 324 | */ 325 | protected function setBlogPosts(?DataList $blogPosts): void 326 | { 327 | // Provided is an opportunity for you to update this DataList 328 | $this->invokeWithExtensions('updateBlogPosts', $blogPosts); 329 | 330 | // Store this DataList as our cached value 331 | $this->blogPosts = $blogPosts; 332 | } 333 | } 334 | --------------------------------------------------------------------------------