├── resources └── img │ └── blocky.png ├── phpstan.neon ├── renovate.json ├── .github ├── linters │ └── .markdown-lint.yml ├── workflows │ ├── linter.yml │ ├── create-release.yml │ └── php-review.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── config └── blocks.php ├── phpcs.xml.dist ├── .gitignore ├── src ├── exceptions │ ├── InvalidBlockException.php │ └── BlockTransformerNotFoundException.php ├── BlockInterface.php ├── variables │ └── BlockParserVariable.php ├── twig │ ├── BlocksTwigExtension.php │ ├── nodes │ │ ├── BlocksLoopNode.php │ │ └── BlocksNode.php │ └── tokenparsers │ │ └── BlocksTokenParser.php ├── Block.php ├── Blocky.php ├── BlockParser.php └── icon.svg ├── LICENSE ├── composer.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md └── README.md /resources/img/blocky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wrux/blocky/HEAD/resources/img/blocky.png -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | paths: 3 | - src 4 | bootstrapFiles: 5 | - vendor/craftcms/cms/bootstrap/console.php 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.github/linters/.markdown-lint.yml: -------------------------------------------------------------------------------- 1 | # Linter rules doc: 2 | # - https://github.com/DavidAnson/markdownlint 3 | # 4 | 5 | MD024: false # Changelog contains multiple Added and Changed headings. 6 | -------------------------------------------------------------------------------- /config/blocks.php: -------------------------------------------------------------------------------- 1 | 'app\blocks\HeadingBlock', 13 | * 'wysiwyg' => 'app\blocks\TextBlock', 14 | * ]; 15 | * ``` 16 | * 17 | */ 18 | 19 | return []; 20 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | PSR2 with 2 spaces indentation. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # CRAFT ENVIRONMENT 2 | .env.php 3 | .env.sh 4 | .env 5 | 6 | # COMPOSER 7 | /vendor 8 | 9 | # BUILD FILES 10 | /bower_components/* 11 | /node_modules/* 12 | /build/* 13 | /yarn-error.log 14 | 15 | # MISC FILES 16 | .cache 17 | .DS_Store 18 | .idea 19 | .project 20 | .settings 21 | *.esproj 22 | *.sublime-workspace 23 | *.sublime-project 24 | *.tmproj 25 | *.tmproject 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | config.codekit3 32 | prepros-6.config 33 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: Lint Code Base 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'master' 7 | - 'main' 8 | jobs: 9 | build: 10 | name: Lint Code Base 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Code 14 | uses: actions/checkout@v3 15 | - name: Lint Code Base 16 | uses: docker://github/super-linter:v4.10.1 17 | env: 18 | DEFAULT_BRANCH: 'main' 19 | VALIDATE_ALL_CODEBASE: false 20 | VALIDATE_ANSIBLE: false 21 | VALIDATE_MD: true 22 | -------------------------------------------------------------------------------- /src/exceptions/InvalidBlockException.php: -------------------------------------------------------------------------------- 1 | parseBlocks($blocks); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/twig/BlocksTwigExtension.php: -------------------------------------------------------------------------------- 1 | true], $lineno, $tag); 31 | } 32 | 33 | /** 34 | * Compile the loop node context. 35 | * 36 | * @param Compiler $compiler 37 | */ 38 | public function compile(Compiler $compiler): void 39 | { 40 | $compiler 41 | ->write("++\$context['loop']['index0'];\n") 42 | ->write("++\$context['loop']['index'];\n") 43 | ->write("\$context['loop']['first'] = false;\n") 44 | ->write("if (isset(\$context['loop']['length'])) {\n") 45 | ->indent() 46 | ->write("--\$context['loop']['revindex0'];\n") 47 | ->write("--\$context['loop']['revindex'];\n") 48 | ->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n") 49 | ->outdent() 50 | ->write("}\n"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wrux/blocky", 3 | "description": "Map a matrix field into an array of blocks to render in twig.", 4 | "type": "craft-plugin", 5 | "version": "1.1.2", 6 | "keywords": ["craft", "cms", "craftcms", "craft-plugin", "block parser"], 7 | "support": { 8 | "docs": "https://github.com/wrux/blocky/blob/master/README.md", 9 | "issues": "https://github.com/wrux/blocky/issues" 10 | }, 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Callum Bonnyman", 15 | "homepage": "https://bloke.blog" 16 | } 17 | ], 18 | "scripts": { 19 | "phpstan": "vendor/bin/phpstan analyse ./src --level=5" 20 | }, 21 | "require": { 22 | "craftcms/cms": "^4.0.0" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "wrux\\blocky\\": "src/" 27 | } 28 | }, 29 | "extra": { 30 | "name": "Blocky", 31 | "handle": "blocky", 32 | "developer": "Callum Bonnyman", 33 | "developerUrl": "https://bloke.blog", 34 | "documentationUrl": "https://github.com/wrux/blocky/blob/master/README.md", 35 | "changelogUrl": "https://raw.githubusercontent.com/wrux/blocky/master/CHANGELOG.md", 36 | "class": "wrux\\blocky\\Blocky" 37 | }, 38 | "require-dev": { 39 | "phpstan/phpstan": "^1.0.0" 40 | }, 41 | "config": { 42 | "platform": { 43 | "php": "8.0.2" 44 | }, 45 | "allow-plugins": { 46 | "yiisoft/yii2-composer": true, 47 | "craftcms/plugin-installer": true 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Block.php: -------------------------------------------------------------------------------- 1 | block = $block; 44 | } 45 | 46 | // Public Methods 47 | // =========================================================================== 48 | 49 | /** 50 | * Returns the Block type handle. 51 | * 52 | * @return string 53 | * Name of the block as defined in Craft. 54 | */ 55 | public function getType(): string 56 | { 57 | return $this->block->type->handle; 58 | } 59 | 60 | /** 61 | * Returns the Block template. 62 | * 63 | * @return string 64 | * Name of the block as defined in Craft. 65 | */ 66 | public function getTemplate(): string 67 | { 68 | return $this->blockTemplate; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Blocky Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) and this 6 | project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## 1.0.1 - 2020-06-22 9 | 10 | ## Added 11 | 12 | - Superlinter with Markdown support 13 | 14 | ## Updated 15 | 16 | - Set minimum PHP version to 7.4.0. 17 | 18 | ## 1.0.0 - 2020-06-16 19 | 20 | ### Added 21 | 22 | - Skip empty blocks in the blocks tag using the `skip empty` keyword 23 | - Variables `context`, `template`, `type` are accessible inside the blocks tag 24 | - PHPCS PSR2 rules 25 | - PHPStan rules 26 | - Github actions to test PHPCS and PHPStan 27 | 28 | ## 0.1.2 - 2020-07-07 29 | 30 | ### Changed 31 | 32 | - Fixed `block.template` not accessible in the template because Twig was trying 33 | to access the protected `block.template` property instead of 34 | `block.getTemplate()` 35 | - Fixed wrong order in CHANGELOG.md 36 | 37 | ## 0.1.1 - 2020-07-07 38 | 39 | ### Changed 40 | 41 | - Typo in class name `InvalicBlockException` 42 | - The class Blocky is not correctly importing the exception classes 43 | 44 | ### Removed 45 | 46 | - Translation files, as these only contained exception translations 47 | - All instances of `Craft::t()` in exceptions 48 | 49 | ## 0.1.0 - 2020-06-05 50 | 51 | ### Added 52 | 53 | - Added `{% blocks %}` Twig tag to simplify templating 54 | 55 | ### Changed 56 | 57 | - Moved the main block parser functionality so that it is now available at 58 | `Blocky::$plugin->parseBlocks()` 59 | - This is consumed by the new `{% blocks %}` Twig tag and 60 | `{% craft.blocky.blockparser() %}` 61 | 62 | ## 0.0.2 - 2020-06-03 63 | 64 | ### Added 65 | 66 | - Cleaned up plugin code and fixed some code style issues 67 | 68 | ## 0.0.1 - 2020-06-03 69 | 70 | ### Added 71 | 72 | - Initial release 73 | -------------------------------------------------------------------------------- /src/twig/tokenparsers/BlocksTokenParser.php: -------------------------------------------------------------------------------- 1 | getLine(); 34 | $stream = $this->parser->getStream(); 35 | $stream->expect(Token::OPERATOR_TYPE, 'in'); 36 | $blocks = $this->parser->getExpressionParser()->parseExpression(); 37 | 38 | $skip_empty = false; 39 | if ($stream->nextIf(Token::NAME_TYPE, 'skip')) { 40 | $stream->expect(Token::NAME_TYPE, 'empty'); 41 | $skip_empty = true; 42 | } 43 | 44 | if ($stream->nextIf(Token::BLOCK_END_TYPE)) { 45 | $body = $this->parser->subparse([$this, 'decideBlocksEnd'], true); 46 | if ($token = $stream->nextIf(Token::NAME_TYPE)) { 47 | $value = $token->getValue(); 48 | } 49 | } else { 50 | $body = new Node( 51 | [ 52 | new PrintNode( 53 | $this->parser->getExpressionParser()->parseExpression(), 54 | $lineno 55 | ), 56 | ] 57 | ); 58 | } 59 | $stream->expect(Token::BLOCK_END_TYPE); 60 | 61 | return new BlocksNode($blocks, $body, $skip_empty, $lineno, $this->getTag()); 62 | } 63 | 64 | /** 65 | * @inheritDoc 66 | */ 67 | public function getTag(): string 68 | { 69 | return 'blocks'; 70 | } 71 | 72 | /** 73 | * Checks for the blocks closing tag. 74 | * 75 | * @param \Twig\Token $token 76 | * Twig token. 77 | * 78 | * @return bool 79 | * True if `{% endblocks %}` is found. 80 | */ 81 | public function decideBlocksEnd(Token $token): bool 82 | { 83 | return $token->test('endblocks'); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Blocky.php: -------------------------------------------------------------------------------- 1 | request->getIsSiteRequest()) { 47 | return; 48 | } 49 | 50 | // Register the blocky variable. 51 | Event::on( 52 | CraftVariable::class, 53 | CraftVariable::EVENT_INIT, 54 | function (Event $event) { 55 | /** @var CraftVariable $variable */ 56 | $variable = $event->sender; 57 | $variable->set('blocky', BlockParserVariable::class); 58 | } 59 | ); 60 | 61 | // Add the `blocks` Twig tag. 62 | Craft::$app->view->registerTwigExtension(new BlocksTwigExtension()); 63 | } 64 | 65 | /** 66 | * Parse the Matrix blocks array. 67 | * 68 | * @param array|MatrixBlockQuery $blocks 69 | * 70 | * @return mixed 71 | * Iterable block object. 72 | */ 73 | public function parseBlocks($blocks) 74 | { 75 | // If the matrix block was not eagar loaded then execute the query. 76 | if ($blocks instanceof MatrixBlockQuery) { 77 | $blocks = $blocks->all(); 78 | } 79 | if (!$blocks || count($blocks) === 0) { 80 | return []; 81 | } 82 | $block_parser = new BlockParser; 83 | foreach ($blocks as $block) { 84 | if (!$block instanceof Element) { 85 | continue; 86 | } 87 | try { 88 | $block_parser->addBlock($block); 89 | } catch (BlockTransformerNotFoundException $e) { 90 | // Block found, but with no corresponding parser class. 91 | Craft::warning( 92 | sprintf('Block not found: %s', $e->getMessage()), 93 | __METHOD__ 94 | ); 95 | continue; 96 | } catch (\Exception $e) { 97 | Craft::error( 98 | sprintf('Block parser error: %s', $e->getMessage()), 99 | __METHOD__ 100 | ); 101 | return []; 102 | } 103 | } 104 | return $block_parser; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at callum@banbury.town. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/twig/nodes/BlocksNode.php: -------------------------------------------------------------------------------- 1 | loop = new BlocksLoopNode($lineno, $tag); 44 | $body = new Node([$body, $this->loop]); 45 | $nodes = [ 46 | 'body' => $body, 47 | 'blocks' => $blocks, 48 | ]; 49 | parent::__construct($nodes, ['skip_empty' => $skip_empty], $lineno, $tag); 50 | } 51 | 52 | /** 53 | * Compile the node. 54 | * 55 | * @param \Twig\Compiler $compiler 56 | * Twig compiler object. 57 | */ 58 | public function compile(Compiler $compiler): void 59 | { 60 | $compiler 61 | ->addDebugInfo($this) 62 | ->write("\$context['_parent'] = \$context;\n") 63 | ->write("\$context['_blocks'] = twig_ensure_traversable(") 64 | ->subcompile($this->getNode('blocks')) 65 | ->raw(");\n"); 66 | 67 | $compiler 68 | ->write("\$context['parsed_blocks'] = \wrux\blocky\Blocky::\$plugin->parseBlocks(\$context['_blocks']); "); 69 | 70 | $compiler 71 | ->write("\$context['loop'] = [\n") 72 | ->write(" 'parent' => \$context['_parent'],\n") 73 | ->write(" 'index0' => 0,\n") 74 | ->write(" 'index' => 1,\n") 75 | ->write(" 'first' => true,\n") 76 | ->write("];\n"); 77 | 78 | $compiler 79 | ->write("if (is_array(\$context['_blocks']) || ") 80 | ->write("(is_object(\$context['_blocks']) && \$context['_blocks'] ") 81 | ->write("instanceof \Countable)) {\n") 82 | ->indent() 83 | ->write("\$length = count(\$context['_blocks']);\n") 84 | ->write("\$context['loop']['revindex0'] = \$length - 1;\n") 85 | ->write("\$context['loop']['revindex'] = \$length;\n") 86 | ->write("\$context['loop']['length'] = \$length;\n") 87 | ->write("\$context['loop']['last'] = 1 === \$length;\n") 88 | ->outdent() 89 | ->write("}\n"); 90 | 91 | $compiler 92 | ->write("foreach (\$context['parsed_blocks'] as \$block) {\n") 93 | ->write("\$block_context = \$block->getContext();\n") 94 | ->indent(); 95 | 96 | // Skip blocks that return an empty context. 97 | if ($this->getAttribute('skip_empty')) { 98 | $compiler 99 | ->write("if (empty(\$block_context)) {\n") 100 | ->indent() 101 | ->write("continue;\n") 102 | ->outdent() 103 | ->write("}\n"); 104 | } 105 | 106 | $compiler 107 | ->write("\$context['block'] = \$block;\n") 108 | ->write("\$context['type'] = \$block->getType();\n") 109 | ->write("\$context['template'] = \$block->getTemplate();\n") 110 | ->write("\$context['context'] = \$block_context;\n") 111 | ->subcompile($this->getNode('body'), false) 112 | ->outdent() 113 | ->write("}\n\n"); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/BlockParser.php: -------------------------------------------------------------------------------- 1 | config = Craft::$app->config->getConfigFromFile('blocks'); 49 | } 50 | 51 | /** 52 | * Returns the instansiated blocks. 53 | * 54 | * When looping over the result of `craft.blocks.parseBlocks` this method will 55 | * get called and return the blocks. 56 | * 57 | * In the twig template you can use the following example: 58 | * ``` 59 | * {% for block in blocks %} 60 | *
61 | * {% include template ignore missing with context only %} 62 | *
63 | * {% endfor %} 64 | * ``` 65 | * 66 | * @return ArrayIterator 67 | */ 68 | public function getIterator(): ArrayIterator 69 | { 70 | return new ArrayIterator($this->blocks); 71 | } 72 | 73 | /** 74 | * Adds a block to the parser. 75 | * 76 | * @param \craft\base\Element $block Matrix block. 77 | * 78 | * @throws \wrux\blocky\exceptions\BlockTransformerNotFoundException 79 | * If no corresponding block parser class is found. 80 | */ 81 | public function addBlock(Element $block): void 82 | { 83 | $block_class = $this->getBlockClass($block->type->handle); 84 | if (!$block_class || !class_exists($block_class)) { 85 | throw new BlockTransformerNotFoundException( 86 | sprintf('The block %s could not be found', $block_class) 87 | ); 88 | } 89 | $reflect = new \ReflectionClass($block_class); 90 | if (!$reflect->implementsInterface(BlockInterface::class)) { 91 | throw new InvalidBlockException( 92 | sprintf( 93 | 'The block class %s does not implement BlockInterface', 94 | $block_class 95 | ) 96 | ); 97 | } 98 | $this->blocks[] = new $block_class($block); 99 | } 100 | 101 | /** 102 | * Returns weather the block parser contains any blocks. 103 | * 104 | * @return bool 105 | */ 106 | public function hasBlocks(): bool 107 | { 108 | return count($this->blocks) > 0; 109 | } 110 | 111 | // Private Methods 112 | // ========================================================================= 113 | 114 | /** 115 | * Returns the block class reference from the configuration. 116 | * 117 | * @param string $handle The Matrix block handle. 118 | * 119 | * @throws \wrux\blocky\exceptions\BlockTransformerNotFoundException 120 | * If no corresponding block parser is found in the config 121 | * @return string 122 | * Block class namespace. 123 | */ 124 | private function getBlockClass(string $handle): string 125 | { 126 | if (empty($this->config[$handle])) { 127 | throw new BlockTransformerNotFoundException( 128 | sprintf( 129 | 'The block handle %s could not be found in the config', 130 | $handle 131 | ) 132 | ); 133 | } 134 | return $this->config[$handle]; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Craft Class Variance Authority 2 | 3 | # Blocky Plugin for Craft CMS 3.x 4 | 5 | Utility plugin for Craft CMS to map Matrix fields. 6 | 7 | Blocky handles the logic of parsing your Matrix blocks so you can create cleaner 8 | Twig templates. 9 | 10 | ## Requirements 11 | 12 | This plugin requires Craft CMS 3.0.0 or later. 13 | 14 | ## Installation 15 | 16 | To install the plugin, follow these instructions. 17 | 18 | 1. Open your terminal and go to your Craft project: 19 | 20 | ```bash 21 | cd /path/to/project 22 | ``` 23 | 24 | 2. Then tell Composer to load the plugin: 25 | 26 | ```bash 27 | composer require wrux/blocky 28 | ``` 29 | 30 | 3. In the Control Panel, go to Settings → Plugins and click the “Install” button 31 | for Blocky. 32 | 33 | ## Configuring the Block Parser 34 | 35 | 1. Create `config/blocks.php` inside your Craft project: 36 | 37 | ```php 38 | 'app\blocks\TextBlock', 42 | ]; 43 | ``` 44 | 45 | 2. Somewhere in your project, create block classes for each Matrix block which 46 | extends `wrux\blocky\Block` 47 | 48 | Here's an example block: 49 | 50 | ```php 51 | !empty($this->block->contentHtml) 64 | ? $this->block->contentHtml->getParsedContent() 65 | : NULL, 66 | ]; 67 | } 68 | } 69 | ``` 70 | 71 | ## Templating 72 | 73 | Blocky is available at `craft.blocky` in the template or you can also use the 74 | `{% blocks ... %}` Twig tag. 75 | 76 | ### Twig Tag 77 | 78 | The `{% blocks %}` tag works similarly to a Twig for loop. It expects a Matrix 79 | field and it will handle the parsing and iteration. 80 | 81 | **Example:** 82 | 83 | ```twig 84 | {% blocks in entry.blockComponents %} 85 |
86 | {% include template with context only %} 87 |
88 | {% endblocks %} 89 | ``` 90 | 91 | **Example with skipping empty blocks:** 92 | 93 | You can use `skip empty` in the opening tag. This will skip blocks that return 94 | an empty context. 95 | 96 | ```twig 97 | {% blocks in entry.blockComponents skip empty %} 98 |
99 | {% include template with context only %} 100 |
101 | {% endblocks %} 102 | ``` 103 | 104 | ### Variables 105 | 106 | The following variables are available inside the `{% blocks %}` tag. 107 | 108 | | Variable | Value | 109 | | -------------- | ------------------------------------------------------------- | 110 | | block | The block object | 111 | | template | The data returned from the `getTemplate()` method | 112 | | type | The value returned from the `getType()` method | 113 | | context | The context returned from the `getContext()` method | 114 | | loop.index | The current iteration of the loop. (1 indexed) | 115 | | loop.index0 | The current iteration of the loop. (0 indexed) | 116 | | loop.revindex | The number of iterations from the end of the loop (1 indexed) | 117 | | loop.revindex0 | The number of iterations from the end of the loop (0 indexed) | 118 | | loop.first | True if first iteration | 119 | | loop.last | True if last iteration | 120 | | loop.length | The number of items in the sequence | 121 | 122 | ### Manually Parsing Blocks 123 | 124 | If you don't want to use the Twig tag, blocks can be parsed manually using 125 | `craft.blocky` service. Internally this consumes the same 126 | `Blocky::$plugin->parseBlocks()` method. This method allows you to check 127 | `blocks.hasBlocks` before the for loop. 128 | 129 | **Example:** 130 | 131 | ```twig 132 | {% set blocks = craft.blocky.parseBlocks(entry.blockComponents) %} 133 | {% if blocks.hasBlocks %} 134 |
135 | {% for block in blocks %} 136 |
137 | {% include block.template ignore missing with block.context only %} 138 |
139 | {% endfor %} 140 |
141 | {% endif %} 142 | ``` 143 | 144 | ## Block Parser Roadmap 145 | 146 | Some things to do, and ideas for potential features: 147 | 148 | - Testing 🔥 149 | - Nested blocks with the release of CraftCMS 4.0. 150 | 151 | Brought to you by [Callum Bonnyman](https://bloke.blog) 152 | -------------------------------------------------------------------------------- /src/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | --------------------------------------------------------------------------------