├── .github └── FUNDING.yml ├── vendor ├── symfony │ ├── filesystem │ │ ├── .gitattributes │ │ ├── README.md │ │ ├── Exception │ │ │ ├── InvalidArgumentException.php │ │ │ ├── ExceptionInterface.php │ │ │ ├── IOExceptionInterface.php │ │ │ ├── FileNotFoundException.php │ │ │ └── IOException.php │ │ ├── composer.json │ │ ├── LICENSE │ │ └── CHANGELOG.md │ └── polyfill-ctype │ │ ├── README.md │ │ ├── composer.json │ │ ├── LICENSE │ │ ├── bootstrap.php │ │ └── Ctype.php ├── autoload.php └── composer │ ├── autoload_classmap.php │ ├── autoload_namespaces.php │ ├── autoload_files.php │ ├── autoload_psr4.php │ ├── LICENSE │ ├── autoload_static.php │ ├── autoload_real.php │ ├── installed.json │ └── ClassLoader.php ├── admin └── pages │ └── presets.md ├── static-generator.yaml ├── composer.json ├── .gitignore ├── templates ├── forms │ └── fields │ │ └── preset_buttons │ │ └── preset_buttons.html.twig └── presets.html.twig ├── package.json ├── blueprints ├── default.yaml └── partials │ ├── generate.yaml │ ├── generate-options.yaml │ ├── options.yaml │ └── presets.yaml ├── classes ├── Config │ ├── ConfigInterface.php │ ├── SSEConfig.php │ ├── AbstractConfig.php │ └── Config.php ├── Data │ ├── DataInterface.php │ ├── AbstractData.php │ ├── TestData.php │ ├── CommandLineData.php │ └── SSEData.php ├── Timer.php ├── Collection │ ├── CollectionInterface.php │ ├── SSECollection.php │ ├── CommandLineCollection.php │ └── AbstractCollection.php ├── Source │ └── Source.php ├── Utilities.php ├── Assets.php ├── Collection.php └── FileStorage.php ├── LICENSE ├── pnpm-lock.yaml ├── cli ├── TestStaticDataCommand.php ├── ClearStaticDataCommand.php ├── GenerateStaticIndexCommand.php ├── GenerateStaticPageCommand.php └── CreatePresetCommand.php ├── CHANGELOG.md ├── languages.yaml ├── composer.lock ├── README.md └── blueprints.yaml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://olevik.me/services"] 2 | ko_fi: olevik 3 | -------------------------------------------------------------------------------- /vendor/symfony/filesystem/.gitattributes: -------------------------------------------------------------------------------- 1 | /Tests export-ignore 2 | /phpunit.xml.dist export-ignore 3 | /.gitignore export-ignore 4 | -------------------------------------------------------------------------------- /admin/pages/presets.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Static Generator Presets 3 | access: 4 | admin.configuration: true 5 | admin.super: true 6 | --- 7 | -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', 10 | ); 11 | -------------------------------------------------------------------------------- /static-generator.yaml: -------------------------------------------------------------------------------- 1 | enabled: true 2 | index: "user://data/persist" 3 | content: "user://data/persist" 4 | content_max_length: 100000 5 | content_permissions: 6 | - admin.super 7 | - admin.maintenance 8 | admin: true 9 | js: true 10 | css: true 11 | quick_tray: true 12 | quick_tray_permissions: 13 | - admin.super 14 | - admin.maintenance 15 | presets: 16 | - name: default 17 | -------------------------------------------------------------------------------- /vendor/symfony/polyfill-ctype/README.md: -------------------------------------------------------------------------------- 1 | Symfony Polyfill / Ctype 2 | ======================== 3 | 4 | This component provides `ctype_*` functions to users who run php versions without the ctype extension. 5 | 6 | More information can be found in the 7 | [main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). 8 | 9 | License 10 | ======= 11 | 12 | This library is released under the [MIT license](LICENSE). 13 | -------------------------------------------------------------------------------- /vendor/composer/autoload_psr4.php: -------------------------------------------------------------------------------- 1 | array($vendorDir . '/symfony/polyfill-ctype'), 10 | 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), 11 | 'Grav\\Plugin\\StaticGenerator\\' => array($baseDir . '/classes'), 12 | 'Grav\\Framework\\Cache\\Adapter\\' => array($baseDir . '/classes'), 13 | ); 14 | -------------------------------------------------------------------------------- /vendor/symfony/filesystem/README.md: -------------------------------------------------------------------------------- 1 | Filesystem Component 2 | ==================== 3 | 4 | The Filesystem component provides basic utilities for the filesystem. 5 | 6 | Resources 7 | --------- 8 | 9 | * [Documentation](https://symfony.com/doc/current/components/filesystem.html) 10 | * [Contributing](https://symfony.com/doc/current/contributing/index.html) 11 | * [Report issues](https://github.com/symfony/symfony/issues) and 12 | [send Pull Requests](https://github.com/symfony/symfony/pulls) 13 | in the [main Symfony repository](https://github.com/symfony/symfony) 14 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "olevik/grav-plugin-static-generator", 3 | "description": "StaticGenerator generation of Page Index and Page(s) as HTML.", 4 | "type": "project", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Ole Vik", 9 | "email": "git@olevik.net" 10 | } 11 | ], 12 | "minimum-stability": "stable", 13 | "autoload": { 14 | "psr-4": { 15 | "Grav\\Plugin\\StaticGenerator\\": "classes/", 16 | "Grav\\Framework\\Cache\\Adapter\\": "classes/" 17 | } 18 | }, 19 | "require": { 20 | "symfony/filesystem": "^4.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /vendor/symfony/filesystem/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Filesystem\Exception; 13 | 14 | /** 15 | * @author Christian Flothmann 16 | */ 17 | class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /vendor/symfony/filesystem/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Filesystem\Exception; 13 | 14 | /** 15 | * Exception interface for all exceptions thrown by the component. 16 | * 17 | * @author Romain Neutron 18 | */ 19 | interface ExceptionInterface extends \Throwable 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Node ### 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Dependency directories 10 | node_modules/ 11 | !node_modules/eventsource/example/eventsource-polyfill.js 12 | 13 | # Sonarlint plugin 14 | .idea/sonarlint 15 | 16 | ### Sass ### 17 | .sass-cache/ 18 | *.css.map 19 | 20 | ### Windows ### 21 | # Windows thumbnail cache files 22 | Thumbs.db 23 | ehthumbs.db 24 | ehthumbs_vista.db 25 | 26 | # Folder config file 27 | Desktop.ini 28 | 29 | # Recycle Bin used on file shares 30 | $RECYCLE.BIN/ 31 | 32 | # Windows Installer files 33 | *.cab 34 | *.msi 35 | *.msm 36 | *.msp 37 | 38 | # Windows shortcuts 39 | *.lnk -------------------------------------------------------------------------------- /templates/forms/fields/preset_buttons/preset_buttons.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "forms/field.html.twig" %} 2 | 3 | {% block contents %} 4 |
5 | {%- if field.fields -%} 6 | {% for child in field.fields %} 7 | {% if authorize(config.plugins['static-generator'].content_permissions) %} 8 | {{ child.name|t }} 9 | {% endif %} 10 | {% endfor %} 11 | {%- endif -%} 12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grav-plugin-static-generator", 3 | "version": "1.0.0", 4 | "description": "Static generation of Page(s) and Index for Grav CMS.", 5 | "main": "js/site-generator.admin.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/OleVik/grav-plugin-static-generator.git" 12 | }, 13 | "author": "Ole Vik ", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/OleVik/grav-plugin-static-generator/issues" 17 | }, 18 | "homepage": "https://github.com/OleVik/grav-plugin-static-generator#readme", 19 | "dependencies": { 20 | "eventsource": "^1.0.7" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /vendor/symfony/filesystem/Exception/IOExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Filesystem\Exception; 13 | 14 | /** 15 | * IOException interface for file and input/output stream related exceptions thrown by the component. 16 | * 17 | * @author Christian Gärtner 18 | */ 19 | interface IOExceptionInterface extends ExceptionInterface 20 | { 21 | /** 22 | * Returns the associated path for the exception. 23 | * 24 | * @return string|null The path 25 | */ 26 | public function getPath(); 27 | } 28 | -------------------------------------------------------------------------------- /blueprints/default.yaml: -------------------------------------------------------------------------------- 1 | title: Static Generator Options 2 | extends@: 3 | type: default 4 | context: blueprints://pages 5 | 6 | form: 7 | fields: 8 | tabs: 9 | type: tabs 10 | fields: 11 | options: 12 | type: tab 13 | fields: 14 | static_generator: 15 | type: section 16 | title: PLUGIN_STATIC_GENERATOR.ADMIN.TITLE 17 | underline: true 18 | fields: 19 | header.search: 20 | type: select 21 | id: static-generator-search-files-select 22 | label: PLUGIN_STATIC_GENERATOR.ADMIN.SEARCH_FILE 23 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.SEARCH_FILE 24 | data-options@: 25 | ['\Grav\Plugin\StaticGenerator::getSearchFiles', "index"] 26 | -------------------------------------------------------------------------------- /vendor/symfony/filesystem/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony/filesystem", 3 | "type": "library", 4 | "description": "Symfony Filesystem Component", 5 | "keywords": [], 6 | "homepage": "https://symfony.com", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Fabien Potencier", 11 | "email": "fabien@symfony.com" 12 | }, 13 | { 14 | "name": "Symfony Community", 15 | "homepage": "https://symfony.com/contributors" 16 | } 17 | ], 18 | "require": { 19 | "php": "^7.1.3", 20 | "symfony/polyfill-ctype": "~1.8" 21 | }, 22 | "autoload": { 23 | "psr-4": { "Symfony\\Component\\Filesystem\\": "" }, 24 | "exclude-from-classmap": [ 25 | "/Tests/" 26 | ] 27 | }, 28 | "minimum-stability": "dev", 29 | "extra": { 30 | "branch-alias": { 31 | "dev-master": "4.4-dev" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /classes/Config/ConfigInterface.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/OleVik/grav-plugin-static-generator 14 | */ 15 | 16 | namespace Grav\Plugin\StaticGenerator\Config; 17 | 18 | use Grav\Common\Config\Config; 19 | 20 | /** 21 | * Config Interface 22 | * 23 | * @category API 24 | * @package Grav\Plugin\StaticGenerator\Config\ConfigInterface 25 | * @author Ole Vik 26 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 27 | * @link https://github.com/OleVik/grav-plugin-static-generator 28 | */ 29 | interface ConfigInterface 30 | { 31 | public function __construct(Config $config, string $path, string $name); 32 | } 33 | -------------------------------------------------------------------------------- /vendor/symfony/polyfill-ctype/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony/polyfill-ctype", 3 | "type": "library", 4 | "description": "Symfony polyfill for ctype functions", 5 | "keywords": ["polyfill", "compatibility", "portable", "ctype"], 6 | "homepage": "https://symfony.com", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Gert de Pagter", 11 | "email": "BackEndTea@gmail.com" 12 | }, 13 | { 14 | "name": "Symfony Community", 15 | "homepage": "https://symfony.com/contributors" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=5.3.3" 20 | }, 21 | "autoload": { 22 | "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, 23 | "files": [ "bootstrap.php" ] 24 | }, 25 | "suggest": { 26 | "ext-ctype": "For best performance" 27 | }, 28 | "minimum-stability": "dev", 29 | "extra": { 30 | "branch-alias": { 31 | "dev-master": "1.14-dev" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Ole Vik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/symfony/filesystem/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2004-2020 Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/symfony/polyfill-ctype/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2019 Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /vendor/symfony/filesystem/Exception/FileNotFoundException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Filesystem\Exception; 13 | 14 | /** 15 | * Exception class thrown when a file couldn't be found. 16 | * 17 | * @author Fabien Potencier 18 | * @author Christian Gärtner 19 | */ 20 | class FileNotFoundException extends IOException 21 | { 22 | public function __construct(string $message = null, int $code = 0, \Throwable $previous = null, string $path = null) 23 | { 24 | if (null === $message) { 25 | if (null === $path) { 26 | $message = 'File could not be found.'; 27 | } else { 28 | $message = sprintf('File "%s" could not be found.', $path); 29 | } 30 | } 31 | 32 | parent::__construct($message, $code, $previous, $path); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /vendor/symfony/filesystem/Exception/IOException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Filesystem\Exception; 13 | 14 | /** 15 | * Exception class thrown when a filesystem operation failure happens. 16 | * 17 | * @author Romain Neutron 18 | * @author Christian Gärtner 19 | * @author Fabien Potencier 20 | */ 21 | class IOException extends \RuntimeException implements IOExceptionInterface 22 | { 23 | private $path; 24 | 25 | public function __construct(string $message, int $code = 0, \Throwable $previous = null, string $path = null) 26 | { 27 | $this->path = $path; 28 | 29 | parent::__construct($message, $code, $previous); 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function getPath() 36 | { 37 | return $this->path; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | eventsource: 1.0.7 3 | lockfileVersion: 5.1 4 | packages: 5 | /eventsource/1.0.7: 6 | dependencies: 7 | original: 1.0.2 8 | dev: false 9 | engines: 10 | node: '>=0.12.0' 11 | resolution: 12 | integrity: sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== 13 | /original/1.0.2: 14 | dependencies: 15 | url-parse: 1.4.7 16 | dev: false 17 | resolution: 18 | integrity: sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== 19 | /querystringify/2.1.1: 20 | dev: false 21 | resolution: 22 | integrity: sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== 23 | /requires-port/1.0.0: 24 | dev: false 25 | resolution: 26 | integrity: sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= 27 | /url-parse/1.4.7: 28 | dependencies: 29 | querystringify: 2.1.1 30 | requires-port: 1.0.0 31 | dev: false 32 | resolution: 33 | integrity: sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== 34 | specifiers: 35 | eventsource: ^1.0.7 36 | -------------------------------------------------------------------------------- /vendor/symfony/polyfill-ctype/bootstrap.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Symfony\Polyfill\Ctype as p; 13 | 14 | if (!function_exists('ctype_alnum')) { 15 | function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } 16 | function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } 17 | function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } 18 | function ctype_digit($text) { return p\Ctype::ctype_digit($text); } 19 | function ctype_graph($text) { return p\Ctype::ctype_graph($text); } 20 | function ctype_lower($text) { return p\Ctype::ctype_lower($text); } 21 | function ctype_print($text) { return p\Ctype::ctype_print($text); } 22 | function ctype_punct($text) { return p\Ctype::ctype_punct($text); } 23 | function ctype_space($text) { return p\Ctype::ctype_space($text); } 24 | function ctype_upper($text) { return p\Ctype::ctype_upper($text); } 25 | function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } 26 | } 27 | -------------------------------------------------------------------------------- /templates/presets.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'partials/base.html.twig' %} 2 | 3 | {% set preset_slug = uri.basename|e %} 4 | 5 | {% block titlebar %} 6 |
7 | {{ "PLUGIN_ADMIN.BACK"|tu }} 8 | {% if data.file.filename %} 9 | 10 | {% endif %} 11 |
12 |

13 | 14 | {% if preset_slug != 'presets' %} 15 | {{ "PLUGIN_STATIC_GENERATOR.ADMIN.TITLE"|tu }} - {{ "PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.SINGULAR"|tu }} - {{ preset_slug|capitalize }} 16 | {% else %} 17 | {{ "PLUGIN_STATIC_GENERATOR.ADMIN.TITLE"|tu }} - {{ "PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.TITLE"|tu }} 18 | {% endif %} 19 |

20 | {% endblock %} 21 | 22 | {% block content_top %} 23 |
{{ "PLUGIN_ADMIN.SAVE_LOCATION"|tu }}: {{ data.file.filename|replace({(base_path):''}) }}
24 | {% endblock %} 25 | 26 | {% block content %} 27 | {% if preset_slug != 'presets' %} 28 | {% set blueprints = admin.blueprints('preset') %} 29 | {% else %} 30 | {% set blueprints = admin.blueprints('presets') %} 31 | {% endif %} 32 | 33 |
34 | {% include 'partials/blueprints.html.twig' with { blueprints: blueprints } %} 35 |
36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /vendor/symfony/filesystem/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | 4.4.0 5 | ----- 6 | 7 | * support for passing a `null` value to `Filesystem::isAbsolutePath()` is deprecated and will be removed in 5.0 8 | 9 | 4.3.0 10 | ----- 11 | 12 | * support for passing arrays to `Filesystem::dumpFile()` is deprecated and will be removed in 5.0 13 | * support for passing arrays to `Filesystem::appendToFile()` is deprecated and will be removed in 5.0 14 | 15 | 4.0.0 16 | ----- 17 | 18 | * removed `LockHandler` 19 | * Support for passing relative paths to `Filesystem::makePathRelative()` has been removed. 20 | 21 | 3.4.0 22 | ----- 23 | 24 | * support for passing relative paths to `Filesystem::makePathRelative()` is deprecated and will be removed in 4.0 25 | 26 | 3.3.0 27 | ----- 28 | 29 | * added `appendToFile()` to append contents to existing files 30 | 31 | 3.2.0 32 | ----- 33 | 34 | * added `readlink()` as a platform independent method to read links 35 | 36 | 3.0.0 37 | ----- 38 | 39 | * removed `$mode` argument from `Filesystem::dumpFile()` 40 | 41 | 2.8.0 42 | ----- 43 | 44 | * added tempnam() a stream aware version of PHP's native tempnam() 45 | 46 | 2.6.0 47 | ----- 48 | 49 | * added LockHandler 50 | 51 | 2.3.12 52 | ------ 53 | 54 | * deprecated dumpFile() file mode argument. 55 | 56 | 2.3.0 57 | ----- 58 | 59 | * added the dumpFile() method to atomically write files 60 | 61 | 2.2.0 62 | ----- 63 | 64 | * added a delete option for the mirror() method 65 | 66 | 2.1.0 67 | ----- 68 | 69 | * 24eb396 : BC Break : mkdir() function now throws exception in case of failure instead of returning Boolean value 70 | * created the component 71 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', 11 | ); 12 | 13 | public static $prefixLengthsPsr4 = array ( 14 | 'S' => 15 | array ( 16 | 'Symfony\\Polyfill\\Ctype\\' => 23, 17 | 'Symfony\\Component\\Filesystem\\' => 29, 18 | ), 19 | 'G' => 20 | array ( 21 | 'Grav\\Plugin\\StaticGenerator\\' => 28, 22 | 'Grav\\Framework\\Cache\\Adapter\\' => 29, 23 | ), 24 | ); 25 | 26 | public static $prefixDirsPsr4 = array ( 27 | 'Symfony\\Polyfill\\Ctype\\' => 28 | array ( 29 | 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', 30 | ), 31 | 'Symfony\\Component\\Filesystem\\' => 32 | array ( 33 | 0 => __DIR__ . '/..' . '/symfony/filesystem', 34 | ), 35 | 'Grav\\Plugin\\StaticGenerator\\' => 36 | array ( 37 | 0 => __DIR__ . '/../..' . '/classes', 38 | ), 39 | 'Grav\\Framework\\Cache\\Adapter\\' => 40 | array ( 41 | 0 => __DIR__ . '/../..' . '/classes', 42 | ), 43 | ); 44 | 45 | public static function getInitializer(ClassLoader $loader) 46 | { 47 | return \Closure::bind(function () use ($loader) { 48 | $loader->prefixLengthsPsr4 = ComposerStaticInite2f4a2b75e1df9085b6021d6d25a7526::$prefixLengthsPsr4; 49 | $loader->prefixDirsPsr4 = ComposerStaticInite2f4a2b75e1df9085b6021d6d25a7526::$prefixDirsPsr4; 50 | 51 | }, null, ClassLoader::class); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /blueprints/partials/generate.yaml: -------------------------------------------------------------------------------- 1 | section: 2 | type: section 3 | text: PLUGIN_STATIC_GENERATOR.ADMIN.GENERATE.EXPLANATION 4 | underline: true 5 | route: 6 | type: text 7 | label: PLUGIN_STATIC_GENERATOR.ADMIN.ROUTE 8 | placeholder: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.ROUTE 9 | target: 10 | type: text 11 | label: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.TARGET 12 | placeholder: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.STORAGE.TARGET 13 | assets: 14 | type: checkbox 15 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.ASSETS 16 | static_assets: 17 | type: checkbox 18 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.STATIC_ASSETS 19 | images: 20 | type: checkbox 21 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.IMAGES 22 | parameters: 23 | type: array 24 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.PARAMETERS.TITLE 25 | description: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.PARAMETERS.DESCRIPTION 26 | placeholder_key: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.PARAMETERS.KEY 27 | placeholder_value: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.PARAMETERS.VALUE 28 | filters: 29 | type: selectize 30 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.FILTERS 31 | description: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.FILTERS 32 | selectize: 33 | options: 34 | - text: "visible" 35 | value: "visible" 36 | - text: "nonVisible" 37 | value: "nonVisible" 38 | - text: "modular" 39 | value: "modular" 40 | - text: "nonModular" 41 | value: "nonModular" 42 | - text: "published" 43 | value: "published" 44 | - text: "nonPublished" 45 | value: "nonPublished" 46 | - text: "routable" 47 | value: "routable" 48 | - text: "nonRoutable" 49 | value: "nonRoutable" 50 | # links: 51 | # type: preset_buttons 52 | # fields: 53 | # - class: static-generator-preset-generate disabled 54 | # name: PLUGIN_STATIC_GENERATOR.ADMIN.GENERATE.TITLE 55 | # disabled: true 56 | -------------------------------------------------------------------------------- /blueprints/partials/generate-options.yaml: -------------------------------------------------------------------------------- 1 | .name: 2 | type: text 3 | label: PLUGIN_ADMIN.NAME 4 | placeholder: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.NAME 5 | validate: 6 | required: true 7 | .route: 8 | type: text 9 | label: PLUGIN_STATIC_GENERATOR.ADMIN.ROUTE 10 | placeholder: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.ROUTE 11 | .target: 12 | type: text 13 | label: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.TARGET 14 | placeholder: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.STORAGE.TARGET 15 | .assets: 16 | type: checkbox 17 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.ASSETS 18 | .static_assets: 19 | type: checkbox 20 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.STATIC_ASSETS 21 | .images: 22 | type: checkbox 23 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.IMAGES 24 | .parameters: 25 | type: array 26 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.PARAMETERS.TITLE 27 | description: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.PARAMETERS.DESCRIPTION 28 | placeholder_key: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.PARAMETERS.KEY 29 | placeholder_value: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.PARAMETERS.VALUE 30 | .filters: 31 | type: selectize 32 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.FILTERS 33 | description: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.FILTERS 34 | selectize: 35 | options: 36 | - text: "visible" 37 | value: "visible" 38 | - text: "nonVisible" 39 | value: "nonVisible" 40 | - text: "modular" 41 | value: "modular" 42 | - text: "nonModular" 43 | value: "nonModular" 44 | - text: "published" 45 | value: "published" 46 | - text: "nonPublished" 47 | value: "nonPublished" 48 | - text: "routable" 49 | value: "routable" 50 | - text: "nonRoutable" 51 | value: "nonRoutable" 52 | .links: 53 | type: preset_buttons 54 | fields: 55 | - class: static-generator-copy-preset 56 | name: PLUGIN_ADMIN.COPY 57 | - class: static-generator-preset-generate disabled 58 | name: PLUGIN_STATIC_GENERATOR.ADMIN.GENERATE.TITLE 59 | disabled: true 60 | -------------------------------------------------------------------------------- /classes/Data/DataInterface.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/OleVik/grav-plugin-static-generator 14 | */ 15 | 16 | namespace Grav\Plugin\StaticGenerator\Data; 17 | 18 | use Grav\Common\Page\Interfaces\PageInterface as Page; 19 | 20 | /** 21 | * Data Builder Interface 22 | * 23 | * @category API 24 | * @package Grav\Plugin\StaticGenerator\Data\DataInterface 25 | * @author Ole Vik 26 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 27 | * @link https://github.com/OleVik/grav-plugin-static-generator 28 | */ 29 | interface DataInterface 30 | { 31 | /** 32 | * Instantiate class 33 | * 34 | * @param string $url Custom Base URL. 35 | * @param bool $content Whether to include content. 36 | * @param int $maxLength Maximum character-length of content. 37 | * @param string $orderBy Property to order by. 38 | * @param string $orderDir Direction to order. 39 | */ 40 | public function __construct( 41 | string $url = '', 42 | bool $content = false, 43 | int $maxLength = null, 44 | string $orderBy = 'date', 45 | string $orderDir = 'desc' 46 | ); 47 | 48 | /** 49 | * Count items 50 | * 51 | * @return int 52 | */ 53 | public function count(): int; 54 | 55 | /** 56 | * Increase counter 57 | * 58 | * @return void 59 | */ 60 | public function progress(): void; 61 | 62 | /** 63 | * Create data-structure recursively 64 | * 65 | * @param string $route Route to page. 66 | * @param string $mode Placeholder for operation-mode, private. 67 | * @param int $depth Placeholder for recursion depth, private. 68 | * 69 | * @return mixed Index of Pages with FrontMatter 70 | */ 71 | public function index(string $route, string $mode = '', int $depth = 0); 72 | 73 | /** 74 | * Parse Page content 75 | * 76 | * @param Page $page Instance of Grav\Common\Page\Page. 77 | * 78 | * @return string content 79 | */ 80 | public function content(Page $page): string; 81 | } 82 | -------------------------------------------------------------------------------- /classes/Timer.php: -------------------------------------------------------------------------------- 1 | 10 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 11 | * @link https://github.com/OleVik/grav-plugin-static-generator 12 | */ 13 | 14 | namespace Grav\Plugin\StaticGenerator; 15 | 16 | /** 17 | * Class Generic Timer 18 | * 19 | * @category API 20 | * @package Grav\Plugin\StaticGenerator\Timer 21 | * @author Ole Vik 22 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 | * @link https://github.com/OleVik/grav-theme-scholar 24 | */ 25 | class Timer 26 | { 27 | protected $start; 28 | protected $stop; 29 | 30 | /** 31 | * Initialize class 32 | */ 33 | public function __construct() 34 | { 35 | $this->start(); 36 | } 37 | 38 | /** 39 | * Start Timer 40 | * 41 | * @return void 42 | */ 43 | public function start(): void 44 | { 45 | $this->start = (int) (microtime(true) * 1000); 46 | } 47 | 48 | /** 49 | * Stop Timer 50 | * 51 | * @return void 52 | */ 53 | public function stop(): void 54 | { 55 | $this->stop = (int) (microtime(true) * 1000); 56 | } 57 | 58 | /** 59 | * Get Time difference 60 | * 61 | * @return int 62 | */ 63 | public function getTime(): int 64 | { 65 | $stop = $this->stop; 66 | if (!$stop) { 67 | $stop = (int) (microtime(true) * 1000); 68 | } 69 | $ms = $stop - $this->start; 70 | return $ms; 71 | } 72 | 73 | /** 74 | * Format milliseconds as closest major time unit 75 | * 76 | * @param int $ms Milliseconds 77 | * 78 | * @return string 79 | */ 80 | public static function format($ms): string 81 | { 82 | $seconds = round(($ms / 1000), 2); 83 | $minutes = round(($ms / (1000 * 60)), 2); 84 | $hours = round(($ms / (1000 * 60 * 60)), 2); 85 | $days = round(($ms / (1000 * 60 * 60 * 24)), 2); 86 | if ($seconds <= 0) { 87 | return $ms . " ms"; 88 | } elseif ($seconds < 60) { 89 | return $seconds . " sec"; 90 | } elseif ($minutes < 60) { 91 | return $minutes . " min"; 92 | } elseif ($hours < 24) { 93 | return $hours . " hrs"; 94 | } else { 95 | return $days . " days"; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /blueprints/partials/options.yaml: -------------------------------------------------------------------------------- 1 | enabled: 2 | type: toggle 3 | label: PLUGIN_ADMIN.PLUGIN_STATUS 4 | options: 5 | 1: PLUGIN_ADMIN.ENABLED 6 | 0: PLUGIN_ADMIN.DISABLED 7 | validate: 8 | type: bool 9 | index: 10 | type: text 11 | label: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.INDEX 12 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.STORAGE.INDEX 13 | highlight: native 14 | default: native 15 | options: 16 | native: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.OPTIONS.NATIVE 17 | persist: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.OPTIONS.PERSIST 18 | transient: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.OPTIONS.TRANSIENT 19 | validate: 20 | required: true 21 | content: 22 | type: text 23 | label: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.CONTENT 24 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.STORAGE.CONTENT 25 | highlight: native 26 | default: native 27 | options: 28 | native: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.OPTIONS.NATIVE 29 | persist: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.OPTIONS.PERSIST 30 | transient: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.OPTIONS.TRANSIENT 31 | validate: 32 | required: true 33 | explanation: 34 | type: spacer 35 | text: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.STORAGE.EXPLANATION 36 | markdown: true 37 | content_max_length: 38 | type: number 39 | label: PLUGIN_STATIC_GENERATOR.ADMIN.CONTENT_MAX_LENGTH 40 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.CONTENT_MAX_LENGTH 41 | validate: 42 | min: 0 43 | step: 10000 44 | max: 10000000 45 | admin: 46 | type: toggle 47 | label: PLUGIN_STATIC_GENERATOR.ADMIN.ADMIN 48 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.ADMIN 49 | options: 50 | 1: PLUGIN_ADMIN.ENABLED 51 | 0: PLUGIN_ADMIN.DISABLED 52 | validate: 53 | type: bool 54 | js: 55 | type: toggle 56 | label: PLUGIN_STATIC_GENERATOR.ADMIN.JS 57 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.JS 58 | options: 59 | 1: PLUGIN_ADMIN.ENABLED 60 | 0: PLUGIN_ADMIN.DISABLED 61 | validate: 62 | type: bool 63 | css: 64 | type: toggle 65 | label: PLUGIN_STATIC_GENERATOR.ADMIN.CSS 66 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.CSS 67 | options: 68 | 1: PLUGIN_ADMIN.ENABLED 69 | 0: PLUGIN_ADMIN.DISABLED 70 | validate: 71 | type: bool 72 | quick_tray: 73 | type: toggle 74 | label: PLUGIN_STATIC_GENERATOR.ADMIN.CSS 75 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.CSS 76 | options: 77 | 1: PLUGIN_ADMIN.ENABLED 78 | 0: PLUGIN_ADMIN.DISABLED 79 | validate: 80 | type: bool 81 | -------------------------------------------------------------------------------- /blueprints/partials/presets.yaml: -------------------------------------------------------------------------------- 1 | section: 2 | type: section 3 | text: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.EXPLANATION 4 | underline: true 5 | markdown: true 6 | presets: 7 | type: list 8 | description: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.EXPLANATION 9 | style: vertical 10 | classes: static-generator-presets 11 | fields: 12 | .name: 13 | type: text 14 | label: PLUGIN_ADMIN.NAME 15 | placeholder: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.NAME 16 | validate: 17 | required: true 18 | .route: 19 | type: text 20 | label: PLUGIN_STATIC_GENERATOR.ADMIN.ROUTE 21 | placeholder: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.ROUTE 22 | .target: 23 | type: text 24 | label: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.TARGET 25 | placeholder: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.STORAGE.TARGET 26 | .assets: 27 | type: checkbox 28 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.ASSETS 29 | .static_assets: 30 | type: checkbox 31 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.STATIC_ASSETS 32 | .images: 33 | type: checkbox 34 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.IMAGES 35 | .parameters: 36 | type: array 37 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.PARAMETERS.TITLE 38 | description: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.PARAMETERS.DESCRIPTION 39 | placeholder_key: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.PARAMETERS.KEY 40 | placeholder_value: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.PARAMETERS.VALUE 41 | .filters: 42 | type: selectize 43 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.FILTERS 44 | description: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.FILTERS 45 | selectize: 46 | options: 47 | - text: "visible" 48 | value: "visible" 49 | - text: "nonVisible" 50 | value: "nonVisible" 51 | - text: "modular" 52 | value: "modular" 53 | - text: "nonModular" 54 | value: "nonModular" 55 | - text: "published" 56 | value: "published" 57 | - text: "nonPublished" 58 | value: "nonPublished" 59 | - text: "routable" 60 | value: "routable" 61 | - text: "nonRoutable" 62 | value: "nonRoutable" 63 | .links: 64 | type: preset_buttons 65 | fields: 66 | - class: static-generator-copy-preset 67 | name: PLUGIN_ADMIN.COPY 68 | # - class: static-generator-preset-generate disabled 69 | # name: PLUGIN_STATIC_GENERATOR.ADMIN.GENERATE.TITLE 70 | # disabled: true 71 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); 27 | if ($useStaticLoader) { 28 | require_once __DIR__ . '/autoload_static.php'; 29 | 30 | call_user_func(\Composer\Autoload\ComposerStaticInite2f4a2b75e1df9085b6021d6d25a7526::getInitializer($loader)); 31 | } else { 32 | $map = require __DIR__ . '/autoload_namespaces.php'; 33 | foreach ($map as $namespace => $path) { 34 | $loader->set($namespace, $path); 35 | } 36 | 37 | $map = require __DIR__ . '/autoload_psr4.php'; 38 | foreach ($map as $namespace => $path) { 39 | $loader->setPsr4($namespace, $path); 40 | } 41 | 42 | $classMap = require __DIR__ . '/autoload_classmap.php'; 43 | if ($classMap) { 44 | $loader->addClassMap($classMap); 45 | } 46 | } 47 | 48 | $loader->register(true); 49 | 50 | if ($useStaticLoader) { 51 | $includeFiles = Composer\Autoload\ComposerStaticInite2f4a2b75e1df9085b6021d6d25a7526::$files; 52 | } else { 53 | $includeFiles = require __DIR__ . '/autoload_files.php'; 54 | } 55 | foreach ($includeFiles as $fileIdentifier => $file) { 56 | composerRequiree2f4a2b75e1df9085b6021d6d25a7526($fileIdentifier, $file); 57 | } 58 | 59 | return $loader; 60 | } 61 | } 62 | 63 | function composerRequiree2f4a2b75e1df9085b6021d6d25a7526($fileIdentifier, $file) 64 | { 65 | if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { 66 | require $file; 67 | 68 | $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /classes/Collection/CollectionInterface.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/OleVik/grav-plugin-static-generator 14 | */ 15 | 16 | namespace Grav\Plugin\StaticGenerator\Collection; 17 | 18 | use Grav\Common\Page\Interfaces\PageInterface as Page; 19 | 20 | /** 21 | * Collection Interface 22 | * 23 | * @category API 24 | * @package Grav\Plugin\StaticGenerator\Collection\CollectionInterface 25 | * @author Ole Vik 26 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 27 | * @link https://github.com/OleVik/grav-plugin-static-generator 28 | */ 29 | interface CollectionInterface 30 | { 31 | /** 32 | * Initialize class 33 | * 34 | * @param string $collection Collection to evaluate. 35 | * @param string $route Route to page, optional. 36 | * @param string $location Where to store output. 37 | * @param boolean $force Forcefully save data. 38 | * @param string $rootPrefix Root prefix. 39 | * @param array $filters Methods to filter Collection by. 40 | * @param array $parameters Parameters to pass to Config or Twig. 41 | */ 42 | public function __construct( 43 | string $collection, 44 | string $route = '', 45 | string $location = '', 46 | bool $force = false, 47 | string $rootPrefix = '', 48 | array $filters = [], 49 | array $parameters = [] 50 | ); 51 | 52 | /** 53 | * Bootstrap data, events, and helpers 54 | * 55 | * @param string $preset Name of Config Preset to load. 56 | * @param bool $offline Force offline-mode. 57 | * 58 | * @return void 59 | */ 60 | public function setup(string $preset, $offline): void; 61 | 62 | /** 63 | * Build Page(s) 64 | * 65 | * @return void 66 | */ 67 | public function buildCollection(): void; 68 | 69 | /** 70 | * Build assets 71 | * 72 | * @return void 73 | */ 74 | public function buildAssets(): void; 75 | 76 | /** 77 | * Mirror images 78 | * 79 | * @param boolean $force Forcefully save data. 80 | * 81 | * @return void 82 | */ 83 | public function mirrorImages(bool $force): void; 84 | 85 | /** 86 | * Store Page 87 | * 88 | * @param Page $Page Grav Page instance. 89 | * 90 | * @return void 91 | */ 92 | public function store(Page $Page): void; 93 | } 94 | -------------------------------------------------------------------------------- /cli/TestStaticDataCommand.php: -------------------------------------------------------------------------------- 1 | 11 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 12 | * @link https://github.com/OleVik/grav-theme-scholar 13 | */ 14 | 15 | namespace Grav\Plugin\Console; 16 | 17 | use Grav\Common\Grav; 18 | use Grav\Console\ConsoleCommand; 19 | use Symfony\Component\Console\Input\InputArgument; 20 | use Symfony\Component\Console\Input\InputOption; 21 | use Grav\Plugin\StaticGenerator\Data\TestData; 22 | use Grav\Plugin\StaticGenerator\Timer; 23 | 24 | /** 25 | * Data Index Builder 26 | * 27 | * Command line utility for storing Pages data as JSON 28 | * 29 | * @category API 30 | * @package Grav\Plugin\Console\TestStaticDataCommand 31 | * @author Ole Vik 32 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 33 | * @link https://github.com/OleVik/grav-theme-scholar 34 | */ 35 | class TestStaticDataCommand extends ConsoleCommand 36 | { 37 | /** 38 | * Command definitions 39 | * 40 | * @return void 41 | */ 42 | protected function configure() 43 | { 44 | $this 45 | ->setName("test") 46 | ->setDescription("Tests Page iteration.") 47 | ->setHelp('The test-command tests Page iteration.') 48 | ->addArgument( 49 | 'route', 50 | InputArgument::REQUIRED, 51 | 'The route to the page' 52 | ) 53 | ->addArgument( 54 | 'target', 55 | InputArgument::OPTIONAL, 56 | 'Override target-option or set a custom destination' 57 | ); 58 | } 59 | 60 | /** 61 | * Build and save data index 62 | * 63 | * @return void 64 | */ 65 | protected function serve() 66 | { 67 | $timer = new Timer(); 68 | $config = Grav::instance()['config']->get('plugins.static-generator'); 69 | $route = $this->input->getArgument('route'); 70 | $maxLength = $config['content_max_length']; 71 | $this->output->writeln('Testing data index'); 72 | try { 73 | parent::initializePages(); 74 | $Data = new TestData(true, $maxLength); 75 | $Data->bootstrap($route); 76 | $this->output->writeln('Count: ' . $Data->count . ''); 77 | $Data->index($route); 78 | $this->output->writeln('Finished in ' . Timer::format($timer->getTime()) . ''); 79 | } catch (\Exception $e) { 80 | throw new \Exception($e); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /classes/Config/SSEConfig.php: -------------------------------------------------------------------------------- 1 | 11 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 12 | * @link https://github.com/OleVik/grav-plugin-static-generator 13 | */ 14 | namespace Grav\Plugin\StaticGenerator\Config; 15 | 16 | use Grav\Common\Grav; 17 | use Grav\Framework\Cache\Adapter\FileStorage; 18 | use Grav\Plugin\StaticGenerator\Timer; 19 | use Grav\Plugin\StaticGenerator\Data\SSEData; 20 | use Grav\Plugin\StaticGenerator\Config\Config; 21 | 22 | /** 23 | * Server Sent Events Config 24 | * 25 | * @category API 26 | * @package Grav\Plugin\StaticGenerator\Config\SSEConfig 27 | * @author Ole Vik 28 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 29 | * @link https://github.com/OleVik/grav-plugin-static-generator 30 | */ 31 | class SSEConfig extends SSEData 32 | { 33 | /** 34 | * Bootstrap data 35 | * 36 | * @return void 37 | */ 38 | public function setup() 39 | { 40 | echo 'event: update' . "\n\n"; 41 | echo 'data: ' . json_encode( 42 | [ 43 | 'datetime' => date(DATE_ISO8601), 44 | 'total' => 1 45 | ] 46 | ) . "\n\n"; 47 | } 48 | 49 | /** 50 | * Mirror Config 51 | * 52 | * @param string $preset Preset name. 53 | * @param string $target Location to store Config in. 54 | * @param string $source Source to copy from. 55 | * @param Timer $Timer Instance of Grav\Plugin\StaticGenerator\Timer. 56 | * @param boolean $force Forcefully save. 57 | * 58 | * @return void 59 | */ 60 | public function mirror( 61 | string $preset, 62 | string $target, 63 | string $source, 64 | Timer $Timer, 65 | bool $force = true 66 | ): void { 67 | try { 68 | $target = Grav::instance()['locator']->findResource($target, true, true); 69 | Config::mirror($target, $source, $force); 70 | $message = ucfirst( 71 | Grav::instance()['language']->translate( 72 | ['PLUGIN_STATIC_GENERATOR.ADMIN.GENERIC.STORED'] 73 | ) 74 | ) . ' "' . $preset . '" ' . 75 | Grav::instance()['language']->translate( 76 | ['PLUGIN_STATIC_GENERATOR.ADMIN.GENERIC.IN'] 77 | ) . ' ' . $target . ' ' . 78 | Grav::instance()['language']->translate( 79 | ['PLUGIN_STATIC_GENERATOR.ADMIN.GENERIC.IN'] 80 | ) . ' ' . Timer::format($Timer->getTime()) . '.'; 81 | echo 'event: update' . "\n\n"; 82 | echo 'data: ' . json_encode( 83 | [ 84 | 'datetime' => date(DATE_ISO8601), 85 | 'content' => $message, 86 | 'text' => $preset, 87 | 'value' => $source 88 | ] 89 | ) . "\n\n"; 90 | Grav::instance()['log']->info($message); 91 | } catch (\Exception $e) { 92 | throw new \Exception($e); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /cli/ClearStaticDataCommand.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/OleVik/grav-theme-scholar 14 | */ 15 | 16 | namespace Grav\Plugin\Console; 17 | 18 | use Grav\Common\Grav; 19 | use Grav\Common\Utils; 20 | use Grav\Console\ConsoleCommand; 21 | use Symfony\Component\Console\Input\InputArgument; 22 | use Grav\Plugin\StaticGenerator\Timer; 23 | use Symfony\Component\Filesystem\Filesystem; 24 | use Symfony\Component\Filesystem\Exception\IOExceptionInterface; 25 | 26 | /** 27 | * Clear Data 28 | * 29 | * Helper-utility for clearing data 30 | * 31 | * @category API 32 | * @package Grav\Plugin\StaticGenerator 33 | * @subpackage Grav\Plugin\StaticGenerator\API 34 | * @author Ole Vik 35 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 36 | * @link https://github.com/OleVik/grav-theme-scholar 37 | */ 38 | class ClearStaticDataCommand extends ConsoleCommand 39 | { 40 | /** 41 | * Command definitions 42 | * 43 | * @return void 44 | */ 45 | protected function configure() 46 | { 47 | $this 48 | ->setName("clear") 49 | ->setDescription("Clears Index and static-generator Page(s)") 50 | ->setHelp('The clear-command deletes Index and static-generator Page(s).') 51 | ->addArgument( 52 | 'target', 53 | InputArgument::OPTIONAL, 54 | 'Override target-option or set a custom destination' 55 | ); 56 | } 57 | 58 | /** 59 | * Clear Data Index 60 | * 61 | * @throws \Symfony\Component\Filesystem\Exception\IOExceptionInterface 62 | * 63 | * @return void 64 | */ 65 | protected function serve() 66 | { 67 | $timer = new Timer(); 68 | $config = Grav::instance()['config']->get('plugins.static-generator'); 69 | $locator = Grav::instance()['locator']; 70 | $target = $this->input->getArgument('target'); 71 | if ($target === null) { 72 | $target = $config['index']; 73 | } 74 | $this->output->writeln('Clearing data'); 75 | try { 76 | $Filesystem = new Filesystem(); 77 | if (Utils::contains($target, '://')) { 78 | $scheme = parse_url($target, PHP_URL_SCHEME); 79 | $location = $locator->findResource($scheme . '://') . str_replace($scheme . '://', '/', $target); 80 | } else { 81 | $this->output->writeln('Target must be a valid stream resource, prefixing one of:'); 82 | foreach ($locator->getSchemes() as $scheme) { 83 | $this->output->writeln($scheme . '://'); 84 | } 85 | return; 86 | } 87 | $Filesystem->remove($location); 88 | $this->output->writeln('Deleted ' . $location . ''); 89 | } catch (IOExceptionInterface $e) { 90 | throw new IOExceptionInterface($e); 91 | } catch (\Exception $e) { 92 | throw new \Exception($e); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v4.0.1 2 | ## 18-07-2023 3 | 4 | 1. [](#bugfix) 5 | * `DataInterface`-compliance 6 | 7 | # v4.0.0 8 | ## 18-07-2023 9 | 10 | 1. [](#new) 11 | * URL-parameter to pass `custom_base_url` to CLI 12 | 2. [](#improved) 13 | * Initialize URI when constructring `AbstractData` 14 | 3. [](#bugfix) 15 | * Refer to own instance on `AbstractData->setup()` 16 | 17 | # v3.1.1 18 | ## 02-06-2021 19 | 20 | 1. [](#bugfix) 21 | * Link to docs 22 | 23 | # v3.1.0 24 | ## 10-04-2021 25 | 26 | 1. [](#improved) 27 | * API-alignment to use `Grav\Common\Page\Interfaces\PageInterface`, transparently 28 | * Code cleanup 29 | 2. [](#new) 30 | * Revert content index-location to `user://data/persist` 31 | 3. [](#bugfix) 32 | * Correct key in indexed metadata 33 | 34 | # v3.0.0 35 | ## 07-03-2021 36 | 37 | 1. [](#new) 38 | * Compatibility with Grav Core 1.7 39 | * Separate buttons and tasks for Index and Content in Admin 40 | 2. [](#improved) 41 | * Stream-resolution in FileStorage Adapter 42 | * Code-quality 43 | 3. [](#bugfix) 44 | * Method-alignment 45 | * Method-fallback 46 | * Exception-catching 47 | 48 | # v2.1.3 49 | ## 26-09-2020 50 | 51 | 1. [](#bugfix) 52 | * Handle route-prefix more gracefully 53 | 54 | # v2.1.2 55 | ## 24-09-2020 56 | 57 | 1. [](#bugfix) 58 | * Do not strip route-prefix 59 | 60 | # v2.1.1 61 | ## 24-09-2020 62 | 63 | 1. [](#improved) 64 | * Media routes sanitizing 65 | 2. [](#bugfix) 66 | * Canonical URLs 67 | 68 | # v2.1.0 69 | ## 22-09-2020 70 | 71 | 1. [](#bugfix) 72 | * Do not end copyMedia() prematurely 73 | * Avoid double-slash prefix 74 | 75 | # v2.0.0 76 | ## 14-03-2020 77 | 78 | 1. [](#new) 79 | * Assets-prefix option 80 | 2. [](#bugfix) 81 | * CommandLineCollection-initialization 82 | 3. [](#improved) 83 | * Version-constraint 84 | 85 | # v2.0.0-beta.1 86 | ## 06-03-2020 87 | 88 | 1. [](#improved) 89 | * Grav-initialization 90 | * API-cleanup 91 | * Asset-parsing and -rewriting 92 | 2. [](#new) 93 | * Offline-option to avoid trying to download remote assets 94 | 3. [](#bugfix) 95 | * Lock version-dependency to Core v1.6.22 96 | * Revert blueprints-logic 97 | * Fix permissions-selectize field 98 | 99 | # v2.0.0-alpha.3 100 | ## 11-02-2020 101 | 102 | 1. [](#new) 103 | * Customizable permissions for Quick Tray, Generation-buttons 104 | * Parameter-handling for Config and Twig 105 | * Deprecate Preset-page in Admin 106 | 2. [](#improved) 107 | * Command-preview in Admin 108 | * Admin tab-order 109 | * README 110 | * Blueprints 111 | 112 | # v2.0.0-alpha.2 113 | ## 06-02-2020 114 | 115 | 1. [](#improved) 116 | * Admin-preview 117 | * Configuration 118 | * Blueprints 119 | 2. [](#new) 120 | * Customizable permissions for Quick Tray, Generation-buttons 121 | * Deprecate Preset-page in Admin 122 | 123 | # v2.0.0-alpha.1 124 | ## 01-02-2020 125 | 126 | 1. [](#new) 127 | * API-refactor 128 | * Presets 129 | 2. [](#improved) 130 | * Blueprints 131 | * Asset- and media-handling 132 | * Script in Admin 133 | 3. [](#bugfix) 134 | * Blueprints & CLI (@klonfish) 135 | 136 | # v1.0.1 137 | ## 15-02-2020 138 | 139 | 1. [](#bugfix) 140 | * Target-selection (@klonfish, #1 and #2) 141 | 142 | # v1.0.0 143 | ## 30-11-2019 144 | 145 | 1. [](#new) 146 | * Initial public release 147 | -------------------------------------------------------------------------------- /languages.yaml: -------------------------------------------------------------------------------- 1 | en: 2 | PLUGIN_STATIC_GENERATOR: 3 | ADMIN: 4 | GENERIC: 5 | STORED: stored 6 | ITEMS: items 7 | IN: in 8 | SEARCH: Search 9 | ROUTE: Route 10 | GENERATE: 11 | TITLE: Generate 12 | EXPLANATION: Fill in the fields below and run the Command in a terminal to create a static copy of the site. All fields are optional. The Command must be run from the root-folder where Grav is installed. 13 | COMMAND: Command 14 | PRESETS: 15 | TITLE: Presets 16 | SINGULAR: Preset 17 | ROOT_PREFIX: Root Prefix 18 | ASSETS: Include Assets 19 | STATIC_ASSETS: Include Static Assets 20 | IMAGES: Include Images 21 | PARAMETERS: 22 | TITLE: Parameters 23 | VALUE: Value 24 | FILTERS: Filters 25 | EXPLANATION: Presets are sets of configurations for telling the plugin how Grav should behave when generating the static copy of the Page(s). A preset only requires a name, parameters and a copy of the current settings are optional. When you click the Copy-button, Grav's site-, system- and extension-settings are stored, and will be used when generating from the preset. Run the Command in a terminal to create a static copy of the site. The Command must be run from the root-folder where Grav is installed. 26 | TITLE: Static Generator Options 27 | SEARCH_FILE: Search File 28 | STORAGE: 29 | INDEX: Index Location 30 | CONTENT: Content Location 31 | TARGET: Target 32 | OPTIONS: 33 | PERSIST: Persist 34 | TRANSIENT: Transient 35 | NATIVE: Native 36 | EMPTY: Found no lower-level Pages 37 | CONTENT_MAX_LENGTH: Content Max Length 38 | CONTENT_PERMISSIONS: Content Permissions 39 | ADMIN: Admin 40 | JS: Admin JS 41 | CSS: Admin CSS 42 | QUICK_TRAY: Quick Tray 43 | QUICK_TRAY_PERMISSIONS: Quick Tray Permissions 44 | INDEX: 45 | HINT: Store Index Data 46 | WAITING: Waiting for Static Generator ... 47 | ONGOING: Creating Data ... 48 | SUCCESS: Saved Data 49 | ERROR: Failed to Create or Save Data 50 | CONTENT: 51 | HINT: Store Content Data 52 | HELP: 53 | ROUTE: Page Route, excluding initial forward slash 54 | PRESETS: 55 | NAME: Name of the Preset 56 | PARAMETERS: 57 | DESCRIPTION: Key-value pairs to pass to Config or Twig, in dot-notation format. 58 | KEY: Eg. theme.color 59 | VALUE: Eg. blue 60 | FILTERS: Collection-methods to apply for filtering. 61 | STORAGE: 62 | TARGET: Non-default location to store the Preset in 63 | ROOT_PREFIX: Base path prefixed to assets and images 64 | DESCRIPTION: 65 | SEARCH_FILE: File to use for searching. 66 | STORAGE: 67 | INDEX: Where to store search-files. 68 | CONTENT: Where to store static copies of the site. 69 | EXPLANATION: Must be a [valid Stream-wrapper](https://learn.getgrav.org/16/advanced/multisite-setup#streams) that PHP can resolve and write to. 70 | CONTENT_MAX_LENGTH: Maximum characters in Page when generating content. If the Page contains more, it will not be stored. 71 | CONTENT_PERMISSIONS: Permissions to see and use Generate-button from Admin. 72 | ADMIN: Enable plugin in Admin. 73 | JS: Use plugin's JavaScript in Admin. 74 | CSS: Use plugin's CSS in Admin. 75 | QUICK_TRAY: Enable Quick Tray button 76 | QUICK_TRAY_PERMISSIONS: Permissions to see and use Index-button from Admin. 77 | -------------------------------------------------------------------------------- /classes/Source/Source.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/OleVik/grav-plugin-static-generator 14 | */ 15 | 16 | namespace Grav\Plugin\StaticGenerator\Source; 17 | 18 | use Grav\Common\Utils; 19 | 20 | /** 21 | * Source Manipulator 22 | * 23 | * @category Extensions 24 | * @package Grav\Plugin\StaticGenerator\Source 25 | * @author Ole Vik 26 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 27 | * @link https://github.com/OleVik/grav-plugin-static-generator 28 | */ 29 | class Source 30 | { 31 | /** 32 | * Rewrite asset-paths 33 | * 34 | * @param string $content Page HTML. 35 | * @param string $rootPrefix Root prefix. 36 | * 37 | * @return string Processed HTML 38 | */ 39 | public static function rewriteAssetURLs( 40 | string $content, 41 | string $rootPrefix 42 | ): string { 43 | preg_match_all('/<(?:link href|script src)="(?[^"]*)"/ui', $content, $matches, PREG_SET_ORDER, 0); 44 | foreach ($matches as $asset) { 45 | if (!isset($asset['url'])) { 46 | continue; 47 | } 48 | if (Utils::startsWith($asset['url'], '/user')) { 49 | $target = $asset['url']; 50 | } elseif (Utils::startsWith($asset['url'], '/system')) { 51 | $target = $asset['url']; 52 | } else { 53 | $url = parse_url($asset['url']); 54 | $target = '/' . $url['host'] . $url['path']; 55 | } 56 | $content = str_replace( 57 | $asset['url'], 58 | $rootPrefix . 'assets' . $target, 59 | $content 60 | ); 61 | } 62 | return $content; 63 | } 64 | 65 | /** 66 | * Rewrite media-paths 67 | * 68 | * @param string $content Page HTML. 69 | * @param string $old Original path. 70 | * @param string $new New path. 71 | * 72 | * @return string Processed HTML 73 | */ 74 | public static function rewritePath( 75 | string $content, 76 | string $old, 77 | string $new 78 | ): string { 79 | return str_replace($old, $new, $content); 80 | } 81 | 82 | /** 83 | * Rewrite Page-routes 84 | * 85 | * @param string $content Page HTML. 86 | * @param string $routes Page routes. 87 | * 88 | * @return string Processed HTML 89 | */ 90 | public static function rewriteRoutes( 91 | string $content, 92 | array $routes 93 | ): string { 94 | foreach ($routes as $route) { 95 | if ($route !== '/') { 96 | $route = \ltrim($route, '/'); 97 | $content = str_replace('//' . $route, '/' . $route, $content); 98 | } 99 | } 100 | return $content; 101 | } 102 | 103 | /** 104 | * Rewrite Media-routes, in src-attribute 105 | * 106 | * @param string $content 107 | * 108 | * @return string Processed HTML 109 | */ 110 | public static function rewriteMediaRoutes(string $content): string 111 | { 112 | return preg_replace('/src="\/\/[0-9]*\.*/mi', 'src="/', $content); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /classes/Utilities.php: -------------------------------------------------------------------------------- 1 | 10 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 11 | * @link https://github.com/OleVik/grav-plugin-static-generator 12 | */ 13 | namespace Grav\Plugin\StaticGenerator; 14 | 15 | /** 16 | * Utilities 17 | * 18 | * @category Extensions 19 | * @package Grav\Plugin\StaticGenerator\Utilities 20 | * @author Ole Vik 21 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 22 | * @link https://github.com/OleVik/grav-plugin-static-generator 23 | */ 24 | class Utilities 25 | { 26 | /** 27 | * Search for a file in multiple locations 28 | * 29 | * @param string $file Filename. 30 | * @param array $locations List of locations. 31 | * 32 | * @return string File location. 33 | */ 34 | public static function fileFinder(string $file, array $locations): string 35 | { 36 | $return = ''; 37 | foreach ($locations as $location) { 38 | if (file_exists($location . '/' . $file)) { 39 | $return = $location . '/' . $file; 40 | break; 41 | } 42 | } 43 | return $return; 44 | } 45 | 46 | /** 47 | * Search for a folder in multiple locations 48 | * 49 | * @param string $folder Folder name.. 50 | * @param array $locations List of locations. 51 | * 52 | * @return string Folder location. 53 | */ 54 | public static function folderFinder(string $folder, array $locations): string 55 | { 56 | $return = ''; 57 | foreach ($locations as $location) { 58 | if (is_dir($location . '/' . $folder)) { 59 | $return = $location . '/' . $folder; 60 | break; 61 | } 62 | } 63 | return $return; 64 | } 65 | 66 | /** 67 | * Search for files in multiple locations 68 | * 69 | * @param string $directory Folder-name. 70 | * @param array $types File extensions. 71 | * 72 | * @return array List of file locations. 73 | */ 74 | public static function filesFinder(string $directory, array $types): array 75 | { 76 | $files = []; 77 | if (!is_dir($directory)) { 78 | return $files; 79 | } 80 | $iterator = new \FilesystemIterator( 81 | $directory, 82 | \FilesystemIterator::SKIP_DOTS 83 | ); 84 | $files = []; 85 | foreach ($iterator as $file) { 86 | if (in_array(pathinfo($file, PATHINFO_EXTENSION), $types)) { 87 | $files[] = $file; 88 | } 89 | } 90 | return $files; 91 | } 92 | 93 | /** 94 | * Search for a folders in multiple locations 95 | * 96 | * @param array $locations List of locations. 97 | * 98 | * @return array List of folder locations. 99 | */ 100 | public static function foldersFinder(array $locations): array 101 | { 102 | $return = array(); 103 | foreach ($locations as $location) { 104 | $folders = new \DirectoryIterator($location); 105 | foreach ($folders as $folder) { 106 | if ($folder->isDir() && !$folder->isDot()) { 107 | $return[] = $folder->getFilename(); 108 | } 109 | } 110 | } 111 | return $return; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /classes/Config/AbstractConfig.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/OleVik/grav-plugin-static-generator 14 | */ 15 | 16 | namespace Grav\Plugin\StaticGenerator\Config; 17 | 18 | use Grav\Common\Grav; 19 | use Grav\Common\Config\Config; 20 | use Grav\Plugin\StaticGenerator\Config\ConfigInterface; 21 | use Grav\Framework\File\YamlFile; 22 | use Grav\Framework\File\Formatter\YamlFormatter; 23 | 24 | /** 25 | * Config Builder 26 | * 27 | * @category API 28 | * @package Grav\Plugin\StaticGenerator\Config\AbstractConfig 29 | * @author Ole Vik 30 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 31 | * @link https://github.com/OleVik/grav-plugin-static-generator 32 | */ 33 | abstract class AbstractConfig implements ConfigInterface 34 | { 35 | /** 36 | * Initialize class 37 | * 38 | * @param Config $config Instance of Grav\Common\Config\Config. 39 | * @param string $path Location of Preset-storage. 40 | * @param string $name Name of Preset. 41 | */ 42 | public function __construct(Config $config, string $path, string $name) 43 | { 44 | $this->origin = $config; 45 | $preset = self::buildPreset($path . DS . $name); 46 | $this->config = new Config($preset); 47 | $this->config->environment = 'preset-' . $name; 48 | } 49 | 50 | /** 51 | * Structure Preset 52 | * 53 | * @param string $folder Location of Preset-files. 54 | * @param array $fileTypes File types to include, defaults to YAML. 55 | * 56 | * @return array 57 | */ 58 | public static function buildPreset( 59 | string $folder, 60 | array $fileTypes = ['yaml'] 61 | ): array { 62 | $preset = array(); 63 | $folder = Grav::instance()['locator']->findResource($folder, true, true); 64 | $fileIterator = self::fileIterator($folder, $fileTypes); 65 | $formatter = new YamlFormatter; 66 | foreach ($fileIterator as $file) { 67 | $base = $file->getBasename('.' . $file->getExtension()); 68 | $prefix = basename( 69 | dirname( 70 | str_replace( 71 | $folder, 72 | '', 73 | str_replace('\\', '/', $file->getRealPath()) 74 | ) 75 | ) 76 | ); 77 | $file = new YamlFile( 78 | $file->getRealPath(), 79 | $formatter 80 | ); 81 | if (!$prefix) { 82 | $preset[$base] = $file->load(); 83 | } else { 84 | $preset[$prefix][$base] = $file->load(); 85 | } 86 | } 87 | return $preset; 88 | } 89 | 90 | /** 91 | * Construct Regular Expression File Iterator 92 | * 93 | * @param string $folder Folder to iterate from. 94 | * @param array $fileTypes File types to include. 95 | * 96 | * @return void 97 | */ 98 | public static function fileIterator(string $folder, array $fileTypes) 99 | { 100 | if (count($fileTypes) > 1) { 101 | $files = implode('|', $fileTypes); 102 | } else { 103 | $files = $fileTypes[0]; 104 | } 105 | $iterator = new \RecursiveIteratorIterator( 106 | new \RecursiveDirectoryIterator($folder) 107 | ); 108 | return new \RegexIterator($iterator, '/\.' . $files . '$/imu'); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /classes/Data/AbstractData.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/OleVik/grav-plugin-static-generator 14 | */ 15 | 16 | namespace Grav\Plugin\StaticGenerator\Data; 17 | 18 | use Grav\Common\Grav; 19 | use Grav\Common\Page\Interfaces\PageInterface as Page; 20 | use Grav\Plugin\StaticGenerator\Data\DataInterface; 21 | 22 | /** 23 | * Abstract Data Builder 24 | * 25 | * @category API 26 | * @package Grav\Plugin\StaticGenerator\Data\AbstractData 27 | * @author Ole Vik 28 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 29 | * @link https://github.com/OleVik/grav-plugin-static-generator 30 | */ 31 | abstract class AbstractData implements DataInterface 32 | { 33 | public $data; 34 | public $grav; 35 | public $pages; 36 | public $progress; 37 | public $count; 38 | public $total; 39 | 40 | /** 41 | * Instantiate class 42 | * 43 | * @param string $url Custom Base URL. 44 | * @param bool $content Whether to include content. 45 | * @param int $maxLength Maximum character-length of content. 46 | * @param string $orderBy Property to order by. 47 | * @param string $orderDir Direction to order. 48 | */ 49 | public function __construct( 50 | string $url = '', 51 | bool $content = false, 52 | int $maxLength = null, 53 | string $orderBy = 'date', 54 | string $orderDir = 'desc' 55 | ) { 56 | $this->grav = Grav::instance(); 57 | if ($url && !empty($url) && filter_var($url, FILTER_VALIDATE_URL)) { 58 | $this->grav['config']->set('system.custom_base_url', $url); 59 | } 60 | $this->grav['uri']->init(); 61 | $this->data = array(); 62 | $this->content = $content; 63 | $this->maxLength = $maxLength; 64 | $this->orderBy = $orderBy; 65 | $this->orderDir = $orderDir; 66 | $this->progress = 1; 67 | $this->total = 0; 68 | } 69 | 70 | /** 71 | * Bootstrap data 72 | * 73 | * @return void 74 | */ 75 | public function setup() 76 | { 77 | if (isset($this->grav['admin'])) { 78 | if (method_exists($this->grav['admin'], 'enablePages')) { 79 | $this->grav['admin']->enablePages(); 80 | } 81 | } 82 | } 83 | 84 | /** 85 | * Count items 86 | * 87 | * @return int 88 | */ 89 | public function count(): int 90 | { 91 | return count($this->pages); 92 | } 93 | 94 | /** 95 | * Increase counter 96 | * 97 | * @return void 98 | */ 99 | public function progress(): void 100 | { 101 | $this->progress++; 102 | } 103 | 104 | /** 105 | * Parse Page content 106 | * 107 | * @param Page $page Instance of Grav\Common\Page\Page. 108 | * 109 | * @return string content 110 | */ 111 | public function content(Page $page): string 112 | { 113 | return $page->rawMarkdown() ?? ''; 114 | } 115 | 116 | /** 117 | * Create data-structure recursively 118 | * 119 | * @param string $route Route to page. 120 | * @param string $mode Placeholder for operation-mode, private. 121 | * @param int $depth Placeholder for recursion depth, private. 122 | * 123 | * @return mixed Index of Pages with FrontMatter 124 | */ 125 | abstract public function index(string $route, string $mode = '', int $depth = 0); 126 | } 127 | -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "symfony/filesystem", 4 | "version": "v4.4.5", 5 | "version_normalized": "4.4.5.0", 6 | "source": { 7 | "type": "git", 8 | "url": "https://github.com/symfony/filesystem.git", 9 | "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd" 10 | }, 11 | "dist": { 12 | "type": "zip", 13 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/266c9540b475f26122b61ef8b23dd9198f5d1cfd", 14 | "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd", 15 | "shasum": "" 16 | }, 17 | "require": { 18 | "php": "^7.1.3", 19 | "symfony/polyfill-ctype": "~1.8" 20 | }, 21 | "time": "2020-01-21T08:20:44+00:00", 22 | "type": "library", 23 | "extra": { 24 | "branch-alias": { 25 | "dev-master": "4.4-dev" 26 | } 27 | }, 28 | "installation-source": "dist", 29 | "autoload": { 30 | "psr-4": { 31 | "Symfony\\Component\\Filesystem\\": "" 32 | }, 33 | "exclude-from-classmap": [ 34 | "/Tests/" 35 | ] 36 | }, 37 | "notification-url": "https://packagist.org/downloads/", 38 | "license": [ 39 | "MIT" 40 | ], 41 | "authors": [ 42 | { 43 | "name": "Fabien Potencier", 44 | "email": "fabien@symfony.com" 45 | }, 46 | { 47 | "name": "Symfony Community", 48 | "homepage": "https://symfony.com/contributors" 49 | } 50 | ], 51 | "description": "Symfony Filesystem Component", 52 | "homepage": "https://symfony.com" 53 | }, 54 | { 55 | "name": "symfony/polyfill-ctype", 56 | "version": "v1.14.0", 57 | "version_normalized": "1.14.0.0", 58 | "source": { 59 | "type": "git", 60 | "url": "https://github.com/symfony/polyfill-ctype.git", 61 | "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38" 62 | }, 63 | "dist": { 64 | "type": "zip", 65 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", 66 | "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", 67 | "shasum": "" 68 | }, 69 | "require": { 70 | "php": ">=5.3.3" 71 | }, 72 | "suggest": { 73 | "ext-ctype": "For best performance" 74 | }, 75 | "time": "2020-01-13T11:15:53+00:00", 76 | "type": "library", 77 | "extra": { 78 | "branch-alias": { 79 | "dev-master": "1.14-dev" 80 | } 81 | }, 82 | "installation-source": "dist", 83 | "autoload": { 84 | "psr-4": { 85 | "Symfony\\Polyfill\\Ctype\\": "" 86 | }, 87 | "files": [ 88 | "bootstrap.php" 89 | ] 90 | }, 91 | "notification-url": "https://packagist.org/downloads/", 92 | "license": [ 93 | "MIT" 94 | ], 95 | "authors": [ 96 | { 97 | "name": "Gert de Pagter", 98 | "email": "BackEndTea@gmail.com" 99 | }, 100 | { 101 | "name": "Symfony Community", 102 | "homepage": "https://symfony.com/contributors" 103 | } 104 | ], 105 | "description": "Symfony polyfill for ctype functions", 106 | "homepage": "https://symfony.com", 107 | "keywords": [ 108 | "compatibility", 109 | "ctype", 110 | "polyfill", 111 | "portable" 112 | ] 113 | } 114 | ] 115 | -------------------------------------------------------------------------------- /classes/Collection/SSECollection.php: -------------------------------------------------------------------------------- 1 | 11 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 12 | * @link https://github.com/OleVik/grav-plugin-static-generator 13 | */ 14 | namespace Grav\Plugin\StaticGenerator\Collection; 15 | 16 | use Grav\Common\Grav; 17 | use Grav\Framework\Cache\Adapter\FileStorage; 18 | use Grav\Plugin\StaticGenerator\Timer; 19 | use Grav\Plugin\StaticGenerator\Config\Config; 20 | use Grav\Plugin\StaticGenerator\Collection\AbstractCollection; 21 | 22 | /** 23 | * Server Sent Collection Builder 24 | * 25 | * @category API 26 | * @package Grav\Plugin\StaticGenerator\Collection\SSECollection 27 | * @author Ole Vik 28 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 29 | * @link https://github.com/OleVik/grav-plugin-static-generator 30 | */ 31 | class SSECollection extends AbstractCollection 32 | { 33 | /** 34 | * Bootstrap data 35 | * 36 | * @return void 37 | */ 38 | public function setup(string $preset): void 39 | { 40 | $this->grav = Grav::instance(); 41 | if (isset($this->grav['admin'])) { 42 | if (method_exists(Grav::instance()['admin'], 'enablePages')) { 43 | $this->grav['admin']->enablePages(); 44 | } 45 | } 46 | $this->grav['streams']; 47 | $this->grav['config']->init(); 48 | $this->grav['themes']->init(); 49 | $this->grav['twig']->init(); 50 | $this->grav['pages']->init(); 51 | $this->grav['assets']->init(); 52 | $this->grav['config']->set('system.cache.enabled', false); 53 | $this->pages = $this->grav['page']->evaluate([$this->collection => $this->route]); 54 | $this->count = $this->count(); 55 | echo 'event: update' . "\n\n"; 56 | echo 'data: ' . json_encode( 57 | [ 58 | 'datetime' => date(DATE_ISO8601), 59 | 'total' => $this->count 60 | ] 61 | ) . "\n\n"; 62 | } 63 | 64 | public function collection() 65 | { 66 | foreach ($this->pages as $Page) { 67 | $content = $Grav['twig']->processTemplate( 68 | $Page->template() . '.' . $Page->templateFormat('html') . '.twig', 69 | ['page' => $Page] 70 | ); 71 | echo 'event: update' . "\n\n"; 72 | echo 'data: ' . json_encode( 73 | [ 74 | 'datetime' => date(DATE_ISO8601), 75 | 'title' => $Page->title(), 76 | 'format' => $Page->template() . '.' . $Page->templateFormat('html') . '.twig', 77 | 'content' => $content 78 | ] 79 | ) . "\n\n"; 80 | } 81 | } 82 | 83 | /** 84 | * Report results 85 | * 86 | * @param array $items Items to report 87 | * 88 | * @return void 89 | */ 90 | public function reporter(array $items): void 91 | { 92 | $message = ucfirst( 93 | Grav::instance()['language']->translate( 94 | ['PLUGIN_STATIC_GENERATOR.ADMIN.GENERIC.STORED'] 95 | ) 96 | ) . ' "' . $items['item'] . '" ' . 97 | Grav::instance()['language']->translate( 98 | ['PLUGIN_STATIC_GENERATOR.ADMIN.GENERIC.IN'] 99 | ) . ' ' . $items['location'] . ' ' . 100 | Grav::instance()['language']->translate( 101 | ['PLUGIN_STATIC_GENERATOR.ADMIN.GENERIC.IN'] 102 | ) . ' ' . $items['time'] . '.'; 103 | echo 'event: update' . "\n\n"; 104 | echo 'data: ' . json_encode( 105 | [ 106 | 'datetime' => date(DATE_ISO8601), 107 | 'content' => $message, 108 | 'text' => $items['item'], 109 | 'value' => $items['location'] 110 | ] 111 | ) . "\n\n"; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /classes/Data/TestData.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/OleVik/grav-plugin-static-generator 14 | */ 15 | 16 | namespace Grav\Plugin\StaticGenerator\Data; 17 | 18 | use Grav\Plugin\StaticGenerator\Data\AbstractData; 19 | 20 | /** 21 | * CLI Data Tester 22 | * 23 | * @category API 24 | * @package Grav\Plugin\StaticGenerator\Data\TestData 25 | * @author Ole Vik 26 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 27 | * @link https://github.com/OleVik/grav-plugin-static-generator 28 | */ 29 | class TestData extends AbstractData 30 | { 31 | /** 32 | * Initialize 33 | * 34 | * @param string $route Route to page. 35 | * 36 | * @return void 37 | */ 38 | public function bootstrap($route) 39 | { 40 | if ($route == '/') { 41 | $this->pages = $this->grav['page']->evaluate(['@root.descendants']); 42 | } else { 43 | $this->pages = $this->grav['page']->evaluate(['@page.descendants' => $route]); 44 | } 45 | $this->count = $this->count(); 46 | } 47 | 48 | /** 49 | * Create data-structure recursively 50 | * 51 | * @param string $route Route to page. 52 | * @param string $mode Placeholder for operation-mode, private. 53 | * @param int $depth Placeholder for recursion depth, private. 54 | * 55 | * @return mixed Index of Pages with FrontMatter 56 | */ 57 | public function index(string $route, string $mode = '', int $depth = 0) 58 | { 59 | $depth++; 60 | $mode = '@page.self'; 61 | if ($route == '/') { 62 | $mode = '@root.descendants'; 63 | } 64 | if ($depth > 1) { 65 | $mode = '@page.children'; 66 | } 67 | $pages = $this->grav['page']->evaluate([$mode => $route]); 68 | $pages = $pages->order($this->orderBy, $this->orderDir); 69 | foreach ($pages as $page) { 70 | $route = $page->rawRoute(); 71 | $item = array( 72 | 'title' => $page->title(), 73 | 'date' => \DateTime::createFromFormat('U', $page->date())->format('c'), 74 | 'url' => $page->url(true, true, true), 75 | 'taxonomy' => array( 76 | 'categories' => array(), 77 | 'tags' => array() 78 | ) 79 | ); 80 | if (isset($page->taxonomy()['category'])) { 81 | $item['taxonomy']['categories'] = array_merge( 82 | $item['taxonomy']['categories'], 83 | $page->taxonomy()['category'] 84 | ); 85 | } 86 | if (isset($page->taxonomy()['categories'])) { 87 | $item['taxonomy']['categories'] = array_merge( 88 | $item['taxonomy']['categories'], 89 | $page->taxonomy()['categories'] 90 | ); 91 | } 92 | if (isset($page->taxonomy()['tags'])) { 93 | $item['taxonomy']['tags'] = array_merge( 94 | $item['taxonomy']['tags'], 95 | $page->taxonomy()['tags'] 96 | ); 97 | } 98 | if (!empty($page->media()->all())) { 99 | $item['media'] = array_keys($page->media()->all()); 100 | } 101 | try { 102 | $pageContent = $this->content($page) ?? 0; 103 | } catch (\Exception $error) { 104 | throw new \Exception($error); 105 | } 106 | echo '[' . $this->progress . '/' . $this->count . '] ' . 107 | $item['title'] . ' (' . strlen($pageContent) . " characters)\n"; 108 | $this->data[] = (object) $item; 109 | $this->progress(); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "ef32ed51e00ac7f90de7e9d28d165503", 8 | "packages": [ 9 | { 10 | "name": "symfony/filesystem", 11 | "version": "v4.4.5", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/symfony/filesystem.git", 15 | "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/266c9540b475f26122b61ef8b23dd9198f5d1cfd", 20 | "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": "^7.1.3", 25 | "symfony/polyfill-ctype": "~1.8" 26 | }, 27 | "type": "library", 28 | "extra": { 29 | "branch-alias": { 30 | "dev-master": "4.4-dev" 31 | } 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Symfony\\Component\\Filesystem\\": "" 36 | }, 37 | "exclude-from-classmap": [ 38 | "/Tests/" 39 | ] 40 | }, 41 | "notification-url": "https://packagist.org/downloads/", 42 | "license": [ 43 | "MIT" 44 | ], 45 | "authors": [ 46 | { 47 | "name": "Fabien Potencier", 48 | "email": "fabien@symfony.com" 49 | }, 50 | { 51 | "name": "Symfony Community", 52 | "homepage": "https://symfony.com/contributors" 53 | } 54 | ], 55 | "description": "Symfony Filesystem Component", 56 | "homepage": "https://symfony.com", 57 | "time": "2020-01-21T08:20:44+00:00" 58 | }, 59 | { 60 | "name": "symfony/polyfill-ctype", 61 | "version": "v1.14.0", 62 | "source": { 63 | "type": "git", 64 | "url": "https://github.com/symfony/polyfill-ctype.git", 65 | "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38" 66 | }, 67 | "dist": { 68 | "type": "zip", 69 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", 70 | "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", 71 | "shasum": "" 72 | }, 73 | "require": { 74 | "php": ">=5.3.3" 75 | }, 76 | "suggest": { 77 | "ext-ctype": "For best performance" 78 | }, 79 | "type": "library", 80 | "extra": { 81 | "branch-alias": { 82 | "dev-master": "1.14-dev" 83 | } 84 | }, 85 | "autoload": { 86 | "psr-4": { 87 | "Symfony\\Polyfill\\Ctype\\": "" 88 | }, 89 | "files": [ 90 | "bootstrap.php" 91 | ] 92 | }, 93 | "notification-url": "https://packagist.org/downloads/", 94 | "license": [ 95 | "MIT" 96 | ], 97 | "authors": [ 98 | { 99 | "name": "Gert de Pagter", 100 | "email": "BackEndTea@gmail.com" 101 | }, 102 | { 103 | "name": "Symfony Community", 104 | "homepage": "https://symfony.com/contributors" 105 | } 106 | ], 107 | "description": "Symfony polyfill for ctype functions", 108 | "homepage": "https://symfony.com", 109 | "keywords": [ 110 | "compatibility", 111 | "ctype", 112 | "polyfill", 113 | "portable" 114 | ], 115 | "time": "2020-01-13T11:15:53+00:00" 116 | } 117 | ], 118 | "packages-dev": [], 119 | "aliases": [], 120 | "minimum-stability": "stable", 121 | "stability-flags": [], 122 | "prefer-stable": false, 123 | "prefer-lowest": false, 124 | "platform": [], 125 | "platform-dev": [] 126 | } 127 | -------------------------------------------------------------------------------- /classes/Assets.php: -------------------------------------------------------------------------------- 1 | 10 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 11 | * @link https://github.com/OleVik/grav-plugin-static-generator 12 | */ 13 | namespace Grav\Plugin\StaticGenerator; 14 | 15 | use Grav\Common\Grav; 16 | use Grav\Common\Utils; 17 | use Symfony\Component\Filesystem\Filesystem; 18 | use Symfony\Component\Filesystem\Exception\IOExceptionInterface; 19 | 20 | /** 21 | * Assets Builder 22 | * 23 | * @category API 24 | * @package Grav\Plugin\StaticGenerator\Assets 25 | * @author Ole Vik 26 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 27 | * @link https://github.com/OleVik/grav-plugin-static-generator 28 | */ 29 | class Assets 30 | { 31 | public $streams; 32 | public $schemes; 33 | 34 | /** 35 | * Initialize class 36 | * 37 | * @param Filesystem $Filesystem Instance of Symfony\Component\Filesystem\Filesystem. 38 | * @param Timer $Timer Instance of Grav\Plugin\StaticGenerator\Timer. 39 | */ 40 | public function __construct(Filesystem $Filesystem, Timer $Timer, bool $offline = false) 41 | { 42 | $this->streams = array(); 43 | $this->schemes = array(); 44 | $this->Filesystem = $Filesystem; 45 | $this->Timer = $Timer; 46 | $this->offline = $offline; 47 | $this->grav = Grav::instance(); 48 | foreach ($this->grav['locator']->getSchemes() as $stream) { 49 | $this->schemes[$stream] = Utils::url($stream . '://'); 50 | } 51 | foreach ($this->grav['streams']->getStreams() as $name => $stream) { 52 | $this->streams[$name] = Utils::url($name . '://'); 53 | } 54 | } 55 | 56 | /** 57 | * Copy Asset 58 | * 59 | * @param string $asset Asset to copy. 60 | * @param string $location Location to store asset in. 61 | * @param boolean $force Forcefully save. 62 | * 63 | * @return array Result 64 | */ 65 | public function copy( 66 | string $asset, 67 | string $location, 68 | bool $force 69 | ): array { 70 | if (empty($asset)) { 71 | return []; 72 | } 73 | $location = $location . DS . 'assets'; 74 | if (Utils::startsWith($asset, '/user')) { 75 | $target = $location . $asset; 76 | $source = GRAV_ROOT . $asset; 77 | } elseif (Utils::startsWith($asset, '/system')) { 78 | $target = $location . $asset; 79 | $source = GRAV_ROOT . $asset; 80 | } elseif (Utils::startsWith($asset, '//')) { 81 | if ($this->offline === true) { 82 | return []; 83 | } 84 | $url = parse_url($asset); 85 | $target = $location . DS . $url['host'] . $url['path']; 86 | $source = 'https://' . $url['host'] . $url['path']; 87 | } else { 88 | if ($this->offline === true) { 89 | return []; 90 | } 91 | $url = parse_url($asset); 92 | $target = $location . DS . $url['host'] . $url['path']; 93 | $source = $asset; 94 | } 95 | try { 96 | if ($force) { 97 | $this->Filesystem->remove($target); 98 | } 99 | $this->Filesystem->copy($source, $target); 100 | return [ 101 | 'item' => basename($asset), 102 | 'location' => $target, 103 | 'time' => Timer::format($this->Timer->getTime()), 104 | ]; 105 | } catch (\Exception $e) { 106 | throw new \Exception($e); 107 | } 108 | } 109 | 110 | /** 111 | * Copy Media 112 | * 113 | * @param string $filename Name of file to copy. 114 | * @param string $path Path of file to copy. 115 | * @param string $location Location to storage media in. 116 | * @param boolean $force Forcefully save data. 117 | * 118 | * @return array Result 119 | */ 120 | public function copyMedia(string $filename, string $path, string $location, bool $force): array 121 | { 122 | if (empty($filename) || empty($path)) { 123 | return []; 124 | } 125 | $location = rtrim($location, '//') . DS; 126 | try { 127 | if ($force) { 128 | $this->Filesystem->remove($location . $filename); 129 | } 130 | $this->Filesystem->copy($path . DS . $filename, $location . $filename); 131 | return [ 132 | 'item' => $filename, 133 | 'location' => $location . $filename, 134 | 'time' => Timer::format($this->Timer->getTime()), 135 | ]; 136 | } catch (\Exception $e) { 137 | throw new \Exception($e); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /classes/Data/CommandLineData.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/OleVik/grav-plugin-static-generator 14 | */ 15 | 16 | namespace Grav\Plugin\StaticGenerator\Data; 17 | 18 | use Grav\Common\Grav; 19 | use Symfony\Component\Console\Helper\ProgressBar; 20 | use Grav\Plugin\StaticGenerator\Data\AbstractData; 21 | 22 | /** 23 | * CLI Data Builder 24 | * 25 | * @category API 26 | * @package Grav\Plugin\StaticGenerator\Data\CommandLineData 27 | * @author Ole Vik 28 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 29 | * @link https://github.com/OleVik/grav-plugin-static-generator 30 | */ 31 | class CommandLineData extends AbstractData 32 | { 33 | /** 34 | * Initialize 35 | * 36 | * @param string $route Route to page. 37 | * @param \Symfony\Component\Console\Output $handle Console-wrapper. 38 | * 39 | * @return void 40 | */ 41 | public function bootstrap($route, $handle) 42 | { 43 | parent::setup(); 44 | if ($route == '/') { 45 | $this->pages = $this->grav['page']->evaluate(['@root.descendants']); 46 | } else { 47 | $this->pages = $this->grav['page']->evaluate(['@page.descendants' => $route]); 48 | } 49 | $this->count = $this->count(); 50 | $this->progress = new ProgressBar($handle, $this->count); 51 | } 52 | 53 | /** 54 | * Increase counter 55 | * 56 | * @return void 57 | */ 58 | public function progress(): void 59 | { 60 | $this->progress->advance(); 61 | } 62 | 63 | /** 64 | * Create data-structure recursively 65 | * 66 | * @param string $route Route to page. 67 | * @param string $mode Placeholder for operation-mode, private. 68 | * @param int $depth Placeholder for recursion depth, private. 69 | * 70 | * @return mixed Index of Pages with FrontMatter 71 | */ 72 | public function index(string $route, string $mode = '', int $depth = 0) 73 | { 74 | $depth++; 75 | $mode = '@page.self'; 76 | if ($route == '/') { 77 | $mode = '@root.children'; 78 | } 79 | if ($depth > 1) { 80 | $mode = '@page.children'; 81 | } 82 | $pages = $this->grav['page']->evaluate([$mode => $route]); 83 | $pages = $pages->order($this->orderBy, $this->orderDir); 84 | foreach ($pages as $page) { 85 | $route = $page->rawRoute(); 86 | $item = array( 87 | 'title' => $page->title(), 88 | 'date' => \DateTime::createFromFormat('U', $page->date())->format('c'), 89 | 'url' => $page->url(true, true, true), 90 | 'taxonomy' => array( 91 | 'categories' => array(), 92 | 'tags' => array() 93 | ) 94 | ); 95 | if (isset($page->taxonomy()['category'])) { 96 | $item['taxonomy']['categories'] = array_merge( 97 | $item['taxonomy']['categories'], 98 | $page->taxonomy()['category'] 99 | ); 100 | } 101 | if (isset($page->taxonomy()['categories'])) { 102 | $item['taxonomy']['categories'] = array_merge( 103 | $item['taxonomy']['categories'], 104 | $page->taxonomy()['categories'] 105 | ); 106 | } 107 | if (isset($page->taxonomy()['tags'])) { 108 | $item['taxonomy']['tags'] = array_merge( 109 | $item['taxonomy']['tags'], 110 | $page->taxonomy()['tags'] 111 | ); 112 | } 113 | if (!empty($page->media()->all())) { 114 | $item['media'] = array_keys($page->media()->all()); 115 | } 116 | if (!$this->content) { 117 | $item['taxonomy']['categories'] = implode(' ', $item['taxonomy']['categories']); 118 | $item['taxonomy']['tags'] = implode(' ', $item['taxonomy']['tags']); 119 | if (isset($item['media']) && is_array($item['media'])) { 120 | $item['media'] = implode(' ', $item['media']); 121 | } 122 | } else { 123 | try { 124 | $pageContent = $this->content($page); 125 | if (!empty($pageContent) && strlen($pageContent) <= $this->maxLength) { 126 | $item['content'] = $pageContent; 127 | } 128 | } catch (Exception $error) { 129 | throw new Exception($error); 130 | } 131 | } 132 | if (count($page->children()) > 0) { 133 | $this->index($route, $mode, $depth); 134 | } 135 | $this->data[] = (object) $item; 136 | $this->progress(); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /classes/Collection/CommandLineCollection.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/OleVik/grav-plugin-static-generator 14 | */ 15 | 16 | namespace Grav\Plugin\StaticGenerator\Collection; 17 | 18 | use Grav\Common\Grav; 19 | use Symfony\Component\Console\Output\ConsoleOutput; 20 | use Symfony\Component\Console\Helper\ProgressBar; 21 | use Grav\Plugin\StaticGenerator\Collection\AbstractCollection; 22 | 23 | /** 24 | * CLI Collection Builder 25 | * 26 | * @category API 27 | * @package Grav\Plugin\StaticGenerator\Collection\CommandLineCollection 28 | * @author Ole Vik 29 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 30 | * @link https://github.com/OleVik/grav-plugin-static-generator 31 | */ 32 | class CommandLineCollection extends AbstractCollection 33 | { 34 | /** 35 | * Bootstrap processor 36 | * 37 | * @param \Symfony\Component\Console\Output\OutputInterface $handle Console-wrapper. 38 | * 39 | * @return void 40 | */ 41 | public function handler(\Symfony\Component\Console\Output\OutputInterface $handle) 42 | { 43 | $this->handle = $handle; 44 | } 45 | 46 | /** 47 | * Bootstrap data, events, and helpers 48 | * 49 | * @param string $preset Name of Config Preset to load. 50 | * 51 | * @return void 52 | */ 53 | public function setup(string $preset, $offline): void 54 | { 55 | parent::setup($preset, $offline); 56 | $this->progress = new ProgressBar($this->handle, $this->count); 57 | } 58 | 59 | /** 60 | * Increase counter 61 | * 62 | * @return void 63 | */ 64 | public function progress(): void 65 | { 66 | $this->progress->advance(); 67 | } 68 | 69 | public function reporter(array $parts, string $itemColor = 'white'): void 70 | { 71 | if (!empty($parts)) { 72 | $message = array(); 73 | if (isset($parts['item'])) { 74 | $message[] = '<' . $itemColor . '>' . $parts['item'] . ''; 75 | } 76 | if (isset($parts['location'])) { 77 | $message[] = '' . $parts['location'] . ''; 78 | } 79 | if (isset($parts['time'])) { 80 | $message[] = '' . $parts['time'] . ''; 81 | } 82 | $this->handle->writeln(implode("\n", $message)); 83 | $this->handle->writeln(''); 84 | } 85 | } 86 | 87 | /** 88 | * Finish progress-counter 89 | * 90 | * @param string $message Exit-message 91 | * 92 | * @return void 93 | */ 94 | public function teardown(string $message = '') 95 | { 96 | $this->progressBar->finish(); 97 | if (!empty($message)) { 98 | $this->handle->writeln("\n" . '' . $message . ''); 99 | } 100 | } 101 | 102 | /** 103 | * Build and store Page(s) 104 | * 105 | * @return void 106 | */ 107 | public function collection(): void 108 | { 109 | $this->handle->writeln('Processing Page(s): ' . $this->count . ''); 110 | $this->progressBar = new ProgressBar( 111 | $this->handle, 112 | $this->count 113 | ); 114 | $this->buildCollection(); 115 | $this->teardown('Finished ' . $this->count . ' Page(s)'); 116 | } 117 | 118 | /** 119 | * Capture and store Asset(s) 120 | * 121 | * @return void 122 | */ 123 | public function assets(): void 124 | { 125 | $count = count($this->grav['assets']['assets_css']) + count($this->grav['assets']['assets_js']); 126 | $this->handle->writeln(''); 127 | $this->handle->writeln('Processing Asset(s): ' . $count . ''); 128 | $this->progressBar = new ProgressBar( 129 | $this->handle, 130 | $count 131 | ); 132 | $this->buildAssets(); 133 | $this->teardown('Finished ' . $count . ' Asset(s)'); 134 | } 135 | 136 | /** 137 | * Capture and store Static Asset(s) 138 | * 139 | * @return void 140 | */ 141 | /** */ 142 | public function staticAssets(): void 143 | { 144 | $this->handle->writeln(''); 145 | $this->handle->writeln('Mirroring Static Asset(s)'); 146 | $this->mirrorStaticAssets(); 147 | $this->teardown('Finished mirroring Static Asset'); 148 | } 149 | 150 | /** 151 | * Mirror images 152 | * 153 | * @param boolean $force Forcefully save data. 154 | * 155 | * @return void 156 | */ 157 | public function images(bool $force): void 158 | { 159 | $this->handle->writeln(''); 160 | $this->handle->writeln('Mirroring generated Images'); 161 | $this->mirrorImages($force); 162 | $this->teardown('Finished mirroring generated Images'); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /cli/GenerateStaticIndexCommand.php: -------------------------------------------------------------------------------- 1 | 11 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 12 | * @link https://github.com/OleVik/grav-theme-scholar 13 | */ 14 | 15 | namespace Grav\Plugin\Console; 16 | 17 | use Grav\Common\Grav; 18 | use Grav\Common\Utils; 19 | use Grav\Console\ConsoleCommand; 20 | use Symfony\Component\Console\Input\InputArgument; 21 | use Symfony\Component\Console\Input\InputOption; 22 | use Grav\Framework\Cache\Adapter\FileStorage; 23 | use Grav\Plugin\StaticGenerator\Data\CommandLineData; 24 | use Grav\Plugin\StaticGenerator\Timer; 25 | 26 | /** 27 | * Data Index Builder 28 | * 29 | * Command line utility for storing Pages data as JSON 30 | * 31 | * @category API 32 | * @package Grav\Plugin\Console\GenerateStaticIndexCommand 33 | * @author Ole Vik 34 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 35 | * @link https://github.com/OleVik/grav-theme-scholar 36 | */ 37 | class GenerateStaticIndexCommand extends ConsoleCommand 38 | { 39 | /** 40 | * Command definitions 41 | * 42 | * @return void 43 | */ 44 | protected function configure() 45 | { 46 | $this 47 | ->setName("index") 48 | ->setDescription("Generates and stores Pages index.") 49 | ->setHelp('The index-command generates and stores Pages index.') 50 | ->addArgument( 51 | 'route', 52 | InputArgument::REQUIRED, 53 | 'The route to the page' 54 | ) 55 | ->addArgument( 56 | 'target', 57 | InputArgument::OPTIONAL, 58 | 'Override target-option or set a custom destination' 59 | ) 60 | ->addOption( 61 | 'url', 62 | 'u', 63 | InputOption::VALUE_OPTIONAL, 64 | 'Base URL override', 65 | '' 66 | ) 67 | ->addOption( 68 | 'basename', 69 | 'b', 70 | InputOption::VALUE_OPTIONAL, 71 | 'Index basename', 72 | 'index' 73 | ) 74 | ->addOption( 75 | 'content', 76 | 'c', 77 | InputOption::VALUE_NONE, 78 | 'Include Page content' 79 | ) 80 | ->addOption( 81 | 'echo', 82 | 'e', 83 | InputOption::VALUE_NONE, 84 | 'Outputs result directly' 85 | ) 86 | ->addOption( 87 | 'wrap', 88 | 'w', 89 | InputOption::VALUE_NONE, 90 | 'Wraps JSON as a JavaScript global' 91 | ) 92 | ->addOption( 93 | 'force', 94 | 'f', 95 | InputOption::VALUE_NONE, 96 | 'Forcefully save data' 97 | ); 98 | } 99 | 100 | /** 101 | * Build and save data index 102 | * 103 | * @return void 104 | */ 105 | protected function serve() 106 | { 107 | $timer = new Timer(); 108 | $config = Grav::instance()['config']->get('plugins.static-generator'); 109 | $locator = Grav::instance()['locator']; 110 | $route = $this->input->getArgument('route'); 111 | $target = $this->input->getArgument('target'); 112 | if ($target === null) { 113 | $target = $config['index']; 114 | } 115 | $url = $this->input->getOption('url'); 116 | $basename = $this->input->getOption('basename'); 117 | $content = $this->input->getOption('content'); 118 | $echo = $this->input->getOption('echo'); 119 | $wrap = $this->input->getOption('wrap'); 120 | $force = $this->input->getOption('force'); 121 | $maxLength = $config['content_max_length']; 122 | $this->output->writeln('Generating data index'); 123 | try { 124 | parent::initializePages(); 125 | if (Utils::contains($target, '://')) { 126 | $scheme = parse_url($target, PHP_URL_SCHEME); 127 | $location = $locator->findResource($scheme . '://') . str_replace($scheme . '://', '/', $target); 128 | } else { 129 | $this->output->writeln('Target must be a valid stream resource, prefixing one of:'); 130 | foreach ($locator->getSchemes() as $scheme) { 131 | $this->output->writeln($scheme . '://'); 132 | } 133 | return; 134 | } 135 | $Data = new CommandLineData($url, $content, $maxLength); 136 | $Data->bootstrap($route, $this->output); 137 | $Data->index($route); 138 | if ($echo) { 139 | echo json_encode($Data->data, JSON_PRETTY_PRINT); 140 | } else { 141 | $extension = '.json'; 142 | if ($content) { 143 | $basename = $basename . '.full'; 144 | } 145 | if ($wrap) { 146 | $extension = '.js'; 147 | } 148 | $Storage = new FileStorage($location); 149 | $file = $basename . $extension; 150 | if ($force && $Storage->doHas($file)) { 151 | $Storage->doDelete($file); 152 | } 153 | if ($wrap && !$content) { 154 | $Storage->doSet($file, 'const GravMetadataIndex = ' . json_encode($Data->data) . ';', 0); 155 | } elseif ($wrap && $content) { 156 | $Storage->doSet($file, 'const GravDataIndex = ' . json_encode($Data->data) . ';', 0); 157 | } else { 158 | $Storage->doSet($file, json_encode($Data->data), 0); 159 | } 160 | $this->output->writeln(''); 161 | $this->output->writeln( 162 | 'Saved ' . count($Data->data) 163 | . ' items to ' 164 | . $location . '/' . $file . ' in ' 165 | . Timer::format($timer->getTime()) . '.' 166 | ); 167 | } 168 | } catch (\Exception $e) { 169 | throw new \Exception($e); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /classes/Config/Config.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/OleVik/grav-plugin-static-generator 14 | */ 15 | 16 | namespace Grav\Plugin\StaticGenerator\Config; 17 | 18 | use Grav\Common\Grav; 19 | use Grav\Common\Utils; 20 | use Grav\Framework\File\YamlFile; 21 | use Grav\Framework\File\Formatter\YamlFormatter; 22 | use Grav\Plugin\StaticGenerator\Config\AbstractConfig; 23 | use Symfony\Component\Filesystem\Filesystem; 24 | use Symfony\Component\Filesystem\Exception\IOExceptionInterface; 25 | 26 | /** 27 | * Config 28 | * 29 | * @category API 30 | * @package Grav\Plugin\StaticGenerator\Config\Config 31 | * @author Ole Vik 32 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 33 | * @link https://github.com/OleVik/grav-plugin-static-generator 34 | */ 35 | class Config extends AbstractConfig 36 | { 37 | /** 38 | * Mirror Config 39 | * 40 | * @param string $target Location to store Config in. 41 | * @param string $source Source to copy from. 42 | * @param boolean $force Forcefully save. 43 | * 44 | * @return boolean Result 45 | */ 46 | public static function mirror( 47 | string $target, 48 | string $source, 49 | bool $force = false 50 | ): bool { 51 | try { 52 | $Filesystem = new Filesystem(); 53 | if ($Filesystem->exists($target)) { 54 | if ($force) { 55 | $Filesystem->remove($target); 56 | } else { 57 | return false; 58 | } 59 | } 60 | $Filesystem->mkdir($target); 61 | $Filesystem->mirror( 62 | $source, 63 | $target, 64 | null, 65 | [ 66 | 'override' => true, 67 | 'copy_on_windows' => true, 68 | 'delete' => true 69 | ] 70 | ); 71 | return true; 72 | } catch (\Exception $e) { 73 | throw new \Exception($e); 74 | } 75 | } 76 | 77 | /** 78 | * Create and store Preset 79 | * 80 | * @param string $name Preset name. 81 | * @param array $parameters Preset parameters. 82 | * @param boolean $force Forcefully save. 83 | * 84 | * @return int Result 85 | */ 86 | public static function addPreset( 87 | string $name, 88 | array $parameters, 89 | bool $force = false 90 | ): int { 91 | try { 92 | $Grav = Grav::instance(); 93 | $formatter = new YamlFormatter; 94 | $file = new YamlFile( 95 | $Grav['locator']->findResource( 96 | 'config://plugins/static-generator.yaml', 97 | true, 98 | true 99 | ), 100 | $formatter 101 | ); 102 | $file->lock(); 103 | $config = $Grav['config']->get('plugins.static-generator'); 104 | if (!isset($config['presets']) 105 | && !is_array($config['presets']) 106 | ) { 107 | $file->unlock(); 108 | return 2; 109 | } 110 | foreach ($config['presets'] as $index => $value) { 111 | if (isset($value['name']) && $value['name'] == $name) { 112 | if ($force) { 113 | $config['presets'][$index] = [ 114 | 'name' => $name, 115 | 'parameters' => $parameters 116 | ]; 117 | $file->save($config); 118 | $file->unlock(); 119 | return 3; 120 | } else { 121 | $file->unlock(); 122 | return 4; 123 | } 124 | } 125 | } 126 | $config['presets'][] = [ 127 | 'name' => $name, 128 | 'parameters' => $parameters 129 | ]; 130 | $file->save($config); 131 | $file->unlock(); 132 | return 1; 133 | } catch (\Exception $e) { 134 | throw new \Exception($e); 135 | } 136 | } 137 | 138 | /** 139 | * Apply parameters to Config or Twig 140 | * 141 | * @param object $config Instance of Grav\Common\Config\Config. 142 | * @param object $twig Instance of Grav\Common\Twig\Twig. 143 | * @param array $parameters Parameters to apply. 144 | * 145 | * @return void 146 | */ 147 | public static function applyParameters( 148 | object $config, 149 | object $twig, 150 | array $parameters 151 | ): void { 152 | if (empty($parameters)) { 153 | return; 154 | } 155 | foreach ($parameters as $parameter => $value) { 156 | if (Utils::startsWith('twig.', $parameter, false)) { 157 | $twig->twig_vars[end(explode('.', $parameter))] = $value; 158 | } else { 159 | $config->set($parameter, $value); 160 | } 161 | } 162 | } 163 | 164 | /** 165 | * Get Preset-parameters 166 | * 167 | * @param object $config Instance of Grav\Common\Config\Config. 168 | * @param string $preset Name of Preset. 169 | * 170 | * @return array Preset-parameters 171 | */ 172 | public static function getPresetParameters(object $config, string $preset): array 173 | { 174 | if ($config->get('plugins.static-generator.presets') !== null 175 | && !empty($config->get('plugins.static-generator.presets')) 176 | ) { 177 | $key = array_search( 178 | $preset, 179 | array_column( 180 | $config->get('plugins.static-generator.presets'), 181 | 'name' 182 | ) 183 | ); 184 | if ($key 185 | && isset($config->get('plugins.static-generator.presets')[$key]['parameters']) 186 | && !empty($config->get('plugins.static-generator.presets')[$key]['parameters']) 187 | ) { 188 | return $config->get('plugins.static-generator.presets')[$key]['parameters']; 189 | } 190 | } 191 | return []; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /cli/GenerateStaticPageCommand.php: -------------------------------------------------------------------------------- 1 | 11 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 12 | * @link https://github.com/OleVik/grav-theme-scholar 13 | */ 14 | 15 | namespace Grav\Plugin\Console; 16 | 17 | use Grav\Common\Grav; 18 | use Grav\Common\Utils; 19 | use Grav\Console\ConsoleCommand; 20 | use Symfony\Component\Console\Input\InputArgument; 21 | use Symfony\Component\Console\Input\InputOption; 22 | use Grav\Plugin\StaticGenerator\Collection\CommandLineCollection; 23 | use Grav\Plugin\StaticGenerator\Timer; 24 | 25 | /** 26 | * StaticGenerator Page Builder 27 | * 28 | * Command line utility for storing Pages data as HTML 29 | * 30 | * @category API 31 | * @package Grav\Plugin\Console\GenerateStaticPageCommand 32 | * @author Ole Vik 33 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 34 | * @link https://github.com/OleVik/grav-theme-scholar 35 | */ 36 | class GenerateStaticPageCommand extends ConsoleCommand 37 | { 38 | /** 39 | * Command definitions 40 | * 41 | * @return void 42 | */ 43 | protected function configure() 44 | { 45 | $this 46 | ->setName("page") 47 | ->setDescription("Generates and stores Page(s) as HTML.") 48 | ->setHelp('The page-command generates and stores Page(s) as HTML.') 49 | ->addArgument( 50 | 'route', 51 | InputArgument::OPTIONAL, 52 | 'The route to the page' 53 | ) 54 | ->addArgument( 55 | 'collection', 56 | InputArgument::OPTIONAL, 57 | 'The Page Collection to store (see https://learn.getgrav.org/16/content/collections#collection-headers)' 58 | ) 59 | ->addArgument( 60 | 'target', 61 | InputArgument::OPTIONAL, 62 | 'Override target-option or set a custom destination' 63 | ) 64 | ->addOption( 65 | 'preset', 66 | 'p', 67 | InputOption::VALUE_OPTIONAL, 68 | 'Name of Config preset' 69 | ) 70 | ->addOption( 71 | 'assets', 72 | 'a', 73 | InputOption::VALUE_NONE, 74 | 'Include Assets' 75 | ) 76 | ->addOption( 77 | 'root-prefix', 78 | 'r', 79 | InputArgument::OPTIONAL, 80 | 'Root prefix for assets and images' 81 | ) 82 | ->addOption( 83 | 'static-assets', 84 | 's', 85 | InputOption::VALUE_NONE, 86 | 'Include Static Assets' 87 | ) 88 | ->addOption( 89 | 'images', 90 | 'i', 91 | InputOption::VALUE_NONE, 92 | 'Include Images' 93 | ) 94 | ->addOption( 95 | 'offline', 96 | 'o', 97 | InputOption::VALUE_NONE, 98 | 'Force offline-mode' 99 | ) 100 | ->addOption( 101 | 'filter', 102 | 'f', 103 | InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 104 | 'Methods for filtering' 105 | ) 106 | ->addOption( 107 | 'parameters', 108 | 'd', 109 | InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 110 | 'Key-value pairs to assign to Twig or Config' 111 | ) 112 | ->addOption( 113 | 'force', 114 | null, 115 | InputOption::VALUE_NONE, 116 | 'Forcefully save data' 117 | ); 118 | } 119 | 120 | /** 121 | * Build and save data index 122 | * 123 | * @return void 124 | */ 125 | protected function serve(): void 126 | { 127 | $config = Grav::instance()['config']->get('plugins.static-generator'); 128 | $locator = Grav::instance()['locator']; 129 | $route = $this->input->getArgument('route') ?? '/'; 130 | $collection = $this->input->getArgument('collection') ?? '@page.self'; 131 | $target = $this->input->getArgument('target'); 132 | if ($target === null) { 133 | $target = $config['content']; 134 | } 135 | $preset = $this->input->getOption('preset') ?? ''; 136 | $assets = $this->input->getOption('assets'); 137 | $rootPrefix = $this->input->getOption('root-prefix') ?? '/'; 138 | $mirrorAssets = $this->input->getOption('static-assets'); 139 | $mirrorImages = $this->input->getOption('images'); 140 | $offline = $this->input->getOption('offline'); 141 | $filters = $this->input->getOption('filter'); 142 | $parameters = $this->input->getOption('parameters'); 143 | $force = $this->input->getOption('force'); 144 | $maxLength = $config['content_max_length']; 145 | try { 146 | parent::initializePages(); 147 | if (Utils::contains($target, '://')) { 148 | $scheme = parse_url($target, PHP_URL_SCHEME); 149 | $location = $locator->findResource($scheme . '://') . 150 | str_replace($scheme . '://', '/', $target); 151 | } else { 152 | $this->output->writeln( 153 | 'Target must be a valid stream resource, prefixing one of:' 154 | ); 155 | foreach ($locator->getSchemes() as $scheme) { 156 | $this->output->writeln($scheme . '://'); 157 | } 158 | return; 159 | } 160 | $Collection = new CommandLineCollection( 161 | $collection, 162 | $route, 163 | $location, 164 | $force, 165 | $rootPrefix, 166 | $filters, 167 | $parameters 168 | ); 169 | $Collection->handler($this->output); 170 | $Collection->setup($preset, $offline); 171 | $Collection->collection(); 172 | if ($assets) { 173 | $Collection->assets(); 174 | } 175 | if ($mirrorAssets) { 176 | $Collection->staticAssets($force); 177 | } 178 | if ($mirrorImages) { 179 | $Collection->images($force); 180 | } 181 | } catch (\Exception $e) { 182 | throw new \Exception($e); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /cli/CreatePresetCommand.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/OleVik/grav-theme-scholar 14 | */ 15 | 16 | namespace Grav\Plugin\Console; 17 | 18 | use Grav\Common\Grav; 19 | use Grav\Common\Utils; 20 | use Grav\Common\Inflector; 21 | use Grav\Console\ConsoleCommand; 22 | use Symfony\Component\Console\Input\InputArgument; 23 | use Symfony\Component\Console\Input\InputOption; 24 | use Grav\Plugin\StaticGenerator\Timer; 25 | use Grav\Plugin\StaticGenerator\CommandLineConfig; 26 | use Grav\Plugin\StaticGenerator\Config\Config; 27 | use Symfony\Component\Filesystem\Filesystem; 28 | use Symfony\Component\Filesystem\Exception\IOExceptionInterface; 29 | 30 | /** 31 | * Create Preset 32 | * 33 | * @category API 34 | * @package Grav\Plugin\StaticGenerator 35 | * @subpackage Grav\Plugin\StaticGenerator\API 36 | * @author Ole Vik 37 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 38 | * @link https://github.com/OleVik/grav-theme-scholar 39 | */ 40 | class CreatePresetCommand extends ConsoleCommand 41 | { 42 | /** 43 | * Command definitions 44 | * 45 | * @return void 46 | */ 47 | protected function configure() 48 | { 49 | $this 50 | ->setName('preset') 51 | ->setDescription('Creates a Preset from the current Config') 52 | ->setHelp('The preset-command creates a Preset from the current Config.') 53 | ->addArgument( 54 | 'name', 55 | InputArgument::REQUIRED, 56 | 'Set Preset-name' 57 | ) 58 | ->addArgument( 59 | 'target', 60 | InputArgument::OPTIONAL, 61 | 'Override target-option or set a custom destination' 62 | ) 63 | ->addOption( 64 | 'parameters', 65 | 'p', 66 | InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 67 | 'Parameters to pass into Twig' 68 | ) 69 | ->addOption( 70 | 'create', 71 | 'c', 72 | InputOption::VALUE_NONE, 73 | 'Create Preset in config://plugins/static-generator.yaml' 74 | ) 75 | ->addOption( 76 | 'mirror', 77 | 'm', 78 | InputOption::VALUE_NONE, 79 | 'Mirror config:// to target destination' 80 | ) 81 | ->addOption( 82 | 'force', 83 | null, 84 | InputOption::VALUE_NONE, 85 | 'Forcefully save data' 86 | ); 87 | } 88 | 89 | /** 90 | * Clear Data Index 91 | * 92 | * @return void 93 | */ 94 | protected function serve() 95 | { 96 | $timer = new Timer(); 97 | $config = Grav::instance()['config']->get('plugins.static-generator'); 98 | $locator = Grav::instance()['locator']; 99 | $name = $this->input->getArgument('name'); 100 | $target = $this->input->getArgument('target'); 101 | $create = $this->input->getOption('create'); 102 | $mirror = $this->input->getOption('mirror'); 103 | $force = $this->input->getOption('force'); 104 | if ($target === null) { 105 | $target = $config['index']; 106 | } 107 | $params = $this->input->getOption('parameters'); 108 | $parameters = array(); 109 | if (\is_array($params) && !empty($params)) { 110 | foreach ($params as $param) { 111 | $param = explode(':', $param); 112 | $parameters[$param[0]] = $param[1]; 113 | } 114 | } 115 | $this->output->writeln('Preset: ' . $name); 116 | try { 117 | if (Utils::contains($target, '://')) { 118 | $scheme = parse_url($target, PHP_URL_SCHEME); 119 | $location = $locator->findResource($scheme . '://') . str_replace($scheme . '://', '/', $target); 120 | } else { 121 | $this->output->writeln('Target must be a valid stream resource, prefixing one of:'); 122 | foreach ($locator->getSchemes() as $scheme) { 123 | $this->output->writeln($scheme . '://'); 124 | } 125 | return; 126 | } 127 | $name = Inflector::hyphenize($name); 128 | $location = $location . '/presets/' . $name; 129 | $source = $locator->findResource('config://'); 130 | if ($create) { 131 | $preset = Config::addPreset($name, $parameters, $force); 132 | if ($preset === 1) { 133 | $this->output->writeln( 134 | 'Added "' . $name . '" to ' . $source . 135 | '/plugins/static-generator.yaml' 136 | ); 137 | } elseif ($preset === 2) { 138 | $this->output->writeln( 139 | 'In ' . $source . '/plugins/static-generator.yaml, 140 | "presets" is not an array or is not set' 141 | ); 142 | } elseif ($preset === 3) { 143 | $this->output->writeln( 144 | 'Updated "' . $name . '" in ' . $source . 145 | '/plugins/static-generator.yaml' 146 | ); 147 | } elseif ($preset === 4) { 148 | $this->output->writeln( 149 | '"' . $name . '" is already set, in ' . $source . 150 | '/plugins/static-generator.yaml' 151 | ); 152 | } else { 153 | $this->output->writeln( 154 | 'Failed adding "' . $name . '" to ' . $source . 155 | '/plugins/static-generator.yaml' 156 | ); 157 | } 158 | } 159 | if ($mirror) { 160 | $mirroring = Config::mirror($location, $source, $force); 161 | if ($mirroring) { 162 | $this->output->writeln('Mirrored ' . $source . ' to ' . $location . ''); 163 | } else { 164 | $this->output->writeln('Could not mirror ' . $source . ' to ' . $location . ''); 165 | } 166 | } 167 | $this->output->writeln('Finished in ' . Timer::format($timer->getTime()) . ''); 168 | } catch (\Exception $e) { 169 | throw new \Exception($e); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /classes/Collection.php: -------------------------------------------------------------------------------- 1 | 11 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 12 | * @link https://github.com/OleVik/grav-plugin-static-generator 13 | */ 14 | 15 | namespace Grav\Plugin\StaticGenerator; 16 | 17 | use Grav\Common\Grav; 18 | use Grav\Common\Utils; 19 | use Grav\Common\Page\Interfaces\PageInterface as Page; 20 | use Symfony\Component\Console\Helper\ProgressBar; 21 | use Symfony\Component\Console\Helper\Table; 22 | use Symfony\Component\Filesystem\Filesystem; 23 | use Symfony\Component\Filesystem\Exception\IOExceptionInterface; 24 | 25 | /** 26 | * Collection Builder 27 | * 28 | * @category API 29 | * @package Grav\Plugin\StaticGenerator\Collection 30 | * @author Ole Vik 31 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 32 | * @link https://github.com/OleVik/grav-plugin-static-generator 33 | */ 34 | class Collection 35 | { 36 | public $assets; 37 | 38 | /** 39 | * Initialize class 40 | * 41 | * @param \Output $handle Instance of Symfony\Component\Console\Output. 42 | * @param string $collection Collection to evaluate. 43 | * @param string $route Route to page, optional. 44 | * @param string $location Where to store output. 45 | * @param boolean $force Forcefully save data. 46 | */ 47 | public function __construct( 48 | $handle, 49 | string $collection, 50 | string $route = '', 51 | string $location = '', 52 | bool $force = false 53 | ) { 54 | $this->assets = array(); 55 | $this->handle = $handle; 56 | $this->collection = $collection; 57 | $this->route = $route; 58 | $this->location = $location; 59 | $this->force = $force; 60 | } 61 | 62 | /** 63 | * Bootstrap data, events, and helpers 64 | * 65 | * @return void 66 | */ 67 | public function setup(bool $offline): void 68 | { 69 | $this->grav = Grav::instance(); 70 | $this->Filesystem = new Filesystem(); 71 | $this->Timer = new Timer(); 72 | $this->Assets = new Assets($this->Filesystem, $this->Timer, $offline); 73 | } 74 | 75 | /** 76 | * Finish progress-counter 77 | * 78 | * @return void 79 | */ 80 | public function teardown() 81 | { 82 | $this->progressBar->finish(); 83 | $this->handle->writeln(''); 84 | $this->result->render(); 85 | } 86 | 87 | /** 88 | * Build Page(s) 89 | * 90 | * @return void 91 | */ 92 | public function buildCollection(): void 93 | { 94 | $this->result = new Table($this->handle); 95 | $this->result->setStyle('box'); 96 | $this->result->setHeaders(['Title', 'Destination', 'Time']); 97 | $this->handle->writeln('Processing Page(s)'); 98 | $pages = $this->grav['page']->evaluate([$this->collection => $this->route]); 99 | $this->progressBar = new ProgressBar( 100 | $this->handle, 101 | count($pages) 102 | ); 103 | foreach ($pages as $Page) { 104 | try { 105 | $this->store($Page); 106 | } catch (\Exception $error) { 107 | throw new \Exception($error); 108 | } 109 | $this->store($Page); 110 | $this->progressBar->advance(); 111 | } 112 | foreach ($this->grav['assets']['assets_css'] as $key => $Asset) { 113 | if (!in_array($Asset['asset'], $this->assets) && get_class($Asset) == 'Grav\Common\Assets\Css') { 114 | $this->assets[] = $Asset['asset']; 115 | } 116 | } 117 | foreach ($this->grav['assets']['assets_js'] as $key => $Asset) { 118 | if (!in_array($Asset['asset'], $this->assets) && get_class($Asset) == 'Grav\Common\Assets\Js') { 119 | $this->assets[] = $Asset['asset']; 120 | } 121 | } 122 | } 123 | 124 | /** 125 | * Build assets 126 | * 127 | * @return void 128 | */ 129 | public function buildAssets(): void 130 | { 131 | $this->result = new Table($this->handle); 132 | $this->result->setStyle('box'); 133 | $this->result->setHeaders(['Title', 'Destination', 'Time']); 134 | $this->handle->writeln('Processing Assets'); 135 | $this->progressBar = new ProgressBar( 136 | $this->handle, 137 | count($this->assets) 138 | ); 139 | $this->Assets->copy( 140 | $this->assets, 141 | $this->location, 142 | $this->result, 143 | $this->progressBar, 144 | $this->force 145 | ); 146 | } 147 | 148 | public function mirrorImages(): void 149 | { 150 | $this->handle->writeln('Processing Images'); 151 | $this->Filesystem->mirror( 152 | GRAV_ROOT . '/images', 153 | $this->location . '/images', 154 | null, 155 | [ 156 | 'override' => true, 157 | 'copy_on_windows' => true, 158 | 'delete' => true 159 | ] 160 | ); 161 | } 162 | 163 | /** 164 | * Store Page 165 | * 166 | * @param Page $Page Grav Page instance. 167 | * 168 | * @return void 169 | */ 170 | public function store(Page $Page): void 171 | { 172 | $route = $Page->route() == '/' ? '' : $Page->route(); 173 | try { 174 | $content = $this->grav['twig']->processTemplate( 175 | $Page->template() . '.' . $Page->templateFormat() . '.twig', 176 | ['page' => $Page] 177 | ); 178 | $content = $this->Assets->rewriteURL($content, $this->rootPrefix); 179 | $content = $this->Assets->rewriteMediaURL( 180 | $content, 181 | Utils::url($Page->getMediaUri()), 182 | $this->rootPrefix . $route 183 | ); 184 | } catch (\Exception $e) { 185 | throw new \Exception($e); 186 | } 187 | try { 188 | $file = 'index.' . $Page->templateFormat(); 189 | if ($this->force) { 190 | $this->Filesystem->remove($this->location . $route . DS . $file); 191 | } 192 | $this->Filesystem->dumpFile($this->location . $route . DS . $file, $content); 193 | $this->result->addRow( 194 | [ 195 | '' . $Page->title() . '', 196 | '' . $this->location . $route . '/' . $file . '', 197 | '' . Timer::format($this->Timer->getTime()) . '' 198 | ] 199 | ); 200 | $this->Assets->copyMedia( 201 | $Page->media()->all(), 202 | $this->location . $Page->route(), 203 | $this->result, 204 | $this->force 205 | ); 206 | } catch (\Exception $e) { 207 | throw new \Exception($e); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /classes/FileStorage.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/OleVik/grav-theme-scholar 14 | */ 15 | 16 | namespace Grav\Framework\Cache\Adapter; 17 | 18 | use Grav\Framework\Cache\AbstractCache; 19 | use Grav\Framework\Cache\Exception\CacheException; 20 | use Grav\Framework\Cache\Exception\InvalidArgumentException; 21 | use Symfony\Component\Filesystem\Exception\IOExceptionInterface; 22 | 23 | /** 24 | * FileStorage Adapter, PSR-16 compatible 25 | * 26 | * @category API 27 | * @package Grav\Framework\Cache\Adapter 28 | * @author Ole Vik 29 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 30 | * @link https://github.com/OleVik/grav-theme-scholar 31 | */ 32 | class FileStorage extends AbstractCache 33 | { 34 | /** 35 | * Path to Storage-directory. 36 | * 37 | * @var string 38 | */ 39 | protected $directory; 40 | 41 | /** 42 | * Temporary filename. 43 | * 44 | * @var string 45 | */ 46 | protected $tmp; 47 | 48 | /** 49 | * Initiate Storage 50 | * 51 | * @param string $directory Path to directory. 52 | * @param string $namespace Name of something. 53 | * @param int $defaultLifetime Duration. 54 | */ 55 | public function __construct($directory, $namespace = '', $defaultLifetime = null) 56 | { 57 | parent::__construct($namespace, $defaultLifetime ?: 31557600); 58 | $this->directory = $directory; 59 | $this->mkdir($directory); 60 | } 61 | 62 | /** 63 | * Fetches a value from storage. 64 | * 65 | * @param string $key The unique key of this item in storage. 66 | * @param mixed $miss Value to return if the key does not exist. 67 | * 68 | * @return mixed The value of the item from storage, or $miss if non-existant. 69 | * 70 | * @throws \Psr\SimpleCache\InvalidArgumentException 71 | */ 72 | public function doGet($key, $miss) 73 | { 74 | parent::validateKey($key); 75 | $now = time(); 76 | $file = $this->directory . DIRECTORY_SEPARATOR . $key; 77 | 78 | if (!file_exists($file) || !$h = @fopen($file, 'rb')) { 79 | return $miss; 80 | } 81 | 82 | if ($now >= (int) $expiresAt = fgets($h)) { 83 | fclose($h); 84 | @unlink($file); 85 | } else { 86 | $i = rawurldecode(rtrim(fgets($h))); 87 | $value = stream_get_contents($h); 88 | fclose($h); 89 | if ($i === $key) { 90 | return unserialize($value); 91 | } 92 | } 93 | 94 | return $miss; 95 | } 96 | 97 | /** 98 | * Persists data in storage. 99 | * 100 | * @param string $key The key of the item to store. 101 | * @param mixed $value The value of the item to store. 102 | * @param null|int|\DateInterval $ttl The Time To Live value of this item. 103 | * 104 | * @return bool True on success, false on failure 105 | * 106 | * @throws \Grav\Framework\Cache\Exception\CacheException 107 | * @throws \Psr\SimpleCache\InvalidArgumentException 108 | * @throws \Symfony\Component\Filesystem\Exception\IOExceptionInterface 109 | */ 110 | public function doSet($key, $value, $ttl) 111 | { 112 | parent::validateKey($key); 113 | $expiresAt = time() + (int) $ttl; 114 | $file = $this->directory . DIRECTORY_SEPARATOR . $key; 115 | 116 | if (!is_writable($this->directory)) { 117 | mkdir($this->directory, 0755, true); 118 | } else { 119 | try { 120 | if ($this->tmp === null) { 121 | $this->tmp = $this->directory . DIRECTORY_SEPARATOR . uniqid('', true); 122 | } 123 | 124 | file_put_contents($this->tmp, $value, LOCK_EX); 125 | 126 | if ($expiresAt !== null) { 127 | touch($this->tmp, $expiresAt); 128 | } 129 | 130 | return rename($this->tmp, $file); 131 | } catch (CacheException $e) { 132 | throw new CacheException($e); 133 | } catch (InvalidArgumentException $e) { 134 | throw new InvalidArgumentException($e); 135 | } catch (IOExceptionInterface $e) { 136 | throw new IOExceptionInterface($e); 137 | } finally { 138 | restore_error_handler(); 139 | } 140 | } 141 | } 142 | 143 | /** 144 | * Delete an item from storage. 145 | * 146 | * @param string $key The unique cache key of the item to delete. 147 | * 148 | * @return bool True if the item was removed, false otherwise. 149 | * 150 | * @throws \Psr\SimpleCache\InvalidArgumentException 151 | */ 152 | public function doDelete($key) 153 | { 154 | parent::validateKey($key); 155 | $file = $this->directory . DIRECTORY_SEPARATOR . $key; 156 | 157 | return (!file_exists($file) || @unlink($file) || !file_exists($file)); 158 | } 159 | 160 | /** 161 | * Wipes clean the entire storage's keys. 162 | * 163 | * @return bool True on success, false otherwise. 164 | */ 165 | public function doClear() 166 | { 167 | $target = new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS); 168 | $iterator = new \RecursiveIteratorIterator($target); 169 | foreach ($iterator as $file) { 170 | $this->doDelete($file->getFilename()); 171 | } 172 | $result = new \FilesystemIterator($this->directory); 173 | if (!$result->valid()) { 174 | return true; 175 | } else { 176 | return false; 177 | } 178 | } 179 | 180 | /** 181 | * Determines whether an item is present in storage. 182 | * 183 | * @param string $key The unique cache key of the item to check for. 184 | * 185 | * @return bool 186 | * 187 | * @throws \Psr\SimpleCache\InvalidArgumentException 188 | */ 189 | public function doHas($key) 190 | { 191 | parent::validateKey($key); 192 | $file = $this->directory . DIRECTORY_SEPARATOR . $key; 193 | return file_exists($file) && (@filemtime($file) > time() || $this->doGet($key, null)); 194 | } 195 | 196 | /** 197 | * Make directory 198 | * 199 | * @param string $dir Directory to create 200 | * @throws RuntimeException 201 | * 202 | * @return void 203 | */ 204 | private function mkdir($dir) 205 | { 206 | if (@is_dir($dir)) { 207 | return; 208 | } 209 | $success = @mkdir($dir, 0777, true); 210 | if (!$success) { 211 | clearstatcache(true, $dir); 212 | if (!@is_dir($dir)) { 213 | throw new \RuntimeException(sprintf('Unable to create directory: %s', $dir)); 214 | } 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /vendor/symfony/polyfill-ctype/Ctype.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Polyfill\Ctype; 13 | 14 | /** 15 | * Ctype implementation through regex. 16 | * 17 | * @internal 18 | * 19 | * @author Gert de Pagter 20 | */ 21 | final class Ctype 22 | { 23 | /** 24 | * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise. 25 | * 26 | * @see https://php.net/ctype-alnum 27 | * 28 | * @param string|int $text 29 | * 30 | * @return bool 31 | */ 32 | public static function ctype_alnum($text) 33 | { 34 | $text = self::convert_int_to_char_for_ctype($text); 35 | 36 | return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text); 37 | } 38 | 39 | /** 40 | * Returns TRUE if every character in text is a letter, FALSE otherwise. 41 | * 42 | * @see https://php.net/ctype-alpha 43 | * 44 | * @param string|int $text 45 | * 46 | * @return bool 47 | */ 48 | public static function ctype_alpha($text) 49 | { 50 | $text = self::convert_int_to_char_for_ctype($text); 51 | 52 | return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text); 53 | } 54 | 55 | /** 56 | * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise. 57 | * 58 | * @see https://php.net/ctype-cntrl 59 | * 60 | * @param string|int $text 61 | * 62 | * @return bool 63 | */ 64 | public static function ctype_cntrl($text) 65 | { 66 | $text = self::convert_int_to_char_for_ctype($text); 67 | 68 | return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text); 69 | } 70 | 71 | /** 72 | * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise. 73 | * 74 | * @see https://php.net/ctype-digit 75 | * 76 | * @param string|int $text 77 | * 78 | * @return bool 79 | */ 80 | public static function ctype_digit($text) 81 | { 82 | $text = self::convert_int_to_char_for_ctype($text); 83 | 84 | return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text); 85 | } 86 | 87 | /** 88 | * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise. 89 | * 90 | * @see https://php.net/ctype-graph 91 | * 92 | * @param string|int $text 93 | * 94 | * @return bool 95 | */ 96 | public static function ctype_graph($text) 97 | { 98 | $text = self::convert_int_to_char_for_ctype($text); 99 | 100 | return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text); 101 | } 102 | 103 | /** 104 | * Returns TRUE if every character in text is a lowercase letter. 105 | * 106 | * @see https://php.net/ctype-lower 107 | * 108 | * @param string|int $text 109 | * 110 | * @return bool 111 | */ 112 | public static function ctype_lower($text) 113 | { 114 | $text = self::convert_int_to_char_for_ctype($text); 115 | 116 | return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text); 117 | } 118 | 119 | /** 120 | * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all. 121 | * 122 | * @see https://php.net/ctype-print 123 | * 124 | * @param string|int $text 125 | * 126 | * @return bool 127 | */ 128 | public static function ctype_print($text) 129 | { 130 | $text = self::convert_int_to_char_for_ctype($text); 131 | 132 | return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text); 133 | } 134 | 135 | /** 136 | * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise. 137 | * 138 | * @see https://php.net/ctype-punct 139 | * 140 | * @param string|int $text 141 | * 142 | * @return bool 143 | */ 144 | public static function ctype_punct($text) 145 | { 146 | $text = self::convert_int_to_char_for_ctype($text); 147 | 148 | return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text); 149 | } 150 | 151 | /** 152 | * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters. 153 | * 154 | * @see https://php.net/ctype-space 155 | * 156 | * @param string|int $text 157 | * 158 | * @return bool 159 | */ 160 | public static function ctype_space($text) 161 | { 162 | $text = self::convert_int_to_char_for_ctype($text); 163 | 164 | return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text); 165 | } 166 | 167 | /** 168 | * Returns TRUE if every character in text is an uppercase letter. 169 | * 170 | * @see https://php.net/ctype-upper 171 | * 172 | * @param string|int $text 173 | * 174 | * @return bool 175 | */ 176 | public static function ctype_upper($text) 177 | { 178 | $text = self::convert_int_to_char_for_ctype($text); 179 | 180 | return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text); 181 | } 182 | 183 | /** 184 | * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise. 185 | * 186 | * @see https://php.net/ctype-xdigit 187 | * 188 | * @param string|int $text 189 | * 190 | * @return bool 191 | */ 192 | public static function ctype_xdigit($text) 193 | { 194 | $text = self::convert_int_to_char_for_ctype($text); 195 | 196 | return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text); 197 | } 198 | 199 | /** 200 | * Converts integers to their char versions according to normal ctype behaviour, if needed. 201 | * 202 | * If an integer between -128 and 255 inclusive is provided, 203 | * it is interpreted as the ASCII value of a single character 204 | * (negative values have 256 added in order to allow characters in the Extended ASCII range). 205 | * Any other integer is interpreted as a string containing the decimal digits of the integer. 206 | * 207 | * @param string|int $int 208 | * 209 | * @return mixed 210 | */ 211 | private static function convert_int_to_char_for_ctype($int) 212 | { 213 | if (!\is_int($int)) { 214 | return $int; 215 | } 216 | 217 | if ($int < -128 || $int > 255) { 218 | return (string) $int; 219 | } 220 | 221 | if ($int < 0) { 222 | $int += 256; 223 | } 224 | 225 | return \chr($int); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /classes/Data/SSEData.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/OleVik/grav-plugin-static-generator 14 | */ 15 | 16 | namespace Grav\Plugin\StaticGenerator\Data; 17 | 18 | use Grav\Common\Grav; 19 | use Grav\Framework\Cache\Adapter\FileStorage; 20 | use Grav\Plugin\StaticGenerator\Timer; 21 | use Grav\Plugin\StaticGenerator\Data\AbstractData; 22 | 23 | /** 24 | * Server Sent Events Data Builder 25 | * 26 | * @category API 27 | * @package Grav\Plugin\StaticGenerator\Data\SSEData 28 | * @author Ole Vik 29 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 30 | * @link https://github.com/OleVik/grav-plugin-static-generator 31 | */ 32 | class SSEData extends AbstractData 33 | { 34 | /** 35 | * Declare headers 36 | * 37 | * @return void 38 | */ 39 | public static function headers() 40 | { 41 | error_reporting(0); 42 | set_time_limit(0); 43 | header('Content-Type: text/event-stream'); 44 | header('Access-Control-Allow-Origin: *'); 45 | header('Cache-Control: no-store, no-cache, must-revalidate'); 46 | header('Cache-Control: post-check=0, pre-check=0', false); 47 | header('Pragma: no-cache'); 48 | } 49 | 50 | /** 51 | * Check count before progressing 52 | * 53 | * @param string $route Route to page. 54 | * 55 | * @return string 56 | */ 57 | public function verify(string $route): string 58 | { 59 | $mode = '@page.descendants'; 60 | if ($route == '/') { 61 | $mode = '@root.descendants'; 62 | } 63 | $this->pages = $this->grav['page']->evaluate([$mode => $route]); 64 | if ($this->count() < 1) { 65 | $route = '/'; 66 | $this->pages = $this->grav['page']->evaluate(['@root.descendants' => '/']); 67 | } 68 | if ($this->count() > 0) { 69 | $this->total = $this->count(); 70 | echo 'event: update' . "\n\n"; 71 | echo 'data: ' . json_encode( 72 | [ 73 | 'datetime' => date(DATE_ISO8601), 74 | 'total' => $this->count() 75 | ] 76 | ) . "\n\n"; 77 | } else { 78 | echo 'event: update' . "\n\n"; 79 | echo 'data: ' . json_encode( 80 | [ 81 | 'datetime' => date(DATE_ISO8601), 82 | 'content' => Grav::instance()['language']->translate( 83 | ['PLUGIN_STATIC_GENERATOR.ADMIN.EMPTY'] 84 | ) . '.' 85 | ] 86 | ) . "\n\n"; 87 | echo 'event: close' . "\n\n"; 88 | echo 'data: ' . json_encode( 89 | [ 90 | 'datetime' => date(DATE_ISO8601), 91 | 'content' => 'END-OF-STREAM' 92 | ] 93 | ) . "\n\n"; 94 | } 95 | return $route; 96 | } 97 | 98 | /** 99 | * Create data-structure recursively 100 | * 101 | * @param string $route Route to page. 102 | * @param string $mode Placeholder for operation-mode, private. 103 | * @param int $depth Placeholder for recursion depth, private. 104 | * 105 | * @return mixed Index of Pages with FrontMatter 106 | */ 107 | public function index(string $route, string $mode = '', int $depth = 0) 108 | { 109 | $depth++; 110 | $mode = '@page.self'; 111 | if ($route == '/') { 112 | $mode = '@root.children'; 113 | } 114 | if ($depth > 1) { 115 | $mode = '@page.children'; 116 | } 117 | $this->pages = $this->grav['page']->evaluate([$mode => $route]); 118 | $this->pages = $this->pages->order($this->orderBy, $this->orderDir); 119 | foreach ($this->pages as $page) { 120 | $route = $page->rawRoute(); 121 | $item = array( 122 | 'title' => $page->title(), 123 | 'date' => \DateTime::createFromFormat('U', $page->date())->format('c'), 124 | 'url' => $page->url(true, true, true), 125 | 'taxonomy' => array( 126 | 'categories' => array(), 127 | 'tags' => array() 128 | ) 129 | ); 130 | if (isset($page->taxonomy()['category'])) { 131 | $item['taxonomy']['categories'] = array_merge( 132 | $item['taxonomy']['categories'], 133 | $page->taxonomy()['category'] 134 | ); 135 | } 136 | if (isset($page->taxonomy()['categories'])) { 137 | $item['taxonomy']['categories'] = array_merge( 138 | $item['taxonomy']['categories'], 139 | $page->taxonomy()['categories'] 140 | ); 141 | } 142 | if (isset($page->taxonomy()['tags'])) { 143 | $item['taxonomy']['tags'] = array_merge( 144 | $item['taxonomy']['tags'], 145 | $page->taxonomy()['tags'] 146 | ); 147 | } 148 | if (!empty($page->media()->all())) { 149 | $item['media'] = array_keys($page->media()->all()); 150 | } 151 | if (!$this->content) { 152 | $item['taxonomy']['categories'] = implode(' ', $item['taxonomy']['categories']); 153 | $item['taxonomy']['tags'] = implode(' ', $item['taxonomy']['tags']); 154 | if (isset($item['media']) && is_array($item['media'])) { 155 | $item['media'] = implode(' ', $item['media']); 156 | } 157 | } else { 158 | try { 159 | $pageContent = $this->content($page); 160 | if (!empty($pageContent) && strlen($pageContent) <= $this->maxLength) { 161 | $item['content'] = $pageContent; 162 | } 163 | } catch (\Exception $error) { 164 | throw new \Exception($error); 165 | } 166 | } 167 | echo 'event: update' . "\n\n"; 168 | echo 'data: ' . json_encode( 169 | [ 170 | 'datetime' => date(DATE_ISO8601), 171 | 'progress' => $this->progress, 172 | 'content' => $page->title() 173 | ] 174 | ) . "\n\n"; 175 | $this->progress(); 176 | if (count($page->children()) > 0) { 177 | $this->index($route, $mode, $depth); 178 | } 179 | $this->data[] = (object) $item; 180 | while (ob_get_level() > 0) { 181 | ob_end_flush(); 182 | } 183 | flush(); 184 | if (connection_aborted()) { 185 | exit; 186 | } 187 | } 188 | } 189 | 190 | /** 191 | * Cleanup 192 | * 193 | * @param string $location Stream to storage-folder. 194 | * @param string $slug Hyphenized Page-route. 195 | * @param array $data Data to store. 196 | * @param Timer $Timer Instance of Grav\Plugin\StaticGenerator\Timer. 197 | * 198 | * @return void 199 | */ 200 | public function teardown(string $location, string $slug, array $data, Timer $Timer): void 201 | { 202 | if (empty($slug)) { 203 | $slug = 'index'; 204 | } 205 | $file = $slug . '.js'; 206 | if ($this->content) { 207 | $file = $slug . '.full.js'; 208 | } 209 | $location = (string) (Grav::instance()['locator']->findResource($location) ?: 210 | Grav::instance()['locator']->findResource($location, true, true)); 211 | $Storage = new FileStorage($location); 212 | if ($Storage->doHas($file)) { 213 | $Storage->doDelete($file); 214 | } 215 | if ($this->content) { 216 | $Storage->doSet($file, 'const GravDataIndex = ' . json_encode($data) . ';', 0); 217 | } else { 218 | $Storage->doSet($file, 'const GravMetadataIndex = ' . json_encode($data) . ';', 0); 219 | } 220 | $message = ucfirst( 221 | $this->grav['language']->translate( 222 | ['PLUGIN_STATIC_GENERATOR.ADMIN.GENERIC.STORED'] 223 | ) 224 | ) . ' ' . $this->total . ' ' . 225 | $this->grav['language']->translate( 226 | ['PLUGIN_STATIC_GENERATOR.ADMIN.GENERIC.ITEMS'] 227 | ) . ' ' . 228 | $this->grav['language']->translate( 229 | ['PLUGIN_STATIC_GENERATOR.ADMIN.GENERIC.IN'] 230 | ) . ' ' . $location . '/' . $file . ' ' . 231 | $this->grav['language']->translate( 232 | ['PLUGIN_STATIC_GENERATOR.ADMIN.GENERIC.IN'] 233 | ) . ' ' . Timer::format($Timer->getTime()) . '.'; 234 | echo 'event: update' . "\n\n"; 235 | echo 'data: ' . json_encode( 236 | [ 237 | 'datetime' => date(DATE_ISO8601), 238 | 'content' => $message, 239 | 'text' => $file, 240 | 'value' => $location . '/' . $file 241 | ] 242 | ) . "\n\n"; 243 | Grav::instance()['log']->info($message); 244 | } 245 | 246 | /** 247 | * Finish stream 248 | * 249 | * @return void 250 | */ 251 | public static function finish(): void 252 | { 253 | echo 'event: close' . "\n\n"; 254 | echo 'data: ' . json_encode( 255 | [ 256 | 'datetime' => date(DATE_ISO8601), 257 | 'content' => 'END-OF-STREAM' 258 | ] 259 | ) . "\n\n"; 260 | exit; 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Static Generator Plugin 2 | 3 | The **Static Generator** Plugin is made for the [Grav CMS](http://github.com/getgrav/grav), and facilitates indexing and static generation of Pages. 4 | 5 | ## Configuration 6 | 7 | Before configuring the plugin, you should copy the `user/plugins/static-generator/static-generator.yaml` to `user/config/plugins/static-generator.yaml` and only edit that copy. 8 | 9 | Here is the default configuration and an explanation of available options: 10 | 11 | ```yaml 12 | # Enable or disable the plugin 13 | enabled: true 14 | # Where to store indices 15 | index: "user://data/persist" 16 | # Where to store static content 17 | content: "user://data/persist/static" 18 | # Maximum character count in each Page 19 | content_max_length: 100000 20 | # Permission-levels that can see buttons in plugin options 21 | content_permissions: 22 | - admin.super 23 | - admin.maintenance 24 | # Enable in Admin-plugin 25 | admin: true 26 | # Use plugin's JavaScript in Admin-plugin 27 | js: true 28 | # Enable index-button in Admin Quick Tray 29 | quick_tray: true 30 | # Permission-levels that can see index-button in Admin Quick Tray 31 | quick_tray_permissions: 32 | - admin.super 33 | - admin.maintenance 34 | # Defined sets of presets 35 | presets: 36 | - name: default 37 | ``` 38 | 39 | Note that if you use the Admin plugin, a file with your configuration named `static-generator.yaml` will be saved in the `user/config/plugins/` folder once changed and saved in the Admin. 40 | 41 | ## Usage 42 | 43 | The Static Generator Plugin does two things: Index Page(s) metadata and content, and create static versions of Page(s). 44 | 45 | ### Indexing 46 | 47 | If you're using the Admin plugin, two icons will be available in the quick navigation tray: A bolt and an archive-box. When clicked, the plugin will start indexing the content in `/user/pages`, and each Page will have an entry. By default, all content is indexed, resulting in something like the code below. 48 | 49 | **The bolt stores index data, which does not include the Page(s) content, whilst the archive-box does.** The time to do this for the plugin is negligible, but if you're loading this data for searching in your theme, know that loading and searching content is more resource-intensive. 50 | 51 | ```json 52 | [ 53 | { 54 | "title": "Body & Hero Classes", 55 | "date": "2017-08-11T12:55:00+00:00", 56 | "url": "http://localhost:8000/blog/hero-classes", 57 | "taxonomy": { 58 | "categories": ["blog"], 59 | "tags": [] 60 | }, 61 | "media": ["unsplash-overcast-mountains.jpg"], 62 | "content": "The [Quark theme](https://getgrav.org/downloads/themes) ...\n" 63 | }, 64 | { 65 | "title": "Text & Typography", 66 | "date": "2017-07-19T11:34:00+00:00", 67 | "url": "http://localhost:8000/blog/text-typography", 68 | "taxonomy": { 69 | "categories": ["blog"], 70 | "tags": [] 71 | }, 72 | "media": ["unsplash-text.jpg"], 73 | "content": "The [Quark theme](https://github.com/getgrav/grav-theme-quark) ...\n" 74 | } 75 | ] 76 | ``` 77 | 78 | It will be wrapped for use in JavaScript, like `const GravDataIndex = [...];`. This makes it apt for use with search engines, like [FlexSearch](https://github.com/nextapps-de/flexsearch/). You can include the resulting `.js`-file and use `GravDataIndex` for searching Pages. If viewing a specific Page in Admin, for example at `http://localhost:8000/en/admin/pages/blog`, it will index the descendants of this Page in a specific-file named `blog.full.js`. 79 | 80 | The same can be achieved through the command-line, with the command `php bin/plugin static-generator index`. **See `php bin/plugin static-generator help index` for options**, a normal `php bin/plugin static-generator index "/" -c` will index all Pages including content. 81 | 82 | #### Usage 83 | 84 | ```bash 85 | php bin/plugin static-generator index [options] [--] [] 86 | ``` 87 | 88 | #### Arguments 89 | 90 | ```bash 91 | route The route to the page 92 | target Override target-option or set a custom destination 93 | ``` 94 | 95 | #### Available options 96 | 97 | ```bash 98 | -u, --url FQDN passed as `system.custom_base_url` 99 | -b, --basename[=BASENAME] Index basename [default: "index"] 100 | -c, --content Include Page content 101 | -e, --echo Outputs result directly 102 | -w, --wrap Wraps JSON as a JavaScript global 103 | -f, --force Forcefully save data 104 | -h, --help Display this help message 105 | -q, --quiet Do not output any message 106 | -V, --version Display this application version 107 | --ansi Force ANSI output 108 | --no-ansi Disable ANSI output 109 | -n, --no-interaction Do not ask any interactive question 110 | -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug 111 | ``` 112 | 113 | **New in v4**: Pass `--url "https://domain.tld/` to set or override `system.yaml`'s `custom_base_url`, for processing out of context, and yielding absolute URL's in the index. 114 | 115 | ### Static Generation 116 | 117 | If you want to generate static versions of the Page(s), use the `php bin/plugin static-generator page`-command. This will create a `index.html`-file for each Page, located in your preset `content`-location, like `/user/data/persist/static`. The folder-structure of `/user/pages` will remain intact, and assets output alongside, for example to `/user/data/persist/static/assets`. Asset-paths will be rewritten, also for media which will remain alongside each Page's `index.html`-file. 118 | 119 | #### Usage 120 | 121 | ```bash 122 | php bin/plugin static-generator page [options] [--] [ [ []]] 123 | ``` 124 | 125 | #### Parameters 126 | 127 | ```bash 128 | route The route to the page 129 | collection The Page Collection to store (see https://learn.getgrav.org/16/content/collections#collection-headers) 130 | target Override target-option or set a custom destination 131 | ``` 132 | 133 | #### Available options 134 | 135 | ```bash 136 | -p, --preset[=PRESET] Name of Config preset 137 | -a, --assets Include Assets 138 | -r, --root-prefix=ROOT-PREFIX Root prefix for assets and images 139 | -s, --static-assets Include Static Assets 140 | -i, --images Include Images 141 | -o, --offline Force offline-mode 142 | -f, --filter[=FILTER] Methods for filtering (multiple values allowed) 143 | ``` 144 | 145 | For example, `php bin/plugin static-generator page "@page.descendants" "/blog"` results in: 146 | 147 | ``` 148 | \---static 149 | +---assets 150 | | \---user 151 | | \---themes 152 | | \---scholar 153 | | +---css 154 | | | | print.css 155 | | | | theme.css 156 | | | | 157 | | | \---styles 158 | | | metal.css 159 | | | 160 | | +---js 161 | | | theme.js 162 | | | 163 | | \---node_modules 164 | | +---dayjs 165 | | | | dayjs.min.js 166 | | | | 167 | | | \---plugin 168 | | | advancedFormat.js 169 | | | 170 | | \---flexsearch 171 | | \---dist 172 | | flexsearch.min.js 173 | | 174 | \---blog 175 | +---classic-modern-architecture 176 | | index.html 177 | | unsplash-luca-bravo.jpg 178 | | 179 | +---daring-fireball-link 180 | | index.html 181 | | refuge-des-merveilles-tende-france---denis-degioanni-unsplashcom.jpg 182 | | 183 | +---focus-and-blur 184 | | index.html 185 | | unsplash-focus.jpg 186 | | 187 | +---hero-classes 188 | | index.html 189 | | unsplash-overcast-mountains.jpg 190 | | 191 | +---london-at-night 192 | | index.html 193 | | unsplash-london-night.jpg 194 | | unsplash-xbrunel-johnson.jpg 195 | | 196 | +---random-thoughts 197 | | index.html 198 | | 199 | +---text-typography 200 | | index.html 201 | | unsplash-text.jpg 202 | | 203 | \---the-urban-jungle 204 | index.html 205 | unsplash-sidney-perry.jpg 206 | ``` 207 | 208 | ### Integration with search-engines 209 | 210 | See explanation in [issue 14](https://github.com/OleVik/grav-plugin-static-generator/issues/14#issuecomment-812124118). 211 | 212 | ### Cleanup 213 | 214 | The `php bin/plugin static-generator clear` command will delete the preset folders containing indices and static content. 215 | 216 | ### Debugging 217 | 218 | The `php bin/plugin static-generator test` command attempts to iterate Page(s) in the samme manner that the `index` and `page` commands do, to verify that the indices and static content can be generated. 219 | 220 | ## Installation 221 | 222 | Installing the Static Generator plugin can be done in one of three ways: With the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm) or with a zip-file. 223 | 224 | ### GPM 225 | 226 | The simplest way to install the plugin is using the GPM through your system's terminal, also called the command line. From the root of your Grav-installation folder, type: 227 | 228 | bin/gpm install static-generator 229 | 230 | This will install the plugin into the `/user/plugins`-directory within Grav. Its files can be found under `/your/grav/site/user/plugins/static-generator`. 231 | 232 | ### Manual 233 | 234 | To install the plugin, download the zip-version of this repository and unzip it under `/your/grav/site/user/plugins`. Then rename the folder to `static-generator`. You can find these files on [GitHub](https://github.com/OleVik/grav-plugin-static-generator) or via [GetGrav.org](http://getgrav.org/downloads/plugins). 235 | 236 | You should now have all the plugin files under 237 | 238 | /your/grav/site/user/plugins/static-generator 239 | 240 | ### Admin Plugin 241 | 242 | If you use the Admin plugin, you can install it directly by browsing the `Plugins`-tab and clicking on the `Add` button. 243 | 244 | ## Credits 245 | 246 | - Developed by [Ole Vik](https://github.com/OleVik) 247 | - Version 2.0.0-refactor sponsored by [Paul Hibbitts](https://twitter.com/hibbittsdesign) 248 | 249 | ## TODO 250 | 251 | - [ ] content_max_length not used on Collection? 252 | -------------------------------------------------------------------------------- /blueprints.yaml: -------------------------------------------------------------------------------- 1 | name: Static Generator 2 | version: 4.0.1 3 | slug: static-generator 4 | type: plugin 5 | description: Static generation of Page(s) and Index. 6 | icon: bolt 7 | author: 8 | name: Ole Vik 9 | email: git@olevik.net 10 | homepage: https://github.com/OleVik/grav-plugin-static-generator 11 | keywords: grav, plugin, static, index, search, data, json, html 12 | bugs: https://github.com/OleVik/grav-plugin-static-generator/issues 13 | docs: https://github.com/OleVik/grav-plugin-static-generator 14 | license: MIT 15 | dependencies: 16 | - { name: grav, version: ">=1.7" } 17 | 18 | form: 19 | validation: strict 20 | fields: 21 | tabs: 22 | type: tabs 23 | active: 1 24 | fields: 25 | generate: 26 | type: tab 27 | title: PLUGIN_STATIC_GENERATOR.ADMIN.GENERATE.TITLE 28 | fields: 29 | section: 30 | type: section 31 | text: PLUGIN_STATIC_GENERATOR.ADMIN.GENERATE.EXPLANATION 32 | underline: true 33 | route: 34 | type: text 35 | label: PLUGIN_STATIC_GENERATOR.ADMIN.ROUTE 36 | placeholder: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.ROUTE 37 | target: 38 | type: text 39 | label: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.TARGET 40 | placeholder: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.STORAGE.TARGET 41 | root_prefix: 42 | type: text 43 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.ROOT_PREFIX 44 | placeholder: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.STORAGE.ROOT_PREFIX 45 | assets: 46 | type: checkbox 47 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.ASSETS 48 | static_assets: 49 | type: checkbox 50 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.STATIC_ASSETS 51 | images: 52 | type: checkbox 53 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.IMAGES 54 | parameters: 55 | type: array 56 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.PARAMETERS.TITLE 57 | description: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.PARAMETERS.DESCRIPTION 58 | placeholder_key: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.PARAMETERS.KEY 59 | placeholder_value: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.PARAMETERS.VALUE 60 | filters: 61 | type: selectize 62 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.FILTERS 63 | description: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.FILTERS 64 | selectize: 65 | options: 66 | - text: "visible" 67 | value: "visible" 68 | - text: "nonVisible" 69 | value: "nonVisible" 70 | - text: "modular" 71 | value: "modular" 72 | - text: "nonModular" 73 | value: "nonModular" 74 | - text: "published" 75 | value: "published" 76 | - text: "nonPublished" 77 | value: "nonPublished" 78 | - text: "routable" 79 | value: "routable" 80 | - text: "nonRoutable" 81 | value: "nonRoutable" 82 | presets: 83 | type: tab 84 | title: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.TITLE 85 | fields: 86 | section: 87 | type: section 88 | text: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.EXPLANATION 89 | underline: true 90 | markdown: true 91 | presets: 92 | type: list 93 | description: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.EXPLANATION 94 | style: vertical 95 | classes: static-generator-presets 96 | fields: 97 | .name: 98 | type: text 99 | label: PLUGIN_ADMIN.NAME 100 | placeholder: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.NAME 101 | validate: 102 | required: true 103 | .route: 104 | type: text 105 | label: PLUGIN_STATIC_GENERATOR.ADMIN.ROUTE 106 | placeholder: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.ROUTE 107 | .target: 108 | type: text 109 | label: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.TARGET 110 | placeholder: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.STORAGE.TARGET 111 | .root_prefix: 112 | type: text 113 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.ROOT_PREFIX 114 | placeholder: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.STORAGE.ROOT_PREFIX 115 | .assets: 116 | type: checkbox 117 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.ASSETS 118 | .static_assets: 119 | type: checkbox 120 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.STATIC_ASSETS 121 | .images: 122 | type: checkbox 123 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.IMAGES 124 | .parameters: 125 | type: array 126 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.PARAMETERS.TITLE 127 | description: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.PARAMETERS.DESCRIPTION 128 | placeholder_key: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.PARAMETERS.KEY 129 | placeholder_value: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.PARAMETERS.VALUE 130 | .filters: 131 | type: selectize 132 | label: PLUGIN_STATIC_GENERATOR.ADMIN.PRESETS.FILTERS 133 | description: PLUGIN_STATIC_GENERATOR.ADMIN.HELP.PRESETS.FILTERS 134 | selectize: 135 | options: 136 | - text: "visible" 137 | value: "visible" 138 | - text: "nonVisible" 139 | value: "nonVisible" 140 | - text: "modular" 141 | value: "modular" 142 | - text: "nonModular" 143 | value: "nonModular" 144 | - text: "published" 145 | value: "published" 146 | - text: "nonPublished" 147 | value: "nonPublished" 148 | - text: "routable" 149 | value: "routable" 150 | - text: "nonRoutable" 151 | value: "nonRoutable" 152 | .links: 153 | type: preset_buttons 154 | fields: 155 | - class: static-generator-copy-preset 156 | name: PLUGIN_ADMIN.COPY 157 | options: 158 | type: tab 159 | title: PLUGIN_ADMIN.OPTIONS 160 | fields: 161 | basic: 162 | type: section 163 | title: PLUGIN_ADMIN.OPTIONS 164 | underline: true 165 | security: [admin.super, admin.maintenance] 166 | fields: 167 | enabled: 168 | type: toggle 169 | label: PLUGIN_ADMIN.PLUGIN_STATUS 170 | options: 171 | 1: PLUGIN_ADMIN.ENABLED 172 | 0: PLUGIN_ADMIN.DISABLED 173 | validate: 174 | type: bool 175 | index: 176 | type: text 177 | label: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.INDEX 178 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.STORAGE.INDEX 179 | highlight: native 180 | default: native 181 | options: 182 | native: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.OPTIONS.NATIVE 183 | persist: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.OPTIONS.PERSIST 184 | transient: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.OPTIONS.TRANSIENT 185 | validate: 186 | required: true 187 | content: 188 | type: text 189 | label: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.CONTENT 190 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.STORAGE.CONTENT 191 | highlight: native 192 | default: native 193 | options: 194 | native: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.OPTIONS.NATIVE 195 | persist: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.OPTIONS.PERSIST 196 | transient: PLUGIN_STATIC_GENERATOR.ADMIN.STORAGE.OPTIONS.TRANSIENT 197 | validate: 198 | required: true 199 | explanation: 200 | type: spacer 201 | text: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.STORAGE.EXPLANATION 202 | markdown: true 203 | content_max_length: 204 | type: number 205 | label: PLUGIN_STATIC_GENERATOR.ADMIN.CONTENT_MAX_LENGTH 206 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.CONTENT_MAX_LENGTH 207 | validate: 208 | min: 0 209 | step: 10000 210 | max: 10000000 211 | admin: 212 | type: toggle 213 | label: PLUGIN_STATIC_GENERATOR.ADMIN.ADMIN 214 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.ADMIN 215 | options: 216 | 1: PLUGIN_ADMIN.ENABLED 217 | 0: PLUGIN_ADMIN.DISABLED 218 | validate: 219 | type: bool 220 | js: 221 | type: toggle 222 | label: PLUGIN_STATIC_GENERATOR.ADMIN.JS 223 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.JS 224 | options: 225 | 1: PLUGIN_ADMIN.ENABLED 226 | 0: PLUGIN_ADMIN.DISABLED 227 | validate: 228 | type: bool 229 | css: 230 | type: toggle 231 | label: PLUGIN_STATIC_GENERATOR.ADMIN.CSS 232 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.CSS 233 | options: 234 | 1: PLUGIN_ADMIN.ENABLED 235 | 0: PLUGIN_ADMIN.DISABLED 236 | validate: 237 | type: bool 238 | quick_tray: 239 | type: toggle 240 | label: PLUGIN_STATIC_GENERATOR.ADMIN.CSS 241 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.CSS 242 | options: 243 | 1: PLUGIN_ADMIN.ENABLED 244 | 0: PLUGIN_ADMIN.DISABLED 245 | validate: 246 | type: bool 247 | permissions: 248 | type: section 249 | title: PLUGIN_ADMIN.PERMISSIONS 250 | underline: true 251 | security: [admin.super, admin.maintenance] 252 | fields: 253 | content_permissions: 254 | type: selectize 255 | label: PLUGIN_STATIC_GENERATOR.ADMIN.CONTENT_PERMISSIONS 256 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.CONTENT_PERMISSIONS 257 | allowEmptyOption: true 258 | merge_items: true 259 | selectize: 260 | create: false 261 | data-options@: '\Grav\Plugin\StaticGeneratorPlugin::getAdminPermissionsBlueprint' 262 | validate: 263 | type: commalist 264 | quick_tray_permissions: 265 | type: selectize 266 | label: PLUGIN_STATIC_GENERATOR.ADMIN.QUICK_TRAY_PERMISSIONS 267 | description: PLUGIN_STATIC_GENERATOR.ADMIN.DESCRIPTION.QUICK_TRAY_PERMISSIONS 268 | allowEmptyOption: true 269 | merge_items: true 270 | selectize: 271 | create: false 272 | data-options@: '\Grav\Plugin\StaticGeneratorPlugin::getAdminPermissionsBlueprint' 273 | validate: 274 | type: commalist 275 | -------------------------------------------------------------------------------- /classes/Collection/AbstractCollection.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/OleVik/grav-plugin-static-generator 14 | */ 15 | 16 | namespace Grav\Plugin\StaticGenerator\Collection; 17 | 18 | use Grav\Common\Grav; 19 | use Grav\Common\Utils; 20 | use Grav\Common\Page\Interfaces\PageInterface as Page; 21 | use Grav\Common\Page\Collection; 22 | use Grav\Plugin\StaticGenerator\Collection\CollectionInterface; 23 | use Grav\Plugin\StaticGenerator\Config\Config; 24 | use Grav\Plugin\StaticGenerator\Assets; 25 | use Grav\Plugin\StaticGenerator\Source\Source; 26 | use Grav\Plugin\StaticGenerator\Timer; 27 | use Symfony\Component\Filesystem\Filesystem; 28 | use Symfony\Component\Filesystem\Exception\IOExceptionInterface; 29 | 30 | /** 31 | * Abstract Collection Builder 32 | * 33 | * @category API 34 | * @package Grav\Plugin\StaticGenerator\Collection\AbstractCollection 35 | * @author Ole Vik 36 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 37 | * @link https://github.com/OleVik/grav-plugin-static-generator 38 | */ 39 | abstract class AbstractCollection implements CollectionInterface 40 | { 41 | public $assets; 42 | public $routes; 43 | 44 | /** 45 | * Initialize class 46 | * 47 | * @param string $collection Collection to evaluate. 48 | * @param string $route Route to page, optional. 49 | * @param string $location Where to store output. 50 | * @param boolean $force Forcefully save data. 51 | * @param string $rootPrefix Root prefix. 52 | * @param array $filters Methods to filter Collection by. 53 | * @param array $parameters Parameters to pass to Config or Twig. 54 | */ 55 | public function __construct( 56 | string $collection, 57 | string $route = '', 58 | string $location = '', 59 | bool $force = false, 60 | string $rootPrefix = '', 61 | array $filters = [], 62 | array $parameters = [] 63 | ) { 64 | $this->assets = array(); 65 | $this->route = $route; 66 | $this->collection = $collection; 67 | $this->location = $location; 68 | $this->force = $force; 69 | $this->rootPrefix = $rootPrefix; 70 | $this->filters = $filters; 71 | $this->parameters = $parameters; 72 | } 73 | 74 | /** 75 | * Bootstrap data, events, and helpers 76 | * 77 | * @param string $preset Name of Config Preset to load. 78 | * @param bool $offline Force offline-mode. 79 | * 80 | * @return void 81 | */ 82 | public function setup(string $preset, $offline): void 83 | { 84 | $this->Filesystem = new Filesystem(); 85 | $this->Timer = new Timer(); 86 | $this->Assets = new Assets($this->Filesystem, $this->Timer, $offline); 87 | $this->Filesystem->mkdir($this->location); 88 | $this->grav = Grav::instance(); 89 | if (!empty($preset)) { 90 | $presetLocation = $this->grav['locator']->findResource( 91 | 'user-data://persist/presets/' . $preset, 92 | true, 93 | true 94 | ); 95 | if ($this->Filesystem->exists($presetLocation)) { 96 | $Config = new Config( 97 | $this->grav['config'], 98 | 'user-data://persist/presets', 99 | $preset 100 | ); 101 | $this->grav['config']->merge($Config->config->toArray()); 102 | } 103 | $this->parameters = Config::getPresetParameters( 104 | $this->grav['config'], 105 | $preset 106 | ); 107 | } 108 | Config::applyParameters( 109 | $this->grav['config'], 110 | $this->grav['twig'], 111 | $this->parameters 112 | ); 113 | if ($this->route == '/') { 114 | $this->collection = '@root.descendants'; 115 | } 116 | if (in_array('all', $this->filters)) { 117 | $this->pages = $this->grav['pages']->all(); 118 | } else { 119 | $this->pages = $this->grav['page']->evaluate( 120 | [$this->collection => $this->route] 121 | ); 122 | } 123 | $this->pages = $this->filterCollection($this->pages, $this->filters); 124 | unset($this->grav['page']); 125 | $this->grav['page'] = $this->grav['pages']->dispatch($this->route); 126 | $this->count = $this->count(); 127 | } 128 | 129 | /** 130 | * Filter Collection 131 | * 132 | * @param Collection $Collection Pages to filter 133 | * @param array $filters Methods to filter Collection by. 134 | * 135 | * @return Collection Filtered Pages 136 | */ 137 | public function filterCollection( 138 | Collection $Collection, 139 | array $filters 140 | ): Collection { 141 | foreach ($filters as $filter) { 142 | if (method_exists($Collection, $filter)) { 143 | $Collection->$filter(); 144 | } 145 | } 146 | return $Collection; 147 | } 148 | 149 | /** 150 | * Store routes and Build Page(s) 151 | * 152 | * @return void 153 | */ 154 | public function buildCollection(): void 155 | { 156 | foreach ($this->pages as $Page) { 157 | $this->routes[] = $Page->route(); 158 | } 159 | foreach ($this->pages as $Page) { 160 | try { 161 | $this->store($Page); 162 | } catch (\Exception $error) { 163 | throw new \Exception($error); 164 | } 165 | } 166 | } 167 | 168 | /** 169 | * Build Assets 170 | * 171 | * @return void 172 | */ 173 | public function buildAssets(): void 174 | { 175 | foreach ($this->grav['assets']['assets_css'] as $key => $Asset) { 176 | if (!in_array($Asset['asset'], $this->assets) && get_class($Asset) == 'Grav\Common\Assets\Css') { 177 | $this->assets[] = $Asset['asset']; 178 | } 179 | } 180 | foreach ($this->grav['assets']['assets_js'] as $key => $Asset) { 181 | if (!in_array($Asset['asset'], $this->assets) && get_class($Asset) == 'Grav\Common\Assets\Js') { 182 | $this->assets[] = $Asset['asset']; 183 | } 184 | } 185 | foreach ($this->assets as $asset) { 186 | $this->reporter( 187 | $this->Assets->copy( 188 | $asset, 189 | $this->location, 190 | $this->force 191 | ), 192 | 'white' 193 | ); 194 | } 195 | } 196 | 197 | /** 198 | * Mirror Static Assets 199 | * 200 | * @param array $folders Folders to mirror below /user. 201 | * @param array $extensions File-extensions to mirror. 202 | * 203 | * @return void 204 | */ 205 | public function mirrorStaticAssets( 206 | array $folders = ['/plugins', '/themes'], 207 | array $extensions = ['ttf', 'eot', 'otf', 'woff', 'woff2'] 208 | ): void { 209 | foreach ($folders as $folder) { 210 | $iterator = new \RecursiveIteratorIterator( 211 | new \RecursiveDirectoryIterator(GRAV_ROOT . '/user' . $folder) 212 | ); 213 | $fileIterator = new \RegexIterator($iterator, '/\.' . implode('|', $extensions) . '$/imu'); 214 | try { 215 | $this->Filesystem->mirror( 216 | GRAV_ROOT . '/user', 217 | $this->location . '/assets/user', 218 | $fileIterator, 219 | [ 220 | 'override' => true, 221 | 'copy_on_windows' => true, 222 | 'delete' => false, 223 | ] 224 | ); 225 | } catch (\Exception $e) { 226 | throw new \Exception($e); 227 | } 228 | } 229 | } 230 | 231 | /** 232 | * Mirror Images 233 | * 234 | * @param boolean $force Forcefully save data. 235 | * 236 | * @return void 237 | */ 238 | public function mirrorImages(bool $force): void 239 | { 240 | try { 241 | $this->Filesystem->mirror( 242 | GRAV_ROOT . '/images', 243 | $this->location . '/images', 244 | null, 245 | [ 246 | 'override' => true, 247 | 'copy_on_windows' => true, 248 | 'delete' => false, 249 | ] 250 | ); 251 | } catch (\Exception $e) { 252 | throw new \Exception($e); 253 | } 254 | } 255 | 256 | /** 257 | * Store Page 258 | * 259 | * @param Page $Page Grav Page instance. 260 | * 261 | * @return void 262 | */ 263 | public function store(Page $Page): void 264 | { 265 | $route = $Page->route() == '/' ? '' : $Page->route(); 266 | $template = $Page->template() . '.' . $Page->templateFormat() . '.twig'; 267 | if (!$this->grav['twig']->loader()->exists($template)) { 268 | $message = 'Template not loaded or found.'; 269 | $this->reporter( 270 | [ 271 | 'item' => $Page->title() . ' (' . $template . ')', 272 | 'location' => $message, 273 | 'time' => Timer::format($this->Timer->getTime()), 274 | ], 275 | 'red' 276 | ); 277 | Grav::instance()['log']->info($message); 278 | return; 279 | } 280 | try { 281 | $content = $this->grav['twig']->processTemplate( 282 | $template, 283 | ['page' => $Page] 284 | ); 285 | $content = Source::rewriteRoutes( 286 | $content, 287 | $this->routes 288 | ); 289 | $content = Source::rewriteAssetURLs($content, $this->rootPrefix); 290 | $content = Source::rewritePath( 291 | $content, 292 | Utils::url($Page->getMediaUri()), 293 | $this->rootPrefix . $route 294 | ); 295 | $content = Source::rewritePath( 296 | $content, 297 | '/user/pages', 298 | $this->rootPrefix . '' 299 | ); 300 | $content = Source::rewritePath( 301 | $content, 302 | '/images/', 303 | $this->rootPrefix . 'images/' 304 | ); 305 | $content = Source::rewriteMediaRoutes($content); 306 | } catch (\Exception $e) { 307 | throw new \Exception($e); 308 | } 309 | try { 310 | $file = 'index.' . $Page->templateFormat(); 311 | if ($this->force) { 312 | $this->Filesystem->remove($this->location . $route . DS . $file); 313 | } 314 | $this->Filesystem->dumpFile($this->location . $route . DS . $file, $content); 315 | $this->reporter( 316 | [ 317 | 'item' => $Page->title() . ' (' . $template . ')', 318 | 'location' => $this->location . $route . '/' . $file, 319 | 'time' => Timer::format($this->Timer->getTime()), 320 | ] 321 | ); 322 | foreach ($Page->media()->all() as $filename => $data) { 323 | $this->reporter( 324 | $this->Assets->copyMedia( 325 | $filename, 326 | $data->path, 327 | $this->location . $Page->route(), 328 | $this->force 329 | ), 330 | 'yellow' 331 | ); 332 | } 333 | } catch (\Exception $e) { 334 | throw new \Exception($e); 335 | } 336 | } 337 | 338 | /** 339 | * Count items 340 | * 341 | * @return int 342 | */ 343 | public function count(): int 344 | { 345 | return count($this->pages); 346 | } 347 | 348 | /** 349 | * Increase counter 350 | * 351 | * @return void 352 | */ 353 | public function progress(): void 354 | { 355 | $this->progress++; 356 | } 357 | 358 | /** 359 | * Report results 360 | * 361 | * @param array $items Items to report 362 | * 363 | * @return void 364 | */ 365 | public function reporter(array $items): void 366 | { 367 | foreach ($items as $item) { 368 | echo $item . "\n"; 369 | } 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /vendor/composer/ClassLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see http://www.php-fig.org/psr/psr-0/ 41 | * @see http://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | // PSR-4 46 | private $prefixLengthsPsr4 = array(); 47 | private $prefixDirsPsr4 = array(); 48 | private $fallbackDirsPsr4 = array(); 49 | 50 | // PSR-0 51 | private $prefixesPsr0 = array(); 52 | private $fallbackDirsPsr0 = array(); 53 | 54 | private $useIncludePath = false; 55 | private $classMap = array(); 56 | private $classMapAuthoritative = false; 57 | private $missingClasses = array(); 58 | private $apcuPrefix; 59 | 60 | public function getPrefixes() 61 | { 62 | if (!empty($this->prefixesPsr0)) { 63 | return call_user_func_array('array_merge', $this->prefixesPsr0); 64 | } 65 | 66 | return array(); 67 | } 68 | 69 | public function getPrefixesPsr4() 70 | { 71 | return $this->prefixDirsPsr4; 72 | } 73 | 74 | public function getFallbackDirs() 75 | { 76 | return $this->fallbackDirsPsr0; 77 | } 78 | 79 | public function getFallbackDirsPsr4() 80 | { 81 | return $this->fallbackDirsPsr4; 82 | } 83 | 84 | public function getClassMap() 85 | { 86 | return $this->classMap; 87 | } 88 | 89 | /** 90 | * @param array $classMap Class to filename map 91 | */ 92 | public function addClassMap(array $classMap) 93 | { 94 | if ($this->classMap) { 95 | $this->classMap = array_merge($this->classMap, $classMap); 96 | } else { 97 | $this->classMap = $classMap; 98 | } 99 | } 100 | 101 | /** 102 | * Registers a set of PSR-0 directories for a given prefix, either 103 | * appending or prepending to the ones previously set for this prefix. 104 | * 105 | * @param string $prefix The prefix 106 | * @param array|string $paths The PSR-0 root directories 107 | * @param bool $prepend Whether to prepend the directories 108 | */ 109 | public function add($prefix, $paths, $prepend = false) 110 | { 111 | if (!$prefix) { 112 | if ($prepend) { 113 | $this->fallbackDirsPsr0 = array_merge( 114 | (array) $paths, 115 | $this->fallbackDirsPsr0 116 | ); 117 | } else { 118 | $this->fallbackDirsPsr0 = array_merge( 119 | $this->fallbackDirsPsr0, 120 | (array) $paths 121 | ); 122 | } 123 | 124 | return; 125 | } 126 | 127 | $first = $prefix[0]; 128 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 129 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 130 | 131 | return; 132 | } 133 | if ($prepend) { 134 | $this->prefixesPsr0[$first][$prefix] = array_merge( 135 | (array) $paths, 136 | $this->prefixesPsr0[$first][$prefix] 137 | ); 138 | } else { 139 | $this->prefixesPsr0[$first][$prefix] = array_merge( 140 | $this->prefixesPsr0[$first][$prefix], 141 | (array) $paths 142 | ); 143 | } 144 | } 145 | 146 | /** 147 | * Registers a set of PSR-4 directories for a given namespace, either 148 | * appending or prepending to the ones previously set for this namespace. 149 | * 150 | * @param string $prefix The prefix/namespace, with trailing '\\' 151 | * @param array|string $paths The PSR-4 base directories 152 | * @param bool $prepend Whether to prepend the directories 153 | * 154 | * @throws \InvalidArgumentException 155 | */ 156 | public function addPsr4($prefix, $paths, $prepend = false) 157 | { 158 | if (!$prefix) { 159 | // Register directories for the root namespace. 160 | if ($prepend) { 161 | $this->fallbackDirsPsr4 = array_merge( 162 | (array) $paths, 163 | $this->fallbackDirsPsr4 164 | ); 165 | } else { 166 | $this->fallbackDirsPsr4 = array_merge( 167 | $this->fallbackDirsPsr4, 168 | (array) $paths 169 | ); 170 | } 171 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 172 | // Register directories for a new namespace. 173 | $length = strlen($prefix); 174 | if ('\\' !== $prefix[$length - 1]) { 175 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 176 | } 177 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 178 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 179 | } elseif ($prepend) { 180 | // Prepend directories for an already registered namespace. 181 | $this->prefixDirsPsr4[$prefix] = array_merge( 182 | (array) $paths, 183 | $this->prefixDirsPsr4[$prefix] 184 | ); 185 | } else { 186 | // Append directories for an already registered namespace. 187 | $this->prefixDirsPsr4[$prefix] = array_merge( 188 | $this->prefixDirsPsr4[$prefix], 189 | (array) $paths 190 | ); 191 | } 192 | } 193 | 194 | /** 195 | * Registers a set of PSR-0 directories for a given prefix, 196 | * replacing any others previously set for this prefix. 197 | * 198 | * @param string $prefix The prefix 199 | * @param array|string $paths The PSR-0 base directories 200 | */ 201 | public function set($prefix, $paths) 202 | { 203 | if (!$prefix) { 204 | $this->fallbackDirsPsr0 = (array) $paths; 205 | } else { 206 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 207 | } 208 | } 209 | 210 | /** 211 | * Registers a set of PSR-4 directories for a given namespace, 212 | * replacing any others previously set for this namespace. 213 | * 214 | * @param string $prefix The prefix/namespace, with trailing '\\' 215 | * @param array|string $paths The PSR-4 base directories 216 | * 217 | * @throws \InvalidArgumentException 218 | */ 219 | public function setPsr4($prefix, $paths) 220 | { 221 | if (!$prefix) { 222 | $this->fallbackDirsPsr4 = (array) $paths; 223 | } else { 224 | $length = strlen($prefix); 225 | if ('\\' !== $prefix[$length - 1]) { 226 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 227 | } 228 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 229 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 230 | } 231 | } 232 | 233 | /** 234 | * Turns on searching the include path for class files. 235 | * 236 | * @param bool $useIncludePath 237 | */ 238 | public function setUseIncludePath($useIncludePath) 239 | { 240 | $this->useIncludePath = $useIncludePath; 241 | } 242 | 243 | /** 244 | * Can be used to check if the autoloader uses the include path to check 245 | * for classes. 246 | * 247 | * @return bool 248 | */ 249 | public function getUseIncludePath() 250 | { 251 | return $this->useIncludePath; 252 | } 253 | 254 | /** 255 | * Turns off searching the prefix and fallback directories for classes 256 | * that have not been registered with the class map. 257 | * 258 | * @param bool $classMapAuthoritative 259 | */ 260 | public function setClassMapAuthoritative($classMapAuthoritative) 261 | { 262 | $this->classMapAuthoritative = $classMapAuthoritative; 263 | } 264 | 265 | /** 266 | * Should class lookup fail if not found in the current class map? 267 | * 268 | * @return bool 269 | */ 270 | public function isClassMapAuthoritative() 271 | { 272 | return $this->classMapAuthoritative; 273 | } 274 | 275 | /** 276 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 277 | * 278 | * @param string|null $apcuPrefix 279 | */ 280 | public function setApcuPrefix($apcuPrefix) 281 | { 282 | $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; 283 | } 284 | 285 | /** 286 | * The APCu prefix in use, or null if APCu caching is not enabled. 287 | * 288 | * @return string|null 289 | */ 290 | public function getApcuPrefix() 291 | { 292 | return $this->apcuPrefix; 293 | } 294 | 295 | /** 296 | * Registers this instance as an autoloader. 297 | * 298 | * @param bool $prepend Whether to prepend the autoloader or not 299 | */ 300 | public function register($prepend = false) 301 | { 302 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 303 | } 304 | 305 | /** 306 | * Unregisters this instance as an autoloader. 307 | */ 308 | public function unregister() 309 | { 310 | spl_autoload_unregister(array($this, 'loadClass')); 311 | } 312 | 313 | /** 314 | * Loads the given class or interface. 315 | * 316 | * @param string $class The name of the class 317 | * @return bool|null True if loaded, null otherwise 318 | */ 319 | public function loadClass($class) 320 | { 321 | if ($file = $this->findFile($class)) { 322 | includeFile($file); 323 | 324 | return true; 325 | } 326 | } 327 | 328 | /** 329 | * Finds the path to the file where the class is defined. 330 | * 331 | * @param string $class The name of the class 332 | * 333 | * @return string|false The path if found, false otherwise 334 | */ 335 | public function findFile($class) 336 | { 337 | // class map lookup 338 | if (isset($this->classMap[$class])) { 339 | return $this->classMap[$class]; 340 | } 341 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 342 | return false; 343 | } 344 | if (null !== $this->apcuPrefix) { 345 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 346 | if ($hit) { 347 | return $file; 348 | } 349 | } 350 | 351 | $file = $this->findFileWithExtension($class, '.php'); 352 | 353 | // Search for Hack files if we are running on HHVM 354 | if (false === $file && defined('HHVM_VERSION')) { 355 | $file = $this->findFileWithExtension($class, '.hh'); 356 | } 357 | 358 | if (null !== $this->apcuPrefix) { 359 | apcu_add($this->apcuPrefix.$class, $file); 360 | } 361 | 362 | if (false === $file) { 363 | // Remember that this class does not exist. 364 | $this->missingClasses[$class] = true; 365 | } 366 | 367 | return $file; 368 | } 369 | 370 | private function findFileWithExtension($class, $ext) 371 | { 372 | // PSR-4 lookup 373 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 374 | 375 | $first = $class[0]; 376 | if (isset($this->prefixLengthsPsr4[$first])) { 377 | $subPath = $class; 378 | while (false !== $lastPos = strrpos($subPath, '\\')) { 379 | $subPath = substr($subPath, 0, $lastPos); 380 | $search = $subPath . '\\'; 381 | if (isset($this->prefixDirsPsr4[$search])) { 382 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 383 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 384 | if (file_exists($file = $dir . $pathEnd)) { 385 | return $file; 386 | } 387 | } 388 | } 389 | } 390 | } 391 | 392 | // PSR-4 fallback dirs 393 | foreach ($this->fallbackDirsPsr4 as $dir) { 394 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 395 | return $file; 396 | } 397 | } 398 | 399 | // PSR-0 lookup 400 | if (false !== $pos = strrpos($class, '\\')) { 401 | // namespaced class name 402 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 403 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 404 | } else { 405 | // PEAR-like class name 406 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 407 | } 408 | 409 | if (isset($this->prefixesPsr0[$first])) { 410 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 411 | if (0 === strpos($class, $prefix)) { 412 | foreach ($dirs as $dir) { 413 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 414 | return $file; 415 | } 416 | } 417 | } 418 | } 419 | } 420 | 421 | // PSR-0 fallback dirs 422 | foreach ($this->fallbackDirsPsr0 as $dir) { 423 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 424 | return $file; 425 | } 426 | } 427 | 428 | // PSR-0 include paths. 429 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 430 | return $file; 431 | } 432 | 433 | return false; 434 | } 435 | } 436 | 437 | /** 438 | * Scope isolated include. 439 | * 440 | * Prevents access to $this/self from included files. 441 | */ 442 | function includeFile($file) 443 | { 444 | include $file; 445 | } 446 | --------------------------------------------------------------------------------