├── pages
├── index.php
├── system.cache_warmup.start.php
├── generator.php
└── warmup.php
├── assets
├── css
│ └── cache-warmup.css
└── js
│ ├── timer.jquery.min.js
│ ├── cache-warmup.js
│ └── handlebars.min.js
├── .github
└── workflows
│ └── publish-to-redaxo.yml
├── lib
├── generator_images.php
├── writer.php
├── generator.php
├── generator_pages.php
└── selector.php
├── package.yml
├── LICENSE
├── lang
├── en_gb.lang
├── sv_se.lang
├── de_de.lang
├── es_es.lang
└── pt_br.lang
├── boot.php
├── CHANGELOG.md
└── README.md
/pages/index.php:
--------------------------------------------------------------------------------
1 | i18n('title'));
6 | rex_be_controller::includeCurrentPageSubPath();
7 |
--------------------------------------------------------------------------------
/assets/css/cache-warmup.css:
--------------------------------------------------------------------------------
1 | .panel-cache-warmup .panel-body {
2 | height: 250px;
3 | overflow: hidden;
4 | overflow-y: auto;
5 | -webkit-overflow-scrolling: touch;
6 | }
7 |
8 | /* hide be_style customizer title here as our popup is too small! */
9 | #rex-page-cache-warmup-warmup .be-style-customizer-title {
10 | display: none;
11 | }
--------------------------------------------------------------------------------
/.github/workflows/publish-to-redaxo.yml:
--------------------------------------------------------------------------------
1 | name: Publish release
2 |
3 | on:
4 | release:
5 | types:
6 | - published
7 |
8 | jobs:
9 | redaxo_publish:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v2
13 | - uses: FriendsOfREDAXO/installer-action@v1
14 | with:
15 | myredaxo-username: ${{ secrets.MYREDAXO_USERNAME }}
16 | myredaxo-api-key: ${{ secrets.MYREDAXO_API_KEY }}
17 | description: ${{ github.event.release.body }}
18 |
--------------------------------------------------------------------------------
/pages/system.cache_warmup.start.php:
--------------------------------------------------------------------------------
1 | ' . rex_i18n::rawMsg('cache_warmup_description') . '
';
6 | $content .= '' . rex_i18n::rawMsg('cache_warmup_button_start') . '
';
7 |
8 | $fragment = new rex_fragment();
9 | $fragment->setVar('title', rex_i18n::rawMsg('cache_warmup_title'));
10 | $fragment->setVar('body', $content, false);
11 | echo $fragment->parse('core/page/section.php');
12 |
--------------------------------------------------------------------------------
/lib/generator_images.php:
--------------------------------------------------------------------------------
1 | isAvailable()) {
17 | foreach ($items as $item) {
18 | [$image, $type] = $item;
19 | rex_media_manager::create($type, $image);
20 | }
21 | }
22 | return $items;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/writer.php:
--------------------------------------------------------------------------------
1 | setSubject(false);
15 | });
16 | }
17 |
18 | /**
19 | * Build JSON object from array.
20 | *
21 | * @param array $items
22 | * @return string
23 | */
24 | public static function buildJSON(array $items): string
25 | {
26 | return (string) json_encode($items);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/generator.php:
--------------------------------------------------------------------------------
1 | isAvailable()) {
17 | foreach ($items as $item) {
18 | [$article_id, $clang] = $item;
19 |
20 | // generate content
21 | $article = new rex_article_content($article_id, $clang);
22 | $article->getArticleTemplate();
23 |
24 | // generate meta
25 | rex_article_cache::generateMeta($article_id, $clang);
26 |
27 | // generate lists
28 | rex_article_cache::generateLists($article_id);
29 | }
30 | }
31 | return $items;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/package.yml:
--------------------------------------------------------------------------------
1 | package: cache_warmup
2 | version: '4.0.0'
3 | author: Friends Of REDAXO
4 | supportpage: https://github.com/FriendsOfREDAXO/cache_warmup
5 |
6 | page:
7 | title: translate:title
8 | perm: admin
9 | popup: true
10 | hidden: true
11 | subpages:
12 | warmup:
13 | title: translate:title
14 | popup: true
15 | hidden: true
16 | generator:
17 | title: generator
18 | hidden: true
19 | hasLayout: false
20 |
21 | pages:
22 | system/cache_warmup:
23 | title: translate:title
24 | perm: admin
25 | subpages:
26 | start: { title: translate:page_start }
27 | readme: { title: translate:page_readme, subPath: README.md }
28 |
29 | requires:
30 | redaxo: '^5.4'
31 | php: '>=7.4'
32 |
33 | # define conflicts: prevents packages from update to avoid breaking changes
34 | conflicts:
35 | packages:
36 | media_manager: '>=3'
37 | metainfo: '>=3'
38 | structure: '>=3'
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Friends Of REDAXO
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 |
--------------------------------------------------------------------------------
/pages/generator.php:
--------------------------------------------------------------------------------
1 | isValid()) {
20 | $proceed = false; // token not valid, do not proceed!
21 | }
22 | rex::setProperty('redaxo', $mode); // reset to previous mode
23 | }
24 |
25 | if (!$proceed) {
26 | // do not proceed but send a 403 forbidden
27 | rex_response::cleanOutputBuffers();
28 | rex_response::setStatus(rex_response::HTTP_FORBIDDEN);
29 | } else {
30 | // generate page cache
31 | $pages = rex_get('pages', 'string');
32 | if (strlen($pages) > 0) {
33 | $generator = new cache_warmup_generator_pages();
34 | $items = cache_warmup_generator::prepareItems($pages);
35 | $generator->generateCache($items);
36 | }
37 |
38 | // generate image cache
39 | $images = rex_get('images', 'string');
40 | if (strlen($images) > 0) {
41 | $generator = new cache_warmup_generator_images();
42 | $items = cache_warmup_generator::prepareItems($images);
43 | $items = cache_warmup_selector::getImageNames($items);
44 | $generator->generateCache($items);
45 | }
46 |
47 | // clear output
48 | cache_warmup_writer::clearOutput();
49 | rex_response::setStatus(rex_response::HTTP_OK);
50 | }
51 |
--------------------------------------------------------------------------------
/lang/en_gb.lang:
--------------------------------------------------------------------------------
1 | cache_warmup_title = Cache Warmup
2 | cache_warmup_description = Generates cache files for all pages and used images in advance to improve the initial website performance.
3 | cache_warmup_page_start = Start
4 | cache_warmup_page_readme = Help and documentation
5 |
6 | cache_warmup_button_start = Generate cache
7 | cache_warmup_button_success = Cool, thanks!
8 | cache_warmup_button_cancel = Cancel
9 | cache_warmup_button_again = Try again
10 |
11 | cache_warmup_pages_title = Generating pages…
12 | cache_warmup_pages_progress = Page 1 of …
13 |
14 | cache_warmup_images_title = Generating images…
15 | cache_warmup_images_progress = Image 1 of …
16 |
17 | cache_warmup_time_elapsed = Time elapsed
18 |
19 | cache_warmup_finished_title = Finished: cache prepared.
20 | cache_warmup_finished_text = The cache has been prepared for pages and images. It took some and will save users from waiting too long when calling up the website for the first time.
21 |
22 | cache_warmup_error_title = Damn, something went wrong!
23 | cache_warmup_error_text = An error occured while generating the cache. Find details on the error page. We appreciate any support in improving the addon quality if you report issues at Github. Thank you!
24 | cache_warmup_error_link = Link to error page
25 |
26 | cache_warmup_nothing_title = No content?
27 | cache_warmup_nothing_text = I’m sorry but there’s nothing to cache. No images or text. Either REDAXO is empty or the required addons (media_manager for images and structure for content) are not available.
28 |
--------------------------------------------------------------------------------
/lang/sv_se.lang:
--------------------------------------------------------------------------------
1 | cache_warmup_title = Cache Warmup
2 | cache_warmup_description = Genererar cachefiler för alla sidor och använda bilder i förväg för att förbättra prestandan vid första besöket på webbsidan.
3 | cache_warmup_page_start = Start
4 | cache_warmup_page_readme = Hjälp och dokumentation
5 |
6 | cache_warmup_button_start = Skapa cachen
7 | cache_warmup_button_success = Cool, tack!
8 | cache_warmup_button_cancel = Avbryta
9 | cache_warmup_button_again = Försök igen
10 |
11 | cache_warmup_pages_title = Skapa sidor ...
12 | cache_warmup_pages_progress = Sida 1 från …
13 |
14 | cache_warmup_images_title = Skapa bilder ...
15 | cache_warmup_images_progress = Bild 1 från …
16 |
17 | cache_warmup_time_elapsed = Tiden som gått
18 |
19 | cache_warmup_finished_title = Färdig: Cachen skapades
20 | cache_warmup_finished_text = Cacheminnet har förberedds för sidor span> och span> bilder span> strong>. Det tog lite strong> och kommer att spara tid för användare från att vänta för länge när de öppnar webbplatsen för första gången.
21 |
22 | cache_warmup_error_title = Något har gått fel!
23 | cache_warmup_error_text = Ett fel uppstod när cacheminnet genererades. Se detaljer på felsidan. Vi uppskattar allt stöd för att förbättra kvalitén av tillägget om du rapporterar problem på Github . Tack!
24 | cache_warmup_error_link = Länk till felsidan
25 |
26 | cache_warmup_nothing_title = Inget innehåll?
27 | cache_warmup_nothing_text = Jag är ledsen men det finns inget att cachar. Inga bilder eller text. Antingen REDAXO är tom eller de nödvändiga tilläggen ( media_manager för bilder och struktur för innehåll) är inte tillgängliga.
28 |
--------------------------------------------------------------------------------
/lang/de_de.lang:
--------------------------------------------------------------------------------
1 | cache_warmup_title = Cache-Warmup
2 | cache_warmup_description = Generiert den Cache für alle Seiten und verwendeten Bilder vorab, so dass die Website bereits beim ersten Aufruf performant läuft.
3 | cache_warmup_page_start = Start
4 | cache_warmup_page_readme = Hilfe und Dokumentation
5 |
6 | cache_warmup_button_start = Cache jetzt generieren
7 | cache_warmup_button_success = Prima, danke!
8 | cache_warmup_button_cancel = Abbrechen
9 | cache_warmup_button_again = Nochmal versuchen
10 |
11 | cache_warmup_pages_title = Generiere Seiten…
12 | cache_warmup_pages_progress = Seite 1 von …
13 |
14 | cache_warmup_images_title = Generiere Bilder…
15 | cache_warmup_images_progress = Bild 1 von …
16 |
17 | cache_warmup_time_elapsed = Verstrichene Zeit
18 |
19 | cache_warmup_finished_title = Fertig: Cache generiert
20 | cache_warmup_finished_text = Der Cache wurde für Seiten und Bilder vorbereitet. Das hat etwa gedauert und sorgt dafür, dass nun niemand mehr bei den ersten Seitenaufrufen unnötig lange warten muss.
21 |
22 | cache_warmup_error_title = Verdammt, etwas ist schief gelaufen!
23 | cache_warmup_error_text = Beim Erzeugen des Caches ist ein Fehler aufgetreten. Details befinden sich auf der Fehlerseite. Wir freuen uns über jede Mithilfe, die Qualität des Addons zu verbessern, indem Fehler bei Github gemeldet werden. Vielen Dank!
24 | cache_warmup_error_link = Link zur Fehlerseite
25 |
26 | cache_warmup_nothing_title = Keine Inhalte?
27 | cache_warmup_nothing_text = Tut mir leid, aber es gibt nichts zum Cachen. Keine Bilder und keine Texte. Entweder ist REDAXO leer, oder die notwendigen Addons (media_manager für Bilder und structure für Inhalte) sind nicht installiert.
28 |
--------------------------------------------------------------------------------
/lang/es_es.lang:
--------------------------------------------------------------------------------
1 | cache_warmup_title = Calentamiento de caché
2 | cache_warmup_description = Genera la memoria caché para todas las páginas y las imágenes usadas por adelantado, de modo que el sitio web se ejecuta rápidamente en la primera llamada.
3 | cache_warmup_page_start = Comienzo
4 | cache_warmup_page_readme = Ayuda y documentación
5 |
6 | cache_warmup_button_start = Generar caché
7 | cache_warmup_button_success = Genial, gracias
8 | cache_warmup_button_cancel = Cancelar
9 | cache_warmup_button_again = Intenta de nuevo
10 |
11 | cache_warmup_pages_title = Generar páginas
12 | cache_warmup_pages_progress = Página 1 de ...
13 |
14 | cache_warmup_images_title = Generando imágenes...
15 | cache_warmup_images_progress = Imagen 1 de …
16 |
17 | cache_warmup_time_elapsed = Tiempo transcurrido
18 |
19 | cache_warmup_finished_title = Terminado: caché generado
20 | cache_warmup_finished_text = La memoria caché se ha preparado para páginas y imágenes . Tomó un poco de y quieren ahorrar a los usuarios de esperar demasiado tiempo Al llamar el sitio web por primera vez.
21 |
22 | cache_warmup_error_title = Maldición, algo salió mal!
23 | cache_warmup_error_text = Se produjo un error al generar el caché. Encuentra detalles en la página de error. Github. Agradecemos cualquier ayuda para mejorar la calidad del complemento. ¡Gracias!
24 | cache_warmup_error_link = Enlace a la página de error
25 |
26 | cache_warmup_nothing_title = ¿Sin contenido?
27 | cache_warmup_nothing_text = Lo siento, pero no hay nada que almacenar en caché. No hay fotos ni textos O bien REDAXO está vacío, o los complementos necesarios (media_manager para imágenes y estructura para el contenido) no están instalados.
28 |
--------------------------------------------------------------------------------
/lang/pt_br.lang:
--------------------------------------------------------------------------------
1 | cache_warmup_title = Cache Warmup
2 | cache_warmup_description = Gera arquivos de cache com antecedência para todas as páginas e imagens usadas para melhorar o desempenho inicial do site.
3 | cache_warmup_page_start = Começar
4 | cache_warmup_page_readme = Ajuda e documentação
5 |
6 | cache_warmup_button_start = Gera chachês
7 | cache_warmup_button_success = Legal, obrigada!
8 | cache_warmup_button_cancel = Cancelar
9 | cache_warmup_button_again = Tende de novo
10 |
11 | cache_warmup_pages_title = Gerando páginas..
12 | cache_warmup_pages_progress = Página 1 of …
13 |
14 | cache_warmup_images_title = Gerando imagens...
15 | cache_warmup_images_progress = Imagem 1 of …
16 |
17 | cache_warmup_time_elapsed = Tempo decorrido
18 |
19 | cache_warmup_finished_title = Concluído: cachê preparado.
20 | cache_warmup_finished_text = O cachê foi preparado para pages e imagens. Demorou um tempo
The cache has been prepared for pages and images. It took some E irá salvar os usuários de esperar muito tempo ao chamar o site pela primeira vez. P>
21 |
22 | cache_warmup_error_title = Merda, algo deu errado!
23 | cache_warmup_error_text =
Um erro ocorreu ao gerar o cachê. Mais detalhes na página. Nós apreciamos qualquer ajuda para melhorar a qualidade do addon, se você reportar os erros aqui: Github. Obrigado!
24 | cache_warmup_error_link = Lindo para a página de erros
25 |
26 | cache_warmup_nothing_title = Sem conteúdo?
27 | cache_warmup_nothing_text = Desculpe-nos, mas não há nada no cachê. O REDAXO ou os addons requeridos estão vazios (media_manager para imagens structure e conteúdo) não estão disponíveis.
28 |
--------------------------------------------------------------------------------
/boot.php:
--------------------------------------------------------------------------------
1 | ['min' => 10, 'max' => 50, 'ratio' => 0.4],
13 | 'chunkSizePages' => ['min' => 100, 'max' => 1000, 'ratio' => 6],
14 | ];
15 |
16 | // get `max_execution_time`
17 | // if it’s 0 (false), set to a low value
18 | $executionTime = (int) ini_get('max_execution_time');
19 | if (0 === $executionTime) {
20 | $executionTime = 30;
21 | }
22 |
23 | // define number of items to generate per single request based on `max_execution_time`
24 | // higher values reduce number of requests but extend script time
25 | // (hint: enable debug mode in REDAXO to report execution times)
26 | foreach ($chunksConfig as $k => $v) {
27 | $numOfItems = round($executionTime * $v['ratio']);
28 |
29 | if ($numOfItems > $v['max']) {
30 | // limit to max value
31 | // hint: executionTime === 0 equates to infinite!
32 | $this->setConfig($k, $v['max']);
33 | } elseif ($numOfItems < $v['min']) {
34 | // limit to min value
35 | $this->setConfig($k, $v['min']);
36 | } else {
37 | // set to calculated number of items
38 | $this->setConfig($k, $numOfItems);
39 | }
40 | }
41 | }
42 |
43 | // inject addon ressources
44 | if (rex::isBackend() && !is_null(rex::getUser()) && false !== strpos(rex_be_controller::getCurrentPage(), 'cache_warmup')) {
45 | if ('warmup' == rex_be_controller::getCurrentPagePart(2)) {
46 | rex_view::addJsFile($this->getAssetsUrl('js/handlebars.min.js'));
47 | rex_view::addJsFile($this->getAssetsUrl('js/timer.jquery.min.js'));
48 | }
49 |
50 | rex_view::addCssFile($this->getAssetsUrl('css/cache-warmup.css'));
51 | rex_view::addJsFile($this->getAssetsUrl('js/cache-warmup.js'));
52 | }
53 |
54 | // switch REDAXO to frontend mode before generating cache files
55 | // this is essential to include content modification by addons, e.g. slice status on/off
56 | rex_extension::register('PACKAGES_INCLUDED', static function () {
57 | if ('cache_warmup/generator' === rex_be_controller::getCurrentPage()) {
58 | rex::setProperty('redaxo', false);
59 | }
60 | }, rex_extension::EARLY);
61 |
--------------------------------------------------------------------------------
/assets/js/timer.jquery.min.js:
--------------------------------------------------------------------------------
1 | /*! timer.jquery 0.7.1 2017-09-27*/
2 | !function(n){var a={PLUGIN_NAME:"timer",TIMER_RUNNING:"running",TIMER_PAUSED:"paused",TIMER_REMOVED:"removed",DAYINSECONDS:86400};function s(t){var e;return t=t||0,e=Math.floor(t/60),{days:t>=a.DAYINSECONDS?Math.floor(t/a.DAYINSECONDS):0,hours:3600<=t?Math.floor(t%a.DAYINSECONDS/3600):0,totalMinutes:e,minutes:60<=t?Math.floor(t%3600/60):e,seconds:t%60,totalSeconds:t}}function r(t){return((t=parseInt(t,10))<10&&"0")+t}function e(){return Math.round((Date.now?Date.now():(new Date).getTime())/1e3)}function i(t){var e,n;return 0t.config.duration)&&(t.config.callback&&t.config.callback(),t.config.repeat||(clearInterval(t.intervalId),o(t,a.TIMER_STOPPED),t.config.duration=null))}};function c(t,e){if(this.element=t,this.originalConfig=n.extend({},e),this.totalSeconds=0,this.intervalId=null,this.html="html","INPUT"!==t.tagName&&"TEXTAREA"!==t.tagName||(this.html="val"),this.config=d.getDefaultConfig(),e.duration&&(e.duration=d.durationTimeToSeconds(e.duration)),"string"!=typeof e&&(this.config=n.extend(this.config,e)),this.config.seconds&&(this.totalSeconds=this.config.seconds),this.config.editable&&d.makeEditable(this),this.startTime=d.unixSeconds()-this.totalSeconds,this.config.duration&&this.config.repeat&&this.config.updateFrequency<1e3&&(this.config.updateFrequency=1e3),this.config.countdown){if(!this.config.duration)throw new Error("Countdown option set without duration!");if(this.config.editable)throw new Error("Cannot set editable on a countdown timer!");this.config.startTime=d.unixSeconds()-this.config.duration,this.totalSeconds=this.config.duration}}c.prototype.start=function(){this.state!==a.TIMER_RUNNING&&(d.setState(this,a.TIMER_RUNNING),this.render(),this.intervalId=setInterval(d.intervalHandler.bind(null,this),this.config.updateFrequency))},c.prototype.pause=function(){this.state===a.TIMER_RUNNING&&(d.setState(this,a.TIMER_PAUSED),clearInterval(this.intervalId))},c.prototype.resume=function(){this.state===a.TIMER_PAUSED&&(d.setState(this,a.TIMER_RUNNING),this.config.countdown?this.startTime=d.unixSeconds()-this.config.duration+this.totalSeconds:this.startTime=d.unixSeconds()-this.totalSeconds,this.intervalId=setInterval(d.intervalHandler.bind(null,this),this.config.updateFrequency))},c.prototype.remove=function(){clearInterval(this.intervalId),d.setState(this,a.TIMER_REMOVED),n(this.element).data(a.PLUGIN_NAME,null),n(this.element).data("seconds",null)},c.prototype.reset=function(){var t=this.originalConfig;this.remove(),n(this.element).timer(t)},c.prototype.render=function(){this.config.format?n(this.element)[this.html](d.secondsToFormattedTime(this.totalSeconds,this.config.format)):n(this.element)[this.html](d.secondsToPrettyTime(this.totalSeconds)),n(this.element).data("seconds",this.totalSeconds)},n.fn.timer=function(e){return e=e||"start",this.each(function(){n.data(this,a.PLUGIN_NAME)instanceof c||n.data(this,a.PLUGIN_NAME,new c(this,e));var t=n.data(this,a.PLUGIN_NAME);"string"==typeof e?"function"==typeof t[e]&&t[e]():t.start()})}}(jQuery);
3 |
--------------------------------------------------------------------------------
/pages/warmup.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | ';
10 |
11 | $footer = '
12 |
13 |
14 |
';
15 |
16 | $fragment = new rex_fragment();
17 | $fragment->setVar('class', 'cache-warmup');
18 | $fragment->setVar('body', $body, false);
19 | $fragment->setVar('footer', $footer, false);
20 | echo $fragment->parse('core/page/section.php');
21 |
22 | /* cache warmup items JSON */
23 | echo '';
24 |
25 | /* CSRF token (REX 5.5+) */
26 | if (class_exists('rex_csrf_token')) {
27 | echo '';
28 | }
29 |
30 | /* disable minibar (REX 5.7+) */
31 | if (class_exists('rex_minibar') && null === rex_minibar::getInstance()->isActive()) {
32 | rex_minibar::getInstance()->setActive(false);
33 | }
34 | ?>
35 |
36 |
37 |
38 |
39 |
47 |
48 |
54 |
55 |
56 |
57 |
58 |
61 |
62 |
63 |
68 |
69 |
70 |
71 |
72 |
75 |
76 |
77 |
80 |
81 |
82 |
85 |
86 |
87 |
90 |
91 |
92 |
95 |
96 |
97 |
98 |
99 |
102 |
103 |
104 |
107 |
108 |
109 |
110 |
111 |
114 |
115 |
116 |
119 |
120 |
121 |
124 |
125 |
126 |
127 |
128 |
131 |
132 |
133 |
136 |
137 |
138 |
141 |
142 |
143 |
144 |
145 |
148 |
149 |
150 |
151 |
152 |
155 |
156 |
157 |
163 |
164 |
165 |
170 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog: Cache Warmup
2 |
3 |
4 | ## [4.0.0](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/4.0.0) – 02.07.2022
5 |
6 | ### Breaking changes
7 |
8 | * Erfordert REDAXO 5.4 und PHP 7.4 als Mindestversionen
9 | Alter Code wurde entfernt, um die Komplexität zu verringern! 🦊
10 |
11 |
12 | ## [3.7.1](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/3.7.1) – 12.06.2022
13 |
14 | ### Bugfixes
15 |
16 | * Externe Pakete aktualisiert
17 | * Code aufgeräumt
18 |
19 |
20 | ## [3.7.0](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/3.7.0) – 20.11.2021
21 |
22 | ### Features
23 |
24 | * Dokumentation für den Dark Mode angepasst (REDAXO 5.13)
25 | * Konflikte mit YForm 4 entfernt
26 | * PHP-Mindestversion auf 7 erhöht
27 |
28 |
29 | ## [3.6.1](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/3.6.1) – 10.10.2019
30 |
31 | ### Bugfixes
32 |
33 | * Generator responds with HTTP 200 to provide YRewrite 2.6+ compat (#106)
34 |
35 |
36 | ## [3.6.0](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/3.6.0) – 10.02.2019
37 |
38 | ### Features
39 |
40 | * Funktioniert mit YForm 3 (#103)
41 |
42 |
43 | ## [3.5.0](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/3.5.0) – 24.12.2018
44 |
45 | ### Features
46 |
47 | * Debug-Modus wird nicht mehr innerhalb des JS aktiviert und deaktiviert, sondern hängt nun fest an REDAXOs Debug-Modus. (#92)
48 | * Dokumentation: Umgang mit Fehlern beim Warmup-Prozess
49 |
50 |
51 | ## [3.4.0](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/3.4.0) – 28.09.2018
52 |
53 | ### Features
54 |
55 | * Use `includeCurrentPageSubPath` (#94 @christophboecker)
56 | Requires at least REDAXO 5.1
57 |
58 | ### Bugfixes
59 |
60 | * fix wrong `rex_media::clearInstance` values (#97 @staabm)
61 |
62 |
63 | ## [3.3.1](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/3.3.1) – 14.07.2018
64 |
65 | ### Features
66 |
67 | * Performance: JS/CSS nur auf Warmup-Seiten laden (#83 @staabm)
68 | * Performance: Generierung von Medien optimiert (#84 @staabm)
69 | * Extension Points (EPs) zum Filtern der zu generierenden Objekte (#90 @schuer)
70 | * Spanische Übersetzung, Traducción en castellano. ¡Gracias! (#91 @nandes2062)
71 |
72 | Hilfe zur Benutzung der neuen Extension Points findet ihr in der README! 🚀
73 |
74 |
75 | ## [3.3.0](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/3.3.0) – 14.07.2018
76 |
77 | -> [3.3.1](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/3.3.1)
78 |
79 |
80 | ## [3.2.0](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/3.2.0) – 13.10.2017
81 |
82 | ### Features
83 |
84 | * Schwedisch. Tack så mycket @interweave-media! (#78)
85 |
86 | ### Bugfixes
87 |
88 | * Cache-Buster mittels AddOn-Version entfernt, weil REDAXO seit 5.3 einen eigenen mitbringt (#79)
89 |
90 |
91 | ## [3.1.1](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/3.1.1) – 29.09.2017
92 |
93 | ### Bugfixes
94 |
95 | * Konfiguration nur noch bei Aufruf der Warmup-Seite bearbeiten (#76 @IngoWinter)
96 | Verbessert die Performance und vermeidet Session-Lock-Probleme.
97 |
98 |
99 | ## [3.1.0](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/3.1.0) – 20.08.2017
100 |
101 | ### Features
102 |
103 | * YForm-Kompatibilität: Der Feldtyp `mediafile` (Uploads in den Medienpool) wird nun ebenfalls beachtet. (#74)
104 |
105 |
106 | ## [3.0.0](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/3.0.0) – 14.06.2017
107 |
108 | ### Features
109 |
110 | * __Template beim Generieren der Artikelcaches mit einbeziehen__ (#71)
111 | Bisher wurden nur die reinen Artikelinhalte generiert. Das war völlig okay, sorgte aber für Fehler, wenn innerhalb der Templates wichtige Dinge definiert wurden, so wie z. B. die Tabs der beliebten [Basisdemo](https://github.com/FriendsOfREDAXO/demo_base). Mit diesem Update lädt Cache-Warmup nicht mehr nur die Artikel, sondern auch die Templates drumrum.
112 | * __Generierung der Bildercaches über neue Funktionen des Media-Managers__ (#72)
113 | Der Media-Manager 2.3.0 enthält eine separate Methode (`rex_media_manager::create()`), um Cachefiles von Bildern mit allen Bildeffekten zu generieren. Diese benutzen wir nun auch für Cache-Warmup und haben unseren alten Code, der dafür notwendig war, als _deprecated_ markiert.
114 |
115 | ### Breaking changes
116 |
117 | * Artikelinhalte werden nun anders generiert als vorher, nämlich inklusive ihrer Templates drumrum. Weil das potentiell zu anderen Ergebnissen führen kann, als mit der vorherigen Version von Cache-Warmup, ist diese aktuelle Version ein _Major Release_.
118 |
119 |
120 | ## [2.3.0](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/2.3.0) – 10.06.2017
121 |
122 | ### Features
123 |
124 | * Extension Point (EP) `CACHE_WARMUP_IMAGES` hinzugefügt, um Entwickler\_innen die Möglichkeit zu geben, Bilder zu ergänzen, für die Cachefiles generiert werden. (@IngoWinter: #69, #70)
125 |
126 |
127 | ## [2.2.0](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/2.2.0) – 07.06.2017
128 |
129 | ### Features
130 |
131 | * Wechsel in den Frontend-Modus vorm Generieren der Cache-Files (#68)
132 | Dadurch werden auch Inhaltsanpassungen fremder AddOns beachtet, die mittels Extension Points die Ausgabe beeinflussen, z. B. der Slice-Status on/off durch [blÖcks](https://github.com/FriendsOfREDAXO/bloecks).
133 | * Portugiesisch, vielen Dank an Taina Soares! (#67)
134 |
135 |
136 | ## [2.1.1](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/2.1.1) – 29.03.2017
137 |
138 | Workaround für Nutzer:innen des [blÖcks](https://github.com/FriendsOfREDAXO/bloecks)-Addons: Cache-Warmup generiert vorerst keine Artikelinhalte mehr, solange der Slice-Status (Online/Offline) nicht beachtet wird. Mit diesem Update wird verhindert, dass Offline-Inhalte publiziert werden.
139 |
140 | Siehe Diskussion: https://github.com/FriendsOfREDAXO/cache_warmup/issues/65
141 |
142 |
143 | ## [2.1.0](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/2.1.0) – 13.03.2017
144 |
145 | ### Features
146 |
147 | * 💥 Cachefiles pro Request deutlich erhöht (#58) — Danke an alle fürs Abstimmen und Testen!
148 | * Konflikte mit anderen Addons/Plugins definiert (#51)
149 |
150 | ### Bugfixes
151 |
152 | * Unnötigen Parameter entfernt (#57, @staabm)
153 | * Darstellungsfehler mit dem be_style-Customizer behoben (#56)
154 | * Fehler beim Speicherüberlauf abfangen (#62)
155 |
156 | ### Security
157 |
158 | * ⚠️ Bilder-IDs prüfen und absichern (#63) — Danke @gharlan!
159 |
160 |
161 | ## [2.0.1](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/2.0.1) – 25.01.2017
162 |
163 | ### Bugfixes
164 |
165 | * Kompatibilität für REX <5.1 wiederhergestellt (#48)
166 |
167 |
168 | ## [2.0.0](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/2.0.0) – 20.01.2017
169 |
170 | Für dieses Release wurden einige Fehler behoben und Funktionen verbessert, so dass Cache-Warmup nun merklich weniger Speicher benötigt und deutlich schneller läuft als vorher. Hooray!
171 |
172 | ### Bugfixes & Improvements:
173 |
174 | * 💥 Speichernutzung beim Filtern von Bildern korrigiert (#36, @isospin @staabm @gharlan)
175 | * 💥 Speichernutzung beim Prüfen von Bildern massiv reduziert (#44, @gharlan)
176 | * 💥 Filterung der Artikel korrigiert (#43, @tbaddade @staabm)
177 | * Code vereinfacht und verbessert (#30 #34 #35, jeweils @staabm)
178 | * Addon-Beschreibung verbessert (#38)
179 | * PHP-Mindestversion definiert (#32)
180 | * `help.php` entfernt, so dass die Hilfefunktion nun den Inhalt der README anzeigt (#47)
181 |
182 | ### Breaking changes:
183 |
184 | * Sichtbarkeit einiger Methoden verringert (#33, @staabm)
185 | * `cache_warmup_writer::replaceOutputWith()` entfernt (#39)
186 | * `cache_warmup_selector::getLanguages()` entfernt (#43)
187 |
188 |
189 | ## [1.0.3](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/1.0.3) – 10.10.2016
190 |
191 | ### Bugfixes
192 |
193 | * Warnings im Systemlog unterbunden (#24)
194 |
195 |
196 | ## [1.0.2](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/1.0.2) – 18.08.2016
197 |
198 | ### Bugfixes
199 |
200 | * Popup nun mit Hinweis, falls keine Artikel oder Bilder zum Generieren vorhanden sind. #23
201 | * Bilder konnten nicht in Sprachen (clang) oder Medien (media) hinterlegt werden. #25
202 |
203 |
204 | ## [1.0.1](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/1.0.1) – 29.07.2016
205 |
206 | ### Bugfixes
207 |
208 | * Kleine PHP-Korrekturen und Aufräumarbeiten #19
209 | * Kleine JavaScript-Korrekturen und Aufräumarbeiten #20
210 |
211 |
212 | ## [1.0.0](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/1.0.0) – 02.06.2016
213 |
214 | ### Breaking changes
215 |
216 | * Unterstriche statt Bindestrichen (REDAXO-Standard, erforderlich für myREDAXO) #17
217 |
218 |
219 | ## [1.0.0-RC1](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/1.0.0-RC1) – 25.05.2016
220 |
221 | ### Bugfixes
222 |
223 | * Ajax Request mit Timestamp, um Caching zu vermeiden #15
224 |
225 | ### Breaking changes
226 |
227 | * Unterstriche statt Bindestrichen (REDAXO-Standard, erforderlich für myREDAXO) #16
228 |
229 |
230 | ## [1.0.0-beta4](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/1.0.0-beta4) – 20.05.2016
231 |
232 | ### Bugfixes
233 |
234 | * Assets-Caching vermeiden + kleine JS-Fixes #12
235 | * Fehler vermeiden, wenn kein Metainfo-Feld existiert #13
236 | * package.yml Verbesserungen #14
237 |
238 |
239 | ## [1.0.0-beta3](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/1.0.0-beta3) – 18.05.2016
240 |
241 | ### Bugfixes
242 |
243 | * JS-Fehler & Popup-Größe #10
244 |
245 |
246 | ## [1.0.0-beta2](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/1.0.0-beta2) – 18.05.2016
247 |
248 | ### Bugfixes
249 |
250 | * Kontext für Subpage system.cache-warmup (REX-5.0-Kompatibilität) #6
251 | * Bilder werden in manchen Umgebungen zu groß generiert #7
252 |
253 |
254 | ## [1.0.0-beta1](https://github.com/FriendsOfREDAXO/cache_warmup/releases/tag/1.0.0-beta1) – 18.05.2016
255 |
256 | Erstes Beta-Release
257 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cache-Warmup
2 |
3 | Generiert den Cache vorab, so dass die Website bereits beim Erstaufruf performant läuft.
4 |
5 | 
6 |
7 | ## Wofür wird das Addon benötigt?
8 |
9 | Manchmal hinterlegt man eine Website zur Ansicht auf einem Testserver. Häufig wird davor oder danach der REDAXO-Cache gelöscht, um veraltete Inhalte zu entfernen, die vielleicht noch aus der Entwicklungszeit enthalten sind. Danach allerdings müssen alle Inhalte neu generiert werden. REDAXO übernimmt dies eigenständig beim Aufruf jeder Seite.
10 |
11 | Diese initialen Seitenaufrufe können leider recht langsam sein, vor allem, wenn der Cache für viele Bilder generiert werden muss. Nutzer*innen, denen die technischen Hintergründe nicht bekannt sind, und die erstmalig die Website anschauen, könnten nun (fälschlicherweise) annehmen, REDAXO sei nicht sonderlich schnell. Verständlich, denn sie erhalten im ersten Moment keine performante Website.
12 |
13 | Das Cache-Warmup-Addon kann alle verwendeten Inhalte der Website vorab generieren, so dass danach niemand mehr unnötig lange warten muss.
14 |
15 | 🐿 __Protip:__ Weil Cache-Warmup alle Bilder und Seiten einmal durchläuft, ist es nebenher sehr nützlich, um zu prüfen, ob die Website fehlerfrei ausgeliefert wird. Ob also keine Bilder zu groß sind, um vom Webserver verarbeitet zu werden, und ob alle Templates und Module richtig funktionieren.
16 |
17 | ---
18 |
19 | ## 🤕 Fehler beim Warmup-Prozess
20 |
21 | Es kommt immer wieder vor, dass Cache-Warmup nicht vollständig durchläuft, sondern vorher mit einem Fehler abbricht. An dieser Stelle ein paar Infos dazu, welche Fehler vorkommen können, und wie du damit umgehen kannst:
22 |
23 | ### `RAM exceeded (internal)`
24 |
25 | __Ursache:__
26 | Der Arbeitsspeicher des Webservers reicht nicht aus, um alle Bilder zu verarbeiten. Dies ist ein Problem für deine Website, denn es bedeutet, dass die betroffenen Bilder nicht von REDAXOs Media Manager ausgegeben werden können, sondern stattdessen ein Fehlerbild angezeigt wird.
27 |
28 | __Maßnahmen:__
29 | Du solltest nun unbedingt deinen Medienpool prüfen und alle übergroßen Bilder — das betrifft nicht die Dateigröße, sondern die Pixel! — manuell verkleinern und neu hochladen. Wenn beispielsweise Bilder in deinem Medienpool liegen, die mit einer Digitalkamera aufgenommen worden sind und unbearbeitet hochgeladen wurden, dann sind sie womöglich ~6000 Pixel breit, und REDAXO würde mehr RAM benötigen, um davon Thumbnails zu erstellen, als auf typischen Webspaces verfügbar ist. Wenn du sie auf ~3000 Pixel verkleinerst und neu in den Medienpool lädst, sollte es hoffentlich klappen!
30 | Wenn möglich, kannst du alternativ auch einfach den Arbeitsspeicher des Webservers vergrößern. 256 MB sollten gut funktionieren, wenn du mit großen Bildern arbeitest.
31 |
32 | ### `Request Timeout (408)`
33 |
34 | __Ursache:__
35 | Das Generieren des Caches dauert zu lange, so dass die maximale Skriptlaufzeit (max\_execution\_time) des Webservers überschritten wird. Cache-Warmup versucht zwar, abhängig von der Skriptlaufzeit den Cache in kleinen Schritten zu generieren, aber manchmal — etwa bei sehr großen Bildern im Medienpool oder aufwendigen Templates/Modulen — sind selbst kleine Schritte noch zu groß.
36 |
37 | __Maßnahmen:__
38 | Du kannst Cache-Warmup verlangsamen, indem du diesen Code-Schnipsel in die `boot.php` des project-AddOns steckst. Der Wert für `$throttle` entspricht der Geschwindigkeit (1–100 in Prozent) und kann von dir angepasst werden. Im Code-Schnipsel wird beispielhaft mit 50% gearbeitet, so dass Cache-Warmup nur noch halb so schnell arbeitet wie üblich:
39 |
40 | ```php
41 | // decelerate Cache Warmup addOn
42 | rex_extension::register('PACKAGES_INCLUDED', function () {
43 | $addon = rex_addon::get('cache_warmup');
44 | $throttle = 50; // percent %
45 |
46 | $addon->setConfig('chunkSizeImages', ceil($addon->getConfig('chunkSizeImages') * $throttle / 100));
47 | $addon->setConfig('chunkSizePages', ceil($addon->getConfig('chunkSizePages') * $throttle / 100));
48 | });
49 | ```
50 |
51 | ### `Internal Server Error (500)`
52 |
53 | __Ursachen:__
54 | Dieser Fehler kann verschiedene Ursachen haben. Manchmal entsteht er aufgrund von Fehlern im PHP-Code, z. B. innerhalb von Templates oder Modulen. In solchen Fällen müsstest du das Problem eigentlich auch auf der Website sehen, wenn du die betroffenen Seiten aufrufst.
55 |
56 | __Maßnahmen:__
57 | Cache-Warmup bearbeitet normalerweise mehrere Seiten in einem Rutsch (etwa 100 bis 1000) und kann leider nicht unterscheiden, auf welchen davon Fehler auftreten. Um das herauszufinden, kannst du den Warmup für jede Seite einzeln durchlaufen lassen, indem du diesen Code-Schnipsel in die `boot.php` des project-AddOns mit aufnimmst:
58 |
59 | ```php
60 | // run Cache Warmup addOn in single page mode
61 | rex_extension::register('PACKAGES_INCLUDED', function () {
62 | rex_addon::get('cache_warmup')->setConfig('chunkSizePages', 1);
63 | });
64 | ```
65 |
66 | Womöglich dauert der Warmup-Vorgang nun sehr lange, das ist leider nicht zu ändern. Aber die URL der Fehlerseite liefert dir nun zumindest die ID samt ctype der Seite, auf der das Problem aufgetreten ist.
67 |
68 | ---
69 |
70 | ## Extension Points (EP)
71 |
72 | Das AddOn stellt verschiedene Extension Points bereit, um in die Auswahl der Artikel und Bilder, deren Cachefiles generiert werden sollen, manuell einzugreifen. Dies kann nützlich sein, um etwa Bilder zu ergänzen, die aus verschiedenen Gründen nicht vom AddOn erfasst worden sind, oder um bestimmte Kategorien oder Medientypen vom Generieren des Caches auszuschließen.
73 |
74 | | Extension Point | Beschreibung |
75 | | --------------------------------------- | ------------ |
76 | | `CACHE_WARMUP_GENERATE_PAGE` | Enthält den zu generierenden Artikel und die Sprache. Kann verwendet werden, um Artikel anhand verschiedener Kriterien auszulassen, wenn der Cache generiert wird. |
77 | | `CACHE_WARMUP_GENERATE_IMAGE` | Enthält das zu generierende Bild und den Medientyp. Kann verwendet werden, um Bilder anhand verschiedener Kriterien auszulassen, wenn der Cache generiert wird. |
78 | | `CACHE_WARMUP_IMAGES` | Ermöglicht, die Liste der vom AddOn ausgewählten Bilder zu bearbeiten. |
79 | | `CACHE_WARMUP_MEDIATYPES` | Ermöglicht, die Liste der vom AddOn ausgewählten Medientypen zu bearbeiten. |
80 | | `CACHE_WARMUP_PAGES_WITH_CLANGS` | Liefert alle zu generierenden Artikel in ihren Sprachen. Kann verwendet werden, um die Artikelliste zu bearbeiten, vor allem, um weitere Artikel mit Angabe der Sprache zu ergänzen. |
81 | | `CACHE_WARMUP_IMAGES_WITH_MEDIATYPES` | Liefert alle zu generierenden Bilder mit ihren Medientypen. Kann verwendet werden, um die Bilderliste zu bearbeiten, vor allem, um weitere Bilder mit Angabe des Medientyps zu ergänzen. |
82 |
83 | ## Anwendungsbeispiele für die Nutzung von EPs
84 |
85 | Die Beispiele zeigen verschiedene Anwendungsfälle und können beispielsweise __in der `boot.php` des project-AddOns__ hinterlegt werden.
86 |
87 | ### `CACHE_WARMUP_GENERATE_PAGE`
88 |
89 | Dieser EP wird unmittelbar vorm Generieren der Cachefiles jedes einzelnen Artikels angesprochen und ermöglicht, anhand verschiedener Kriterien den Artikel zu überspringen. Das Codebeispiel zeigt verschiedene Anwendungsfälle:
90 |
91 | ```php
92 | rex_extension::register('CACHE_WARMUP_GENERATE_PAGE', function (rex_extension_point $ep) {
93 | list($article_id, $clang) = $ep->getParams();
94 |
95 | $article = rex_article::get($article_id);
96 |
97 | // Artikel mit ID 42 auslassen
98 | if ($article_id == 42) {
99 | return false;
100 | }
101 |
102 | // Artikel der Kategorie 23 und deren Kindkategorien auslassen
103 | if (in_array(23, $article->getPathAsArray())) {
104 | return false;
105 | }
106 |
107 | // Sprache mit clang 2 komplett auslassen
108 | if ($clang == 2) {
109 | return false;
110 | }
111 |
112 | return true;
113 | });
114 | ```
115 |
116 | ### `CACHE_WARMUP_GENERATE_IMAGE `
117 |
118 | Dieser EP wird unmittelbar vorm Generieren der Cachefiles jedes einzelnen Bilders angesprochen und ermöglicht, anhand verschiedener Kriterien das Bild zu überspringen. Das Codebeispiel zeigt verschiedene Anwendungsfälle:
119 |
120 | ```php
121 | rex_extension::register('CACHE_WARMUP_GENERATE_IMAGE', function (rex_extension_point $ep) {
122 | list($image, $mediaType) = $ep->getParams();
123 |
124 | $media = rex_media::get($image);
125 | if ($media) {
126 | if ($media->isImage()) {
127 |
128 | // Bilder vom Typ SVG auslassen
129 | if ($media->getExtension() == 'svg') {
130 | return false;
131 | }
132 |
133 | // Bilder der Kategorie 2 auslassen
134 | if ($media->getCategoryId() == 2) {
135 | return false;
136 | }
137 |
138 | // MediaType 'photos' ausschließlich für Bilder der Kategorie 3 verwenden
139 | if ($mediaType == 'photos' && $media->getCategoryId() != 3) {
140 | return false;
141 | }
142 |
143 | // MediaType 'fullscreen' auslassen
144 | if ($mediaType == 'fullscreen') {
145 | return false;
146 | }
147 |
148 | // Interne REDAXO-MediaTypes (beginnen mit 'rex_') auslassen
149 | if (strpos($mediaType, 'rex_') !== false) {
150 | return false;
151 | }
152 | }
153 | rex_media::clearInstance($image);
154 | }
155 | return true;
156 | });
157 | ```
158 |
159 | ### `CACHE_WARMUP_IMAGES `
160 |
161 | Über diesen EP kann die Liste der vom AddOn erfassten Bilder modifiziert werden, um z. B. Bilder aus der Liste zu entfernen, deren Cachefiles nicht generiert werden sollen, oder um Bilder zu ergänzen, die aus verschiedenen Gründen nicht vom AddOn erfasst worden sind.
162 |
163 | ```php
164 | rex_extension::register('CACHE_WARMUP_IMAGES', function (rex_extension_point $ep) {
165 | $images = $ep->getSubject();
166 |
167 | // Bilder hinzufügen
168 | $images[] = 'dave-grohl.jpg';
169 | $images[] = 'pat-smear.jpg';
170 | $images[] = 'nate-mendel.jpg';
171 | $images[] = 'taylor-hawkins.jpg';
172 | $images[] = 'chris-shiflett.jpg';
173 |
174 | return $images;
175 | });
176 | ```
177 |
178 | ### `CACHE_WARMUP_MEDIATYPES `
179 |
180 | Über diesen EP können die im System hinterlegten Mediatypen modifiziert werden, um z. B. Mediatypen aus der Liste zu entfernen, die nicht zum Generieren von Cachefiles verwendet werden sollen, oder um eigene Mediatypen zu ergänzen.
181 |
182 | ```php
183 | rex_extension::register('CACHE_WARMUP_MEDIATYPES', function (rex_extension_point $ep) {
184 | $mediaTypes = $ep->getSubject();
185 | foreach ($mediaTypes as $k => $mediaType) {
186 |
187 | // MediaType 'content' auslassen
188 | if ($mediaType === 'content') {
189 | unset($mediaTypes[$k]);
190 | }
191 |
192 | // REDAXO-MediaTypes auslassen
193 | if (strpos($mediaType, 'rex_') !== false) {
194 | unset($mediaTypes[$k]);
195 | }
196 | }
197 | return $mediaTypes;
198 | });
199 | ```
200 |
201 | ### `CACHE_WARMUP_PAGES_WITH_CLANGS`
202 |
203 | Liefert alle zu generierenden Artikel in ihren Sprachen. Kann verwendet werden, um die Artikelliste zu bearbeiten, vor allem, um weitere Artikel mit Angabe der Sprache zu ergänzen, z. B. solche Artikel, die aufgrund ihres Offline-Status’ nicht vom AddOn erfasst worden sind.
204 |
205 | ```php
206 | rex_extension::register('CACHE_WARMUP_PAGES_WITH_CLANGS', function (rex_extension_point $ep) {
207 | $pages = $ep->getSubject();
208 |
209 | // Seite hinzufügen (article_id, clang)
210 | $pages[] = array(12, 1);
211 | $pages[] = array(12, 2);
212 |
213 | return $pages;
214 | });
215 | ```
216 |
217 | ### `CACHE_WARMUP_IMAGES_WITH_MEDIATYPES `
218 |
219 | Liefert alle zu generierenden Bilder mit ihren Medientypen. Kann verwendet werden, um die Bilderliste zu bearbeiten, vor allem, um weitere Bilder mit Angabe des Medientyps zu ergänzen.
220 |
221 | 🐿 __Protip:__ Dieser EP ist sehr nützlich im Umgang mit responsive Images und virtuellen Medientypen!
222 |
223 | ```php
224 | rex_extension::register('CACHE_WARMUP_IMAGES_WITH_MEDIATYPES', function (rex_extension_point $ep) {
225 | $images = $ep->getSubject();
226 |
227 | // Bild mit MediaType hinzufügen
228 | $images[] = array('dave-grohl.jpg', 'portrait');
229 |
230 | // Liste von Bildern mit Liste von MediaTypes hinzufügen
231 | $imagesToAdd = array(
232 | 'pat-smear.jpg',
233 | 'nate-mendel.jpg',
234 | 'taylor-hawkins.jpg',
235 | 'chris-shiflett.jpg'
236 | );
237 | $mediaTypesToAdd = array(
238 | 'type1',
239 | 'type2',
240 | 'type3'
241 | );
242 | foreach ($imagesToAdd as $image) {
243 |
244 | // Prüfen, Bilder vorhanden ist
245 | $media = rex_media::get($image);
246 | if ($media) {
247 | if ($media->isImage()) {
248 |
249 | // Bild mit Medientyp hinfügen
250 | foreach ($mediaTypesToAdd as $mediaType) {
251 | $images[] = array($image, $mediaType);
252 | }
253 | }
254 | rex_media::clearInstance($image);
255 | }
256 | }
257 |
258 | return $images;
259 | });
260 | ```
261 |
262 | ---
263 |
264 | ## Ich bin Entwickler*in. Was genau macht das Addon?
265 |
266 | 1. Es werden alle __Bilder__ erfasst, die in __Modulen, Metainfos und yforms__ verwendet werden, sowie alle definierten __MediaTypes__ des Media Managers. Verschiedene Extension Points (EPs) ermöglichen, die Liste der ausgewählten Bilder und MediaTypes zu bearbeiten, um zu kontrollieren, welche Cachefiles erstellt werden.
267 | 2. Es werden alle __Seiten__ erfasst, die online sind, sowie alle __Sprachen__. Verschiedene Extension Points (EPs) ermöglichen, die Liste zu bearbeiten.
268 | 3. Aus den erfassten Daten wird __ein großes Array erstellt__ mit Einträgen für jedes Bild mit jedem MediaType und jeder Seite in jeder Sprache. Beispiel: 10 Bilder mit 5 MediaTypes = 50 Bilder. 100 Seiten in 3 Sprachen = 300 Seiten.
269 | 4. Das große Array wird danach in viele Häppchen zerhackt, deren Größe von der __Skriptlaufzeit des Servers__ abhängt. Damit kann später gesteuert werden, wie viele Cachefiles bei jedem Request erstellt werden. Bilder benötigen dabei natürlich massiv mehr Serverressourcen als Seiten.
270 | 5. Das Array wird __als JSON im HTML des Popups__ ausgegeben, das das Generieren des Caches triggert, den Fortschritt zeigt und Infos ausgibt. Das Popup __parst das JSON__ und sendet __häppchenweise AJAX-Requests an einen Generator__.
271 | 6. Der Generator erstellt die Cachefiles für Bilder und Seiten. Die Angaben dazu, welche Bilder mit welchen Mediatypen und welche Seiten in welchen Sprachen erstellt werden sollen, befinden sich im __Query string__ der URL jedes AJAX-Request.
272 |
--------------------------------------------------------------------------------
/lib/selector.php:
--------------------------------------------------------------------------------
1 | getConfig('chunkSizePages'));
30 | $images['items'] = self::chunk(
31 | $images['items'],
32 | rex_addon::get('cache_warmup')->getConfig('chunkSizeImages')
33 | );
34 | }
35 |
36 | return [
37 | 'pages' => $pages,
38 | 'images' => $images,
39 | ];
40 | }
41 |
42 | /**
43 | * Get all images being used in REDAXO (pages, meta, yforms)
44 | * »X never, ever marks the spot.« (-- Indiana Jones).
45 | *
46 | * @throws rex_sql_exception
47 | *
48 | * @return array
49 | */
50 | private static function getImages(): array
51 | {
52 | if (rex_addon::get('media_manager')->isAvailable() && rex_addon::get('structure')->isAvailable()) {
53 | $images = [];
54 | $sql = rex_sql::factory();
55 |
56 | /* find images in pages (media1-10, medialist1-10) */
57 |
58 | $select = 'media1,media2,media3,media4,media5,media6,media7,media8,media9,media10,medialist1,medialist2,medialist3,medialist4,medialist5,medialist6,medialist7,medialist8,medialist9,medialist10';
59 | $sql->setQuery('SELECT '.$select.' FROM '.rex::getTablePrefix().'article_slice');
60 | foreach ($sql as $row) {
61 | foreach (range(1, 10) as $num) {
62 | if (is_string($row->getValue('media'.$num))) {
63 | $images[] = $row->getValue('media'.$num);
64 | }
65 | if (is_string($row->getValue('medialist'.$num))) {
66 | $files = explode(',', $row->getValue('medialist'.$num));
67 | foreach ($files as $file) {
68 | $images[] = $file;
69 | }
70 | }
71 | }
72 | }
73 |
74 | /* find images in yforms (be_media, be_medialist, mediafile) */
75 |
76 | if (rex_addon::get('yform')->isAvailable()) {
77 | $yforms = [];
78 |
79 | // get tables and fields where 'be_media' and 'be_medialist' are used
80 | $sql->setQuery(
81 | 'SELECT table_name,name FROM '.rex::getTablePrefix(
82 | ).'yform_field WHERE type_name LIKE "be_media%" OR type_name LIKE "mediafile"'
83 | );
84 | foreach ($sql as $row) {
85 | $yforms[$row->getValue('table_name')][] = $row->getValue('name');
86 | }
87 |
88 | // walk through tables and find images
89 | foreach ($yforms as $table => $fields) {
90 | $sql->setQuery('SELECT '.implode(',', array_values($fields)).' FROM '.$table);
91 | foreach ($sql as $row) {
92 | foreach ($fields as $field) {
93 | $files = $row->getValue($field);
94 | if (strpos($files, ',') > 0) {
95 | // is medialist
96 | foreach (explode(',', $files) as $file) {
97 | $images[] = $file;
98 | }
99 | } else {
100 | // is media
101 | $images[] = $files;
102 | }
103 | }
104 | }
105 | }
106 | }
107 |
108 | /* find images in metainfos (REX_MEDIA_WIDGET, REX_MEDIALIST_WIDGET) */
109 |
110 | if (rex_addon::get('metainfo')->isAvailable()) {
111 | $metainfos = [];
112 |
113 | // get 'REX_MEDIA_WIDGET' and 'REX_MEDIALIST_WIDGET' ids
114 | $sql->setQuery('SELECT id FROM '.rex::getTablePrefix().'metainfo_type WHERE label LIKE "REX_MEDIA%"');
115 | foreach ($sql as $row) {
116 | $metainfos['ids'][] = $row->getValue('id');
117 | }
118 |
119 | // get field names where 'REX_MEDIA_WIDGET' and 'REX_MEDIALIST_WIDGET' are used
120 | $sql->setQuery(
121 | 'SELECT name FROM '.rex::getTablePrefix().'metainfo_field WHERE type_id IN ('.implode(
122 | ',',
123 | $metainfos['ids']
124 | ).')'
125 | );
126 | foreach ($sql as $row) {
127 | $metainfos['names'][] = $row->getValue('name');
128 | }
129 |
130 | // find images in metas (article, clang, media)
131 | if (isset($metainfos['names'])) {
132 | $tablesFrom = [
133 | rex::getTablePrefix().'article',
134 | rex::getTablePrefix().'clang',
135 | rex::getTablePrefix().'media',
136 | ];
137 | foreach ($tablesFrom as $table) {
138 | $sql->setQuery('SELECT * FROM '.$table);
139 | if ($sql->getRows() > 0) {
140 | foreach ($sql as $row) {
141 | foreach ($metainfos['names'] as $field) {
142 | if ($row->hasValue($field)) {
143 | $files = $row->getValue($field);
144 | if (strpos($files, ',') > 0) {
145 | // is medialist
146 | foreach (explode(',', $files) as $file) {
147 | $images[] = $file;
148 | }
149 | } else {
150 | // is media
151 | $images[] = $files;
152 | }
153 | }
154 | }
155 | }
156 | }
157 | }
158 | }
159 | }
160 |
161 | /* prepare and return ------------------------------------------------- */
162 |
163 | // filter images
164 | return self::filterImages($images);
165 | }
166 | return [];
167 | }
168 |
169 | /**
170 | * Filter images: remove duplicate images and non-image items.
171 | *
172 | * @param array $items
173 | * @return array
174 | */
175 | private static function filterImages(array $items): array
176 | {
177 | $filteredImages = [];
178 |
179 | $items = array_unique($items); // remove duplicate values
180 |
181 | foreach ($items as $item) {
182 | $media = rex_media::get($item);
183 | if (!is_null($media)) {
184 | if ($media->isImage()) {
185 | $filteredImages[] = $item;
186 | }
187 | rex_media::clearInstance($item);
188 | }
189 | }
190 |
191 | return $filteredImages;
192 | }
193 |
194 | /**
195 | * Get image IDs
196 | * returns sth like `array(17, 'content')` from `array('image.jpg', 'content')`.
197 | *
198 | * @param array $items
199 | * @return array
200 | */
201 | public static function getImageIds(array $items): array
202 | {
203 | $filteredImages = [];
204 |
205 | foreach ($items as $item) {
206 | $media = rex_media::get($item[0]);
207 | if (!is_null($media)) {
208 | if ($media->isImage()) {
209 | $filteredImages[] = [(int) $media->getId(), $item[1]];
210 | }
211 | rex_media::clearInstance($item);
212 | }
213 | }
214 |
215 | return $filteredImages;
216 | }
217 |
218 | /**
219 | * Get image names
220 | * returns sth like `array('image.jpg', 'portrait')` from `array(23, 'portrait')`.
221 | *
222 | * @param array $items
223 | * @throws rex_sql_exception
224 | * @return array
225 | */
226 | public static function getImageNames(array $items): array
227 | {
228 | $filteredImages = [];
229 |
230 | // filter image ids
231 | $imageIds = array_column($items, 0);
232 | $imageIds = array_filter($imageIds, static function ($v) {
233 | return preg_match('/^\d+$/', $v) && (int) $v > 0; // sanitize
234 | });
235 | $imageIds = array_unique($imageIds);
236 |
237 | // fetch images names for selected ids
238 | $images = [];
239 | $sql = rex_sql::factory();
240 | $sql->setQuery(
241 | 'SELECT id, filename FROM '.rex::getTablePrefix().'media WHERE id IN ('.implode(',', $imageIds).')'
242 | );
243 | foreach ($sql as $row) {
244 | $images[$row->getValue('id')] = $row->getValue('filename');
245 | }
246 |
247 | // loop through items and replace ids with names
248 | foreach ($items as $item) {
249 | $filteredImages[] = [$images[$item[0]], $item[1]];
250 | }
251 |
252 | return $filteredImages;
253 | }
254 |
255 | /**
256 | * Get all images and mediatypes as array including 'count' and 'items'.
257 | *
258 | * @return array
259 | */
260 | private static function getImagesArray(): array
261 | {
262 | $images = self::getImages();
263 | $mediaTypes = self::getMediaTypes();
264 |
265 | // EPs to modify images and mediatypes
266 | $images = rex_extension::registerPoint(new rex_extension_point('CACHE_WARMUP_IMAGES', $images));
267 | $mediaTypes = rex_extension::registerPoint(new rex_extension_point('CACHE_WARMUP_MEDIATYPES', $mediaTypes));
268 |
269 | $items = [];
270 | if (count($images) > 0 && count($mediaTypes) > 0) {
271 | foreach ($images as $image) {
272 | $media = rex_media::get($image);
273 | if (!is_null($media)) {
274 | if ($media->isImage()) {
275 | foreach ($mediaTypes as $type) {
276 | // EP to control cache generation
277 | $generateImage = rex_extension::registerPoint(
278 | new rex_extension_point(
279 | 'CACHE_WARMUP_GENERATE_IMAGE',
280 | true, [$image, $type]
281 | )
282 | );
283 |
284 | if ($generateImage) {
285 | $items[] = [$image, $type];
286 | }
287 | }
288 | }
289 | rex_media::clearInstance($media);
290 | }
291 | }
292 | }
293 |
294 | // EP to modify images with mediatypes
295 | $items = rex_extension::registerPoint(new rex_extension_point('CACHE_WARMUP_IMAGES_WITH_MEDIATYPES', $items));
296 |
297 | return ['count' => count($items), 'items' => $items];
298 | }
299 |
300 | /**
301 | * Get all media types as defined in media manager addon.
302 | *
303 | * @throws rex_sql_exception
304 | *
305 | * @return array
306 | */
307 | private static function getMediaTypes(): array
308 | {
309 | if (rex_addon::get('media_manager')->isAvailable()) {
310 | $mediaTypes = [];
311 |
312 | $sql = rex_sql::factory();
313 | $sql->setQuery('SELECT name FROM '.rex::getTablePrefix().'media_manager_type');
314 |
315 | foreach ($sql as $row) {
316 | $mediaTypes[] = $row->getValue('name');
317 | }
318 |
319 | return $mediaTypes;
320 | }
321 | return [];
322 | }
323 |
324 | /**
325 | * Get all pages being online.
326 | *
327 | * @throws rex_sql_exception
328 | *
329 | * @return array
330 | */
331 | private static function getPages(): array
332 | {
333 | if (rex_addon::get('structure')->isAvailable()) {
334 | $query = 'SELECT a.id, a.clang_id FROM '.rex::getTable('article').' AS a INNER JOIN '.rex::getTable(
335 | 'clang'
336 | ).' AS c ON a.clang_id = c.id WHERE a.status = ? AND c.status = ?';
337 | $params = [1, 1];
338 |
339 | $sql = rex_sql::factory();
340 | return $sql->getArray($query, $params, PDO::FETCH_NUM);
341 | }
342 | return [];
343 | }
344 |
345 | /**
346 | * Get all pages and languages as array including 'count' and 'items'.
347 | *
348 | * @return array
349 | */
350 | private static function getPagesArray(): array
351 | {
352 | $pages = self::getPages();
353 |
354 | $items = [];
355 | if (count($pages) > 0) {
356 | foreach ($pages as $page) {
357 | // EP to control cache generation
358 | $generatePage = rex_extension::registerPoint(
359 | new rex_extension_point(
360 | 'CACHE_WARMUP_GENERATE_PAGE',
361 | true, $page
362 | )
363 | );
364 |
365 | if ($generatePage) {
366 | $items[] = [(int) $page[0], (int) $page[1]];
367 | }
368 | }
369 | }
370 |
371 | // EP to modify pages with clangs
372 | $items = rex_extension::registerPoint(new rex_extension_point('CACHE_WARMUP_PAGES_WITH_CLANGS', $items));
373 |
374 | return ['count' => count($items), 'items' => $items];
375 | }
376 |
377 | /**
378 | * Split an array into chunks.
379 | *
380 | * @param array $items
381 | * @param int $chunkSize
382 | *
383 | * @return array
384 | */
385 | private static function chunk(array $items, int $chunkSize): array
386 | {
387 | return array_chunk($items, $chunkSize);
388 | }
389 | }
390 |
--------------------------------------------------------------------------------
/assets/js/cache-warmup.js:
--------------------------------------------------------------------------------
1 | (function ($) {
2 | $(document).ready(function () {
3 |
4 | /* set debug mode */
5 |
6 | var DEBUGMODE = false;
7 | if ($('.rex-is-debugmode').length) {
8 | DEBUGMODE = true;
9 | }
10 |
11 |
12 | /* debug log helper */
13 |
14 | var debug = (function () {
15 | return {
16 | log: function () {
17 | var args = Array.prototype.slice.call(arguments);
18 | (DEBUGMODE) ? console.log.apply(console, args) : false;
19 | },
20 | info: function () {
21 | var args = Array.prototype.slice.call(arguments);
22 | (DEBUGMODE) ? console.info.apply(console, args) : false;
23 | },
24 | error: function () {
25 | var args = Array.prototype.slice.call(arguments);
26 | (DEBUGMODE) ? console.error.apply(console, args) : false;
27 | }
28 | }
29 | })();
30 |
31 |
32 | /* warmup popup */
33 |
34 | var popup = null;
35 | var popupButton = $('.cache-warmup__button-start');
36 | var warmupButton = $('#rex-page-cache-warmup-warmup');
37 |
38 | popupButton.on('click', function (e) {
39 | e.preventDefault();
40 |
41 | var url = $(this).attr('href');
42 | var title = 'Cache Warmup';
43 | var parameters = 'left=' + (screen.width - 650) + ', top=50, height=471, width=600, menubar=no, location=no, resizable=no, status=no, scrollbars=yes';
44 |
45 | if (popup === null || popup.closed) {
46 | popup = window.open(url, title, parameters);
47 | popup.resizeTo(600, 470);
48 | debug.log('open new popup: ', [title, url, parameters]);
49 | }
50 | else {
51 | popup.focus();
52 | debug.log('focus popup: ', title);
53 | }
54 | });
55 |
56 | warmupButton.on('click', '.cache-warmup__button--success, .cache-warmup__button--cancel', function (e) {
57 | e.preventDefault();
58 | debug.log('close popup.');
59 | window.close();
60 | });
61 |
62 | warmupButton.on('click', '.cache-warmup__button--again', function (e) {
63 | e.preventDefault();
64 | document.location.reload(true);
65 | });
66 |
67 |
68 | /**
69 | * Content
70 | * precompiles handlebar templates and injects content to page
71 | *
72 | * @param config
73 | * @constructor
74 | */
75 | var Content = function (config) {
76 | debug.info('new Content');
77 |
78 | // register templates
79 | if (config.templates && config.templates.length) {
80 | this._templates = {};
81 | this._registerTemplates(config.templates);
82 | }
83 | };
84 |
85 | Content.prototype = {
86 |
87 | _registerTemplates: function (templateSlugs) {
88 | if (templateSlugs.length) {
89 | templateSlugs.forEach(function (slug) {
90 | this._templates[slug] = Handlebars.compile($('#cache_warmup_tpl_' + slug).html());
91 | }, this);
92 | debug.log('content: registered ' + templateSlugs.length + ' templates: ' + templateSlugs);
93 | return this;
94 | }
95 | },
96 |
97 | _selectTarget: function (target) {
98 | var _target = $('.cache-warmup__target__' + target);
99 | return (_target.length) ? _target : undefined;
100 | },
101 |
102 | injectTemplate: function (target, template) {
103 | if (this._templates[template]) {
104 | this._selectTarget(target).html(this._templates[template]());
105 | debug.log('content: inject ' + template + ' to ' + target);
106 | }
107 | },
108 |
109 | injectContent: function (target, content) {
110 | var _target = this._selectTarget(target);
111 | if (_target) {
112 | _target.html(content);
113 | debug.log('content: inject content to ' + target);
114 | }
115 | },
116 |
117 | removeElement: function (target) {
118 | this._selectTarget(target).remove();
119 | },
120 |
121 | setFromValue: function (value) {
122 | $('.cache-warmup__target__progress-from').html(value);
123 | },
124 |
125 | setToValue: function (value) {
126 | $('.cache-warmup__target__progress-to').html(value);
127 | }
128 | };
129 |
130 |
131 | /**
132 | * Stopwatch
133 | * binds timer (external package) to given element selector
134 | *
135 | * @param selector
136 | * @constructor
137 | */
138 | var Stopwatch = function (selector) {
139 | debug.info('new Stopwatch at "' + selector + '"');
140 |
141 | this._selector = selector;
142 | this._el = null;
143 | };
144 |
145 | Stopwatch.prototype = {
146 |
147 | start: function (value) {
148 | debug.log('stopwatch: started at ' + value);
149 | this._el = $(this._selector);
150 | this._el.timer({
151 | 'seconds': value,
152 | 'format': '%H:%M:%S'
153 | });
154 | },
155 |
156 | pause: function () {
157 | this._el.timer('pause');
158 | debug.log('stopwatch: stopped at ' + this._el.data('seconds'));
159 | },
160 |
161 | getTime: function () {
162 | return this._el.data('seconds');
163 | },
164 |
165 | reset: function () {
166 | this.start();
167 | debug.info('stopwatch: reset.');
168 | }
169 | };
170 |
171 |
172 | /**
173 | * Progressbar
174 | * controls the progress bar (bootstrap), sets to given value
175 | *
176 | * @param selector
177 | * @param value
178 | * @constructor
179 | */
180 | var Progressbar = function (selector, value) {
181 | debug.info('new Progressbar at "' + selector + '" starting at ' + value);
182 |
183 | this._selector = selector;
184 | this._el = $(selector);
185 | this._value = value;
186 | this._min = 0;
187 | this._max = 100;
188 | };
189 |
190 | Progressbar.prototype = {
191 |
192 | setProgress: function (value) {
193 | if (value > this._max) {
194 | this._value = this._max;
195 | }
196 | else if (value < this._min) {
197 | this._value = this._min;
198 | }
199 | else {
200 | this._value = value;
201 | }
202 | this._update();
203 | debug.log('progressbar: set to ' + this._value);
204 | return this;
205 | },
206 |
207 | getProgress: function () {
208 | return this._value;
209 | },
210 |
211 | reset: function () {
212 | this._value = this._min;
213 | this._update();
214 | debug.info('progressbar: reset.');
215 | return this;
216 | },
217 |
218 | _update: function () {
219 | this._el = $(this._selector);
220 | this._el.find('.progress-bar').css('width', this._value + '%');
221 | return this;
222 | }
223 | };
224 |
225 |
226 | /**
227 | * Calculator
228 | * stores number of chunks and calculates progress
229 | * does not care about items but chunks only
230 | *
231 | * @param config
232 | * @constructor
233 | */
234 | var Calculator = function (config) {
235 | this._initialConfig = config;
236 |
237 | this._init();
238 | debug.info('new Calculator :', JSON.stringify(this.config));
239 | };
240 |
241 | Calculator.prototype = {
242 |
243 | _init: function () {
244 | this.config = {};
245 | for (var item in this._initialConfig) {
246 | if (this._initialConfig.hasOwnProperty(item)) {
247 | this.config[item] = this._initialConfig[item];
248 | this.config[item].current = 0;
249 | }
250 | }
251 | },
252 |
253 | reset: function () {
254 | this._init();
255 | debug.info('calculator: reset.');
256 | },
257 |
258 | registerNextChunk: function (type) {
259 | this.config[type].current += 1;
260 | debug.log('calculator: set ' + type + ' to ' + this.config[type].current + '/' + this.config[type].total + ', overall progress at ' + this.getProgress() + '% now');
261 | return this;
262 | },
263 |
264 | getCurrent: function (type) {
265 | return this.config[type].current;
266 | },
267 |
268 | getTotal: function (type) {
269 | return this.config[type].total;
270 | },
271 |
272 | getProgress: function () {
273 | return this._calculateProgress();
274 | },
275 |
276 | _calculateProgress: function () {
277 | var finished = 0;
278 | var total = 0;
279 | for (var type in this.config) {
280 | if (this.config.hasOwnProperty(type)) {
281 | finished += this.getCurrent(type);
282 | total += this.getTotal(type);
283 | }
284 | }
285 | return Math.round(finished / total * 100);
286 | }
287 | };
288 |
289 |
290 | /**
291 | * Config
292 | * prepares config JSON, returns URIs for generator requests
293 | *
294 | * @param itemsJSON
295 | * @param generatorUrl
296 | * @constructor
297 | */
298 | var Config = function (itemsJSON, generatorUrl, token) {
299 | this._items = itemsJSON;
300 | this._generatorUrl = generatorUrl;
301 | this._token = token;
302 |
303 | if (!$.isEmptyObject(this._items) && this._generatorUrl.length) {
304 | debug.info("new Config for " + this._getDebugInfo() + "generator at " + generatorUrl + " with token " + token);
305 | }
306 | else {
307 | debug.error('new Config: no content.');
308 | }
309 | };
310 |
311 | Config.prototype = {
312 |
313 | _getDebugInfo: function () {
314 | var info = '';
315 | var types = this.getItemTypes();
316 | if (types.length) {
317 | types.forEach(function (entry) {
318 | info += this.getNumOfItems(entry) + ' ' + entry + ' (' + this.getNumOfChunks(entry) + ' chunks), ';
319 | }, this);
320 | return info;
321 | }
322 | },
323 |
324 | hasItems: function () {
325 | return Object.keys(this._items).some(function(type) {
326 | return this._items[type].count > 0;
327 | }, this);
328 | },
329 |
330 | getNumOfItems: function (type) {
331 | return this._items[type] ? this._items[type].count : 0;
332 | },
333 |
334 | getNumOfChunks: function (type) {
335 | return this._items[type].items ? this._items[type].items.length : 0;
336 | },
337 |
338 | getItemTypes: function () {
339 | return (Object.keys(this._items));
340 | },
341 |
342 | generateTokenParameter: function () {
343 | return this._token ? "&_csrf_token=" + this._token : null;
344 | },
345 |
346 | getUrlsForType: function (type) {
347 | var urls = [];
348 | var chunk = [];
349 | if (this._items[type] && this._items[type].items.length) {
350 | for (var i = 0, imax = this._items[type].items.length; i < imax; i++) {
351 | chunk = [];
352 | for (var j = 0, jmax = this._items[type].items[i].length; j < jmax; j++) {
353 | chunk.push(this._items[type].items[i][j].join('.'));
354 | }
355 | urls.push({
356 | 'absolute': this._generatorUrl + '&' + type + '=' + chunk.join() + this.generateTokenParameter(),
357 | 'slug': type + '=' + chunk.join(),
358 | 'itemsNum': jmax
359 | });
360 | }
361 | }
362 | return urls;
363 | }
364 | };
365 |
366 |
367 | /**
368 | * Cache
369 | * sends ajax request to generator file
370 | *
371 | * @param cacheWarmup
372 | * @constructor
373 | */
374 | var Cache = function (cacheWarmup) {
375 | this.cacheWarmup = cacheWarmup;
376 | };
377 |
378 | Cache.prototype = {
379 |
380 | generate: function (type, callback) {
381 | var timerStart;
382 | var timerEnd;
383 | var executionTimes = [];
384 | var that = this;
385 | var urls = this.cacheWarmup.config.getUrlsForType(type);
386 | var cachedItemsCount = 0;
387 |
388 | if (urls.length) {
389 |
390 | // loop through urls and send serial requests (not parallel!)
391 | urls.reduce(function (p, url, index) {
392 | return p.then(function () {
393 | timerStart = new Date().getTime();
394 | // send request
395 | return $.ajax({
396 | url: url.absolute,
397 | cache: false,
398 | beforeSend: function () {
399 | // update components
400 | // why not after request? because from UX view it feels better beforehand.
401 | debug.log('---');
402 | cachedItemsCount += url.itemsNum;
403 | that.cacheWarmup.content.setFromValue(cachedItemsCount);
404 | that.cacheWarmup.calculator.registerNextChunk(type);
405 | that.cacheWarmup.progressbar.setProgress(that.cacheWarmup.calculator.getProgress());
406 | }
407 | })
408 | .done(function (data) {
409 | // special: error on success (http status 200)
410 | // media manager returns 200 even if an image cannot be generated (too big, RAM exceeded)
411 | // we assume an error if response starts with rex-page-header
412 | // otherwise page will return blank if stuff works out as expected
413 | if (data.substr(0, 30) === '' + this.content._templates['error_link']() + '';
576 |
577 | // inject values
578 | this.content.injectTemplate('title', 'title_error');
579 | this.content.injectTemplate('icon', 'icon_error');
580 | this.content.injectContent('text', errorDetails);
581 |
582 | // remove progressbar
583 | this.content.removeElement('progressbar');
584 | },
585 |
586 | isNothing: function () {
587 |
588 | // inject content and footer
589 | this.content.injectTemplate('content', 'content_info');
590 | this.content.injectTemplate('footer', 'button_again');
591 |
592 | // inject values
593 | this.content.injectTemplate('title', 'title_nothing');
594 | this.content.injectTemplate('icon', 'icon_nothing');
595 | this.content.injectTemplate('text', 'text_nothing');
596 |
597 | // remove progressbar
598 | this.content.removeElement('progressbar');
599 | }
600 | };
601 |
602 |
603 | /* Cache warmup */
604 |
605 | if (typeof cacheWarmupItems !== 'undefined') {
606 |
607 | new CacheWarmup({
608 | 'itemsJSON': cacheWarmupItems,
609 | 'token': cacheWarmupToken || false,
610 | 'generatorUrl': window.location.origin + window.location.pathname + '?page=cache_warmup/generator',
611 | 'templates': [
612 | 'content_task', 'content_info',
613 | 'stopwatch', 'progressbar',
614 | 'title_pages', 'title_images', 'title_finished', 'title_error', 'title_nothing',
615 | 'progress_pages', 'progress_images',
616 | 'icon_finished', 'icon_error', 'icon_nothing',
617 | 'text_finished', 'text_error', 'text_nothing',
618 | 'error_link',
619 | 'button_success', 'button_again', 'button_cancel'
620 | ],
621 | 'targets': [
622 | 'title', 'content', 'progressbar', 'footer', 'task', 'elapsed', 'icon', 'text'
623 | ],
624 | 'components': {
625 | 'stopwatch': '#cache_warmup_time',
626 | 'progressbar': '.cache-warmup__progressbar'
627 | }
628 | });
629 | }
630 |
631 | });
632 | })(jQuery);
--------------------------------------------------------------------------------
/assets/js/handlebars.min.js:
--------------------------------------------------------------------------------
1 | /**!
2 |
3 | @license
4 | handlebars v4.7.7
5 |
6 | Copyright (C) 2011-2019 by Yehuda Katz
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in
16 | all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | THE SOFTWARE.
25 |
26 | */
27 | !function(a,b){"object"==typeof exports&&"object"==typeof module?module.exports=b():"function"==typeof define&&define.amd?define([],b):"object"==typeof exports?exports.Handlebars=b():a.Handlebars=b()}(this,function(){return function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={exports:{},id:d,loaded:!1};return a[d].call(e.exports,e,e.exports,b),e.loaded=!0,e.exports}var c={};return b.m=a,b.c=c,b.p="",b(0)}([function(a,b,c){"use strict";function d(){var a=r();return a.compile=function(b,c){return k.compile(b,c,a)},a.precompile=function(b,c){return k.precompile(b,c,a)},a.AST=i["default"],a.Compiler=k.Compiler,a.JavaScriptCompiler=m["default"],a.Parser=j.parser,a.parse=j.parse,a.parseWithoutProcessing=j.parseWithoutProcessing,a}var e=c(1)["default"];b.__esModule=!0;var f=c(2),g=e(f),h=c(45),i=e(h),j=c(46),k=c(51),l=c(52),m=e(l),n=c(49),o=e(n),p=c(44),q=e(p),r=g["default"].create,s=d();s.create=d,q["default"](s),s.Visitor=o["default"],s["default"]=s,b["default"]=s,a.exports=b["default"]},function(a,b){"use strict";b["default"]=function(a){return a&&a.__esModule?a:{"default":a}},b.__esModule=!0},function(a,b,c){"use strict";function d(){var a=new h.HandlebarsEnvironment;return n.extend(a,h),a.SafeString=j["default"],a.Exception=l["default"],a.Utils=n,a.escapeExpression=n.escapeExpression,a.VM=p,a.template=function(b){return p.template(b,a)},a}var e=c(3)["default"],f=c(1)["default"];b.__esModule=!0;var g=c(4),h=e(g),i=c(37),j=f(i),k=c(6),l=f(k),m=c(5),n=e(m),o=c(38),p=e(o),q=c(44),r=f(q),s=d();s.create=d,r["default"](s),s["default"]=s,b["default"]=s,a.exports=b["default"]},function(a,b){"use strict";b["default"]=function(a){if(a&&a.__esModule)return a;var b={};if(null!=a)for(var c in a)Object.prototype.hasOwnProperty.call(a,c)&&(b[c]=a[c]);return b["default"]=a,b},b.__esModule=!0},function(a,b,c){"use strict";function d(a,b,c){this.helpers=a||{},this.partials=b||{},this.decorators=c||{},i.registerDefaultHelpers(this),j.registerDefaultDecorators(this)}var e=c(1)["default"];b.__esModule=!0,b.HandlebarsEnvironment=d;var f=c(5),g=c(6),h=e(g),i=c(10),j=c(30),k=c(32),l=e(k),m=c(33),n="4.7.7";b.VERSION=n;var o=8;b.COMPILER_REVISION=o;var p=7;b.LAST_COMPATIBLE_COMPILER_REVISION=p;var q={1:"<= 1.0.rc.2",2:"== 1.0.0-rc.3",3:"== 1.0.0-rc.4",4:"== 1.x.x",5:"== 2.0.0-alpha.x",6:">= 2.0.0-beta.1",7:">= 4.0.0 <4.3.0",8:">= 4.3.0"};b.REVISION_CHANGES=q;var r="[object Object]";d.prototype={constructor:d,logger:l["default"],log:l["default"].log,registerHelper:function(a,b){if(f.toString.call(a)===r){if(b)throw new h["default"]("Arg not supported with multiple helpers");f.extend(this.helpers,a)}else this.helpers[a]=b},unregisterHelper:function(a){delete this.helpers[a]},registerPartial:function(a,b){if(f.toString.call(a)===r)f.extend(this.partials,a);else{if("undefined"==typeof b)throw new h["default"]('Attempting to register a partial called "'+a+'" as undefined');this.partials[a]=b}},unregisterPartial:function(a){delete this.partials[a]},registerDecorator:function(a,b){if(f.toString.call(a)===r){if(b)throw new h["default"]("Arg not supported with multiple decorators");f.extend(this.decorators,a)}else this.decorators[a]=b},unregisterDecorator:function(a){delete this.decorators[a]},resetLoggedPropertyAccesses:function(){m.resetLoggedProperties()}};var s=l["default"].log;b.log=s,b.createFrame=f.createFrame,b.logger=l["default"]},function(a,b){"use strict";function c(a){return k[a]}function d(a){for(var b=1;b":">",'"':""","'":"'","`":"`","=":"="},l=/[&<>"'`=]/g,m=/[&<>"'`=]/,n=Object.prototype.toString;b.toString=n;var o=function(a){return"function"==typeof a};o(/x/)&&(b.isFunction=o=function(a){return"function"==typeof a&&"[object Function]"===n.call(a)}),b.isFunction=o;var p=Array.isArray||function(a){return!(!a||"object"!=typeof a)&&"[object Array]"===n.call(a)};b.isArray=p},function(a,b,c){"use strict";function d(a,b){var c=b&&b.loc,g=void 0,h=void 0,i=void 0,j=void 0;c&&(g=c.start.line,h=c.end.line,i=c.start.column,j=c.end.column,a+=" - "+g+":"+i);for(var k=Error.prototype.constructor.call(this,a),l=0;l0?(c.ids&&(c.ids=[c.name]),a.helpers.each(b,c)):e(this);if(c.data&&c.ids){var g=d.createFrame(c.data);g.contextPath=d.appendContextPath(c.data.contextPath,c.name),c={data:g}}return f(b,c)})},a.exports=b["default"]},function(a,b,c){(function(d){"use strict";var e=c(13)["default"],f=c(1)["default"];b.__esModule=!0;var g=c(5),h=c(6),i=f(h);b["default"]=function(a){a.registerHelper("each",function(a,b){function c(b,c,d){l&&(l.key=b,l.index=c,l.first=0===c,l.last=!!d,m&&(l.contextPath=m+b)),k+=f(a[b],{data:l,blockParams:g.blockParams([a[b],b],[m+b,null])})}if(!b)throw new i["default"]("Must pass iterator to #each");var f=b.fn,h=b.inverse,j=0,k="",l=void 0,m=void 0;if(b.data&&b.ids&&(m=g.appendContextPath(b.data.contextPath,b.ids[0])+"."),g.isFunction(a)&&(a=a.call(this)),b.data&&(l=g.createFrame(b.data)),a&&"object"==typeof a)if(g.isArray(a))for(var n=a.length;j=0?b:parseInt(a,10)}return a},log:function(a){if(a=e.lookupLevel(a),"undefined"!=typeof console&&e.lookupLevel(e.level)<=a){var b=e.methodMap[a];console[b]||(b="log");for(var c=arguments.length,d=Array(c>1?c-1:0),f=1;f=v.LAST_COMPATIBLE_COMPILER_REVISION&&b<=v.COMPILER_REVISION)){if(b2&&v.push("'"+this.terminals_[s]+"'");x=this.lexer.showPosition?"Parse error on line "+(i+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+v.join(", ")+", got '"+(this.terminals_[n]||n)+"'":"Parse error on line "+(i+1)+": Unexpected "+(1==n?"end of input":"'"+(this.terminals_[n]||n)+"'"),this.parseError(x,{text:this.lexer.match,token:this.terminals_[n]||n,line:this.lexer.yylineno,loc:l,expected:v})}}if(q[0]instanceof Array&&q.length>1)throw new Error("Parse Error: multiple actions possible at state: "+p+", token: "+n);switch(q[0]){case 1:d.push(n),e.push(this.lexer.yytext),f.push(this.lexer.yylloc),d.push(q[1]),n=null,o?(n=o,o=null):(j=this.lexer.yyleng,h=this.lexer.yytext,i=this.lexer.yylineno,l=this.lexer.yylloc,k>0&&k--);break;case 2:if(t=this.productions_[q[1]][1],w.$=e[e.length-t],w._$={first_line:f[f.length-(t||1)].first_line,last_line:f[f.length-1].last_line,first_column:f[f.length-(t||1)].first_column,last_column:f[f.length-1].last_column},m&&(w._$.range=[f[f.length-(t||1)].range[0],f[f.length-1].range[1]]),r=this.performAction.call(w,h,j,i,this.yy,q[1],e,f),"undefined"!=typeof r)return r;t&&(d=d.slice(0,-1*t*2),e=e.slice(0,-1*t),f=f.slice(0,-1*t)),d.push(this.productions_[q[1]][0]),e.push(w.$),f.push(w._$),u=g[d[d.length-2]][d[d.length-1]],d.push(u);break;case 3:return!0}}return!0}},c=function(){var a={EOF:1,parseError:function(a,b){if(!this.yy.parser)throw new Error(a);this.yy.parser.parseError(a,b)},setInput:function(a){return this._input=a,this._more=this._less=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var a=this._input[0];this.yytext+=a,this.yyleng++,this.offset++,this.match+=a,this.matched+=a;var b=a.match(/(?:\r\n?|\n).*/g);return b?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),a},unput:function(a){var b=a.length,c=a.split(/(?:\r\n?|\n)/g);this._input=a+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-b-1),this.offset-=b;var d=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),c.length-1&&(this.yylineno-=c.length-1);var e=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:c?(c.length===d.length?this.yylloc.first_column:0)+d[d.length-c.length].length-c[0].length:this.yylloc.first_column-b},this.options.ranges&&(this.yylloc.range=[e[0],e[0]+this.yyleng-b]),this},more:function(){return this._more=!0,this},less:function(a){this.unput(this.match.slice(a))},pastInput:function(){var a=this.matched.substr(0,this.matched.length-this.match.length);return(a.length>20?"...":"")+a.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var a=this.match;return a.length<20&&(a+=this._input.substr(0,20-a.length)),(a.substr(0,20)+(a.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var a=this.pastInput(),b=new Array(a.length+1).join("-");return a+this.upcomingInput()+"\n"+b+"^"},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var a,b,c,d,e;this._more||(this.yytext="",this.match="");for(var f=this._currentRules(),g=0;gb[0].length)||(b=c,d=g,this.options.flex));g++);return b?(e=b[0].match(/(?:\r\n?|\n).*/g),e&&(this.yylineno+=e.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:e?e[e.length-1].length-e[e.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+b[0].length},this.yytext+=b[0],this.match+=b[0],this.matches=b,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._input=this._input.slice(b[0].length),this.matched+=b[0],a=this.performAction.call(this,this.yy,this,f[d],this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),a?a:void 0):""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var a=this.next();return"undefined"!=typeof a?a:this.lex()},begin:function(a){this.conditionStack.push(a)},popState:function(){return this.conditionStack.pop()},_currentRules:function(){return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules},topState:function(){return this.conditionStack[this.conditionStack.length-2]},pushState:function(a){this.begin(a)}};return a.options={},a.performAction=function(a,b,c,d){function e(a,c){return b.yytext=b.yytext.substring(a,b.yyleng-c+a)}switch(c){case 0:if("\\\\"===b.yytext.slice(-2)?(e(0,1),this.begin("mu")):"\\"===b.yytext.slice(-1)?(e(0,1),this.begin("emu")):this.begin("mu"),b.yytext)return 15;break;case 1:return 15;case 2:return this.popState(),15;case 3:return this.begin("raw"),15;case 4:return this.popState(),"raw"===this.conditionStack[this.conditionStack.length-1]?15:(e(5,9),"END_RAW_BLOCK");case 5:return 15;case 6:return this.popState(),14;case 7:return 65;case 8:return 68;case 9:return 19;case 10:return this.popState(),this.begin("raw"),23;case 11:return 55;case 12:return 60;case 13:return 29;case 14:return 47;case 15:return this.popState(),44;case 16:return this.popState(),44;case 17:return 34;case 18:return 39;case 19:return 51;case 20:return 48;case 21:this.unput(b.yytext),this.popState(),this.begin("com");break;case 22:return this.popState(),14;case 23:return 48;case 24:return 73;case 25:return 72;case 26:return 72;case 27:return 87;case 28:break;case 29:return this.popState(),54;case 30:return this.popState(),33;case 31:return b.yytext=e(1,2).replace(/\\"/g,'"'),80;case 32:return b.yytext=e(1,2).replace(/\\'/g,"'"),80;case 33:return 85;case 34:return 82;case 35:return 82;case 36:return 83;case 37:return 84;case 38:return 81;case 39:return 75;case 40:return 77;case 41:return 72;case 42:return b.yytext=b.yytext.replace(/\\([\\\]])/g,"$1"),72;case 43:return"INVALID";case 44:return 5}},a.rules=[/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/,/^(?:\{\{\{\{(?=[^\/]))/,/^(?:\{\{\{\{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])\}\}\}\})/,/^(?:[^\x00]+?(?=(\{\{\{\{)))/,/^(?:[\s\S]*?--(~)?\}\})/,/^(?:\()/,/^(?:\))/,/^(?:\{\{\{\{)/,/^(?:\}\}\}\})/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#>)/,/^(?:\{\{(~)?#\*?)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^\s*(~)?\}\})/,/^(?:\{\{(~)?\s*else\s*(~)?\}\})/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{(~)?!--)/,/^(?:\{\{(~)?![\s\S]*?\}\})/,/^(?:\{\{(~)?\*?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.)|])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s)])))/,/^(?:false(?=([~}\s)])))/,/^(?:undefined(?=([~}\s)])))/,/^(?:null(?=([~}\s)])))/,/^(?:-?[0-9]+(?:\.[0-9]+)?(?=([~}\s)])))/,/^(?:as\s+\|)/,/^(?:\|)/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)|]))))/,/^(?:\[(\\\]|[^\]])*\])/,/^(?:.)/,/^(?:$)/],a.conditions={mu:{rules:[7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44],inclusive:!1},emu:{rules:[2],inclusive:!1},com:{rules:[6],inclusive:!1},raw:{rules:[3,4,5],inclusive:!1},INITIAL:{rules:[0,1,44],inclusive:!0}},a}();return b.lexer=c,a.prototype=b,b.Parser=a,new a}();b["default"]=c,a.exports=b["default"]},function(a,b,c){"use strict";function d(){var a=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];this.options=a}function e(a,b,c){void 0===b&&(b=a.length);var d=a[b-1],e=a[b-2];return d?"ContentStatement"===d.type?(e||!c?/\r?\n\s*?$/:/(^|\r?\n)\s*?$/).test(d.original):void 0:c}function f(a,b,c){void 0===b&&(b=-1);var d=a[b+1],e=a[b+2];return d?"ContentStatement"===d.type?(e||!c?/^\s*?\r?\n/:/^\s*?(\r?\n|$)/).test(d.original):void 0:c}function g(a,b,c){var d=a[null==b?0:b+1];if(d&&"ContentStatement"===d.type&&(c||!d.rightStripped)){var e=d.value;d.value=d.value.replace(c?/^\s+/:/^[ \t]*\r?\n?/,""),d.rightStripped=d.value!==e}}function h(a,b,c){var d=a[null==b?a.length-1:b-1];if(d&&"ContentStatement"===d.type&&(c||!d.leftStripped)){var e=d.value;return d.value=d.value.replace(c?/\s+$/:/[ \t]+$/,""),d.leftStripped=d.value!==e,d.leftStripped}}var i=c(1)["default"];b.__esModule=!0;var j=c(49),k=i(j);d.prototype=new k["default"],d.prototype.Program=function(a){var b=!this.options.ignoreStandalone,c=!this.isRootSeen;this.isRootSeen=!0;for(var d=a.body,i=0,j=d.length;i0)throw new q["default"]("Invalid path: "+d,{loc:c});".."===i&&f++}}return{type:"PathExpression",data:a,depth:f,parts:e,original:d,loc:c}}function j(a,b,c,d,e,f){var g=d.charAt(3)||d.charAt(2),h="{"!==g&&"&"!==g,i=/\*/.test(d);return{type:i?"Decorator":"MustacheStatement",path:a,params:b,hash:c,escaped:h,strip:e,loc:this.locInfo(f)}}function k(a,b,c,e){d(a,c),e=this.locInfo(e);var f={type:"Program",body:b,strip:{},loc:e};return{type:"BlockStatement",path:a.path,params:a.params,hash:a.hash,program:f,openStrip:{},inverseStrip:{},closeStrip:{},loc:e}}function l(a,b,c,e,f,g){e&&e.path&&d(a,e);var h=/\*/.test(a.open);b.blockParams=a.blockParams;var i=void 0,j=void 0;if(c){if(h)throw new q["default"]("Unexpected inverse block on decorator",c);c.chain&&(c.program.body[0].closeStrip=e.strip),j=c.strip,i=c.program}return f&&(f=i,i=b,b=f),{type:h?"DecoratorBlock":"BlockStatement",path:a.path,params:a.params,hash:a.hash,program:b,inverse:i,openStrip:a.strip,inverseStrip:j,closeStrip:e&&e.strip,loc:this.locInfo(g)}}function m(a,b){if(!b&&a.length){var c=a[0].loc,d=a[a.length-1].loc;c&&d&&(b={source:c.source,start:{line:c.start.line,column:c.start.column},end:{line:d.end.line,column:d.end.column}})}return{type:"Program",body:a,strip:{},loc:b}}function n(a,b,c,e){return d(a,c),{type:"PartialBlockStatement",name:a.path,params:a.params,hash:a.hash,program:b,openStrip:a.strip,closeStrip:c&&c.strip,loc:this.locInfo(e)}}var o=c(1)["default"];b.__esModule=!0,b.SourceLocation=e,b.id=f,b.stripFlags=g,b.stripComment=h,b.preparePath=i,b.prepareMustache=j,b.prepareRawBlock=k,b.prepareBlock=l,b.prepareProgram=m,b.preparePartialBlock=n;var p=c(6),q=o(p)},function(a,b,c){"use strict";function d(){}function e(a,b,c){if(null==a||"string"!=typeof a&&"Program"!==a.type)throw new l["default"]("You must pass a string or Handlebars AST to Handlebars.precompile. You passed "+a);b=b||{},"data"in b||(b.data=!0),b.compat&&(b.useDepths=!0);var d=c.parse(a,b),e=(new c.Compiler).compile(d,b);return(new c.JavaScriptCompiler).compile(e,b)}function f(a,b,c){function d(){var d=c.parse(a,b),e=(new c.Compiler).compile(d,b),f=(new c.JavaScriptCompiler).compile(e,b,void 0,!0);return c.template(f)}function e(a,b){return f||(f=d()),f.call(this,a,b)}if(void 0===b&&(b={}),null==a||"string"!=typeof a&&"Program"!==a.type)throw new l["default"]("You must pass a string or Handlebars AST to Handlebars.compile. You passed "+a);b=m.extend({},b),"data"in b||(b.data=!0),b.compat&&(b.useDepths=!0);var f=void 0;return e._setup=function(a){return f||(f=d()),f._setup(a)},e._child=function(a,b,c,e){return f||(f=d()),f._child(a,b,c,e)},e}function g(a,b){if(a===b)return!0;if(m.isArray(a)&&m.isArray(b)&&a.length===b.length){for(var c=0;c1)throw new l["default"]("Unsupported number of partial arguments: "+c.length,a);c.length||(this.options.explicitPartialContext?this.opcode("pushLiteral","undefined"):c.push({type:"PathExpression",parts:[],depth:0}));var d=a.name.original,e="SubExpression"===a.name.type;e&&this.accept(a.name),this.setupFullMustacheParams(a,b,void 0,!0);var f=a.indent||"";this.options.preventIndent&&f&&(this.opcode("appendContent",f),f=""),this.opcode("invokePartial",e,d,f),this.opcode("append")},PartialBlockStatement:function(a){this.PartialStatement(a)},MustacheStatement:function(a){this.SubExpression(a),a.escaped&&!this.options.noEscape?this.opcode("appendEscaped"):this.opcode("append")},Decorator:function(a){this.DecoratorBlock(a)},ContentStatement:function(a){a.value&&this.opcode("appendContent",a.value)},CommentStatement:function(){},SubExpression:function(a){h(a);var b=this.classifySexpr(a);"simple"===b?this.simpleSexpr(a):"helper"===b?this.helperSexpr(a):this.ambiguousSexpr(a)},ambiguousSexpr:function(a,b,c){var d=a.path,e=d.parts[0],f=null!=b||null!=c;this.opcode("getContext",d.depth),this.opcode("pushProgram",b),this.opcode("pushProgram",c),d.strict=!0,this.accept(d),this.opcode("invokeAmbiguous",e,f)},simpleSexpr:function(a){var b=a.path;b.strict=!0,this.accept(b),this.opcode("resolvePossibleLambda")},helperSexpr:function(a,b,c){var d=this.setupFullMustacheParams(a,b,c),e=a.path,f=e.parts[0];if(this.options.knownHelpers[f])this.opcode("invokeKnownHelper",d.length,f);else{if(this.options.knownHelpersOnly)throw new l["default"]("You specified knownHelpersOnly, but used the unknown helper "+f,a);e.strict=!0,e.falsy=!0,this.accept(e),this.opcode("invokeHelper",d.length,e.original,o["default"].helpers.simpleId(e))}},PathExpression:function(a){this.addDepth(a.depth),this.opcode("getContext",a.depth);var b=a.parts[0],c=o["default"].helpers.scopedId(a),d=!a.depth&&!c&&this.blockParamIndex(b);d?this.opcode("lookupBlockParam",d,a.parts):b?a.data?(this.options.data=!0,this.opcode("lookupData",a.depth,a.parts,a.strict)):this.opcode("lookupOnContext",a.parts,a.falsy,a.strict,c):this.opcode("pushContext")},StringLiteral:function(a){this.opcode("pushString",a.value)},NumberLiteral:function(a){this.opcode("pushLiteral",a.value)},BooleanLiteral:function(a){this.opcode("pushLiteral",a.value)},UndefinedLiteral:function(){this.opcode("pushLiteral","undefined")},NullLiteral:function(){this.opcode("pushLiteral","null")},Hash:function(a){var b=a.pairs,c=0,d=b.length;for(this.opcode("pushHash");c=0)return[b,e]}}}},function(a,b,c){"use strict";function d(a){this.value=a}function e(){}function f(a,b,c,d){var e=b.popStack(),f=0,g=c.length;for(a&&g--;f0&&(c+=", "+d.join(", "));var e=0;g(this.aliases).forEach(function(a){var d=b.aliases[a];d.children&&d.referenceCount>1&&(c+=", alias"+ ++e+"="+a,d.children[0]="alias"+e)}),this.lookupPropertyFunctionIsUsed&&(c+=", "+this.lookupPropertyFunctionVarDeclaration());var f=["container","depth0","helpers","partials","data"];(this.useBlockParams||this.useDepths)&&f.push("blockParams"),this.useDepths&&f.push("depths");var h=this.mergeSource(c);return a?(f.push(h),Function.apply(this,f)):this.source.wrap(["function(",f.join(","),") {\n ",h,"}"])},mergeSource:function(a){var b=this.environment.isSimple,c=!this.forceBuffer,d=void 0,e=void 0,f=void 0,g=void 0;return this.source.each(function(a){a.appendToBuffer?(f?a.prepend(" + "):f=a,g=a):(f&&(e?f.prepend("buffer += "):d=!0,g.add(";"),f=g=void 0),e=!0,b||(c=!1))}),c?f?(f.prepend("return "),g.add(";")):e||this.source.push('return "";'):(a+=", buffer = "+(d?"":this.initializeBuffer()),f?(f.prepend("return buffer + "),g.add(";")):this.source.push("return buffer;")),a&&this.source.prepend("var "+a.substring(2)+(d?"":";\n")),this.source.merge()},lookupPropertyFunctionVarDeclaration:function(){return"\n lookupProperty = container.lookupProperty || function(parent, propertyName) {\n if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {\n return parent[propertyName];\n }\n return undefined\n }\n ".trim()},blockValue:function(a){var b=this.aliasable("container.hooks.blockHelperMissing"),c=[this.contextName(0)];this.setupHelperArgs(a,0,c);var d=this.popStack();c.splice(1,0,d),this.push(this.source.functionCall(b,"call",c))},ambiguousBlockValue:function(){var a=this.aliasable("container.hooks.blockHelperMissing"),b=[this.contextName(0)];this.setupHelperArgs("",0,b,!0),this.flushInline();var c=this.topStack();b.splice(1,0,c),this.pushSource(["if (!",this.lastHelper,") { ",c," = ",this.source.functionCall(a,"call",b),"}"])},appendContent:function(a){this.pendingContent?a=this.pendingContent+a:this.pendingLocation=this.source.currentLocation,this.pendingContent=a},append:function(){if(this.isInline())this.replaceStack(function(a){return[" != null ? ",a,' : ""']}),this.pushSource(this.appendToBuffer(this.popStack()));else{var a=this.popStack();this.pushSource(["if (",a," != null) { ",this.appendToBuffer(a,void 0,!0)," }"]),this.environment.isSimple&&this.pushSource(["else { ",this.appendToBuffer("''",void 0,!0)," }"])}},appendEscaped:function(){this.pushSource(this.appendToBuffer([this.aliasable("container.escapeExpression"),"(",this.popStack(),")"]))},getContext:function(a){this.lastContext=a},pushContext:function(){this.pushStackLiteral(this.contextName(this.lastContext))},lookupOnContext:function(a,b,c,d){var e=0;d||!this.options.compat||this.lastContext?this.pushContext():this.push(this.depthedLookup(a[e++])),this.resolvePath("context",a,e,b,c)},lookupBlockParam:function(a,b){this.useBlockParams=!0,this.push(["blockParams[",a[0],"][",a[1],"]"]),this.resolvePath("context",b,1)},lookupData:function(a,b,c){a?this.pushStackLiteral("container.data(data, "+a+")"):this.pushStackLiteral("data"),this.resolvePath("data",b,0,!0,c)},resolvePath:function(a,b,c,d,e){var g=this;if(this.options.strict||this.options.assumeObjects)return void this.push(f(this.options.strict&&e,this,b,a));for(var h=b.length;cthis.stackVars.length&&this.stackVars.push("stack"+this.stackSlot),this.topStackName()},topStackName:function(){return"stack"+this.stackSlot},flushInline:function(){var a=this.inlineStack;this.inlineStack=[];for(var b=0,c=a.length;b