├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── composer.json
└── src
├── PreparseField.php
├── fields
└── PreparseFieldType.php
├── icon-mask.svg
├── icon.svg
├── migrations
├── m190226_225259_craft3.php
└── m240711_230833_jalendport.php
├── services
└── PreparseFieldService.php
└── templates
└── _components
└── fields
├── _input.twig
└── _settings.twig
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Preparse Field Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## 3.0.0-alpha.2 - 2024-07-15
8 | ### Fixed
9 | - Fixed reference to renamed method that was preventing preparse fields from rendering in the table view in certain cases ([#101](https://github.com/jalendport/craft-preparse/issues/101))
10 |
11 | ## 3.0.0-alpha.1 - 2024-07-12
12 | ### Added
13 | - Initial Craft 5 release
14 |
15 | ## 2.1.2 - 2024-07-12
16 | ### Fixed
17 | - Added namespace aliasing to prevent integrations with other plugins/modules from breaking
18 |
19 | ## 2.1.1 - 2024-07-12
20 | ### Fixed
21 | - Fixed a bug where the `preparseFieldService` could not be found([#100](https://github.com/jalendport/craft-preparse/issues/100))
22 |
23 | ## 2.1.0 - 2024-07-11
24 | ### Changed
25 | - Migrated to `jalendport/craft-preparse`
26 |
27 | ## 2.0.2 - 2022-12-05
28 | ### Fixed
29 | - Updated reference to Twigfield
30 |
31 | ## 2.0.1 - 2022-12-02
32 | ### Changed
33 | - Updated to use craft-code-editor instead of craft-twigfield ([#87](https://github.com/jalendport/craft-preparse/pull/87) - thanks @khalwat)
34 |
35 | ## 2.0.0 - 2022-08-08
36 | ### Added
37 | - Initial Craft 4 release
38 |
39 | ## 1.4.1 - 2022-12-02
40 | ### Changed
41 | - Updated to use craft-code-editor instead of craft-twigfield ([#86](https://github.com/jalendport/craft-preparse/pull/86) - thanks @khalwat)
42 |
43 | ## 1.4.0 - 2022-08-08
44 | ### Added
45 | - Added support for craft-twigfield ([#81](https://github.com/jalendport/craft-preparse/pull/81) - thanks @khalwat)
46 |
47 | ## 1.3.0 - 2022-08-06
48 | ### Added
49 | - Added datetime column type option ([#63](https://github.com/jalendport/craft-preparse/pull/63) - thanks @mmikkel)
50 |
51 | ## 1.2.5 - 2021-07-02
52 | ### Fixed
53 | - Reverted [#66](https://github.com/jalendport/craft-preparse/pull/66) due to bug where sometimes the element couldn't be re-fetched from the database ([#70](https://github.com/jalendport/craft-preparse/issues/70), [#71](https://github.com/jalendport/craft-preparse/issues/71), [#72](https://github.com/jalendport/craft-preparse/issues/72), [#73](https://github.com/jalendport/craft-preparse/issues/73))
54 | - Fixed a bug causing missing Matrix blocks on elements in certain cases ([#69](https://github.com/jalendport/craft-preparse/issues/69))
55 |
56 | ## 1.2.4 - 2021-02-24
57 | ### Fixed
58 | - Fixed a bug preventing elements from saving successfully in certain multisite setups ([#70](https://github.com/jalendport/craft-preparse/pull/70))
59 |
60 | ## 1.2.3 - 2021-02-23
61 | ### Fixed
62 | - Fixed a bug causing missing Matrix blocks on new elements ([#66](https://github.com/jalendport/craft-preparse/pull/66) - thanks @monachilada)
63 |
64 | ## 1.2.2 - 2020-11-30
65 | ### Fixed
66 | - Fixed a bug causing missing Matrix blocks on revisions ([#65](https://github.com/jalendport/craft-preparse/pull/65) - thanks @brandonkelly)
67 |
68 | ## 1.2.1 - 2020-06-25
69 | ### Fixed
70 | - Fixed incorrect branch names in README and composer.json
71 |
72 | ## 1.2.0 - 2020-06-25
73 | Transfer of ownership...
74 |
75 | ### Added
76 | - Added a class alias so sites with Preparse currently installed will continue to function smoothly after the namespace change
77 |
78 | ## 1.1.0 - 2019-08-03
79 | ### Fixed
80 | - Fixes compability issues with Craft 3.2 (Thanks, @brandonkelly).
81 |
82 | ### Added
83 | - Added `SortableFieldInterface` to field type.
84 |
85 | ### Changed
86 | - Changed composer requirement for `craftcms/cms` to `^3.2.0`.
87 |
88 | ## 1.0.7 - 2019-08-03
89 | ### Changed
90 | - Replaced `unset()` on `$_FILES` with setting it to an empty array (fixes #52).
91 |
92 | ## 1.0.6 - 2019-03-21
93 | ### Fixed
94 | - Fixed a bug where warnings weren’t showing up when editing an existing preparse field’s column type.
95 |
96 | ## 1.0.5.1 - 2019-02-27
97 | ### Fixed
98 | - Fixed an error that occurred when updating to preparse 1.0.5 on Craft 3.0.x
99 |
100 | ## 1.0.5 - 2019-02-27
101 | ### Added
102 | - Adds Craft 3 migrations. (thanks @carlcs).
103 |
104 | ## 1.0.4 - 2018-12-16
105 | ### Added
106 | - Adds support for showing preparse fields in element indexes (#33) (thanks @benface).
107 |
108 | ## 1.0.3 - 2018-10-24
109 | ### Fixed
110 | - Fixed an issue (#45) that would occure when uploading files through a front-end form for elements with a preparse field (thanks @aaronwaldon and @ademers).
111 |
112 | ## 1.0.2 - 2018-08-01
113 | ### Fixed
114 | - Fixed a bug that would keep preparse fields on assets from parsing on first save/upload (#37).
115 | - Fixes a bug where preparse fields could not be hidden in asset element modals and matrixblocks.
116 |
117 | ## 1.0.1 - 2018-07-30
118 | ### Added
119 | - Added support for DECIMAL column types.
120 |
121 | ### Fixed
122 | - Fixed an issue that would result in a duplicate key exception in multisite installations.
123 |
124 | ## 1.0.0 - 2017-12-02
125 | ### Added
126 | - Initial Craft 3 release.
127 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Jalen Davenport
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Preparse Field for Craft
2 |
3 | A fieldtype that parses Twig when an element is saved and saves the result as plain text.
4 |
5 | ## Requirements
6 |
7 | This plugin requires Craft CMS 5.0.0 or later and PHP 8.2.0 or later.
8 |
9 | ## Installation
10 |
11 | To install the plugin, follow these instructions.
12 |
13 | 1. Open your terminal and go to your Craft project:
14 |
15 | cd /path/to/project
16 |
17 | 2. Then tell Composer to load the plugin:
18 |
19 | composer require jalendport/craft-preparse
20 |
21 | 3. In the Control Panel, go to Settings → Plugins and click the “Install” button for Preparse Field.
22 |
23 | ## Usage
24 |
25 | When creating a new Preparse field, you add the Twig that you want run to the field's settings. When an element with that Preparse field is saved, the code will be parsed and the resulting value saved as plain text.
26 |
27 | It's worth noting that the Preparse field is only updated when the element the field is on is saved. If you grab data from a related element (like in the category title example below), and then update the related element, the preparsed value will not automatically be updated.
28 |
29 | In the Twig, the element that the Preparse field is added to is available as a variable named `element`. It's best to use this variable (as opposed to something like `entry` or `asset`) because it's possible you add the same Preparse field to multiple element types. This also means that when a Preparse field is added to a Matrix, SuperTable, or Neo block, that block will be what is available as `element`, so if you want to access the element that the Matrix/SuperTable/Neo field belongs to, you will want to use `element.owner`.
30 |
31 | ### Examples
32 |
33 | If you have a category field on your element named `relatedCategory`, you can save the category title to the Preparse field by adding the following Twig to the field settings:
34 |
35 | {{ element.relatedCategory.one().title ?? '' }}
36 |
37 | This is useful for saving preparsed values to a field for use with sorting, searching, or similar things.
38 |
39 | You can also do more advanced stuff, for instance performance optimizing. Let's say you have three different asset fields that may or may not be populated. Having to check these in the template may require a bunch of queries since you can't check if a field has a relation in Craft without actually querying for it. You could do something like this to get the id of the asset to use:
40 |
41 | {% if element.smallListImage | length %}
42 | {{ element.smallListImage.one().id }}
43 | {% elseif element.largeListImage | length %}
44 | {{ element.largeListImage.one().id }}
45 | {% elseif element.mainImage | length %}
46 | {{ element.mainImage.one().id }}
47 | {% endif %}
48 |
49 | _You'd probably want to wrap that in `{% apply spaceless %} ... {% endapply %}` to make it more useful..._
50 |
51 | Or you could just use it to do some bulk work when saving, like pre-generating a bunch of image transforms with [Imager X](https://plugins.craftcms.com/imager-x?craft4):
52 |
53 | {% if element.mainImage | length %}
54 | {% set transformedImages = craft.imager.transformImage(element.mainImage.one(), [
55 | { width: 1000 },
56 | { width: 900 },
57 | { width: 800 },
58 | { width: 700 },
59 | { width: 600 },
60 | { width: 500 },
61 | { width: 400 },
62 | { width: 300 },
63 | { width: 200 },
64 | { width: 100 }
65 | ]) %}
66 | {% endif %}
67 |
68 | Preparse also has access to your site's template root, so you can even include local templates if you want to do more advanced stuff and/or want to keep your field's Twig in version control:
69 |
70 | {% include '_partials/customPreparseFieldStuff' %}
71 |
72 | Make sure that you always write solid Twig, taking into account that fields may not be populated yet. If an error occurs in your Twig, the element will not be saved. [Code defensively!](https://nystudio107.com/blog/handling-errors-gracefully-in-craft-cms#defensive-coding-in-twig)
73 |
74 | ## Price, License, and Support
75 |
76 | The plugin is released under the MIT license, meaning you can do whatever you want with it as long as you don't blame us. **It's free**, which means there is absolutely no support included, but you might get it anyway. Just post an issue here on GitHub if you have one, and we'll see what we can do. :)
77 |
78 | ## Changelog
79 |
80 | See the [changelog file](https://github.com/jalendport/craft-preparse/blob/master/CHANGELOG.md).
81 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jalendport/craft-preparse",
3 | "description": "A fieldtype that parses Twig when an element is saved and saves the result as plain text.",
4 | "type": "craft-plugin",
5 | "version": "3.0.0-alpha.2",
6 | "keywords": [
7 | "craft",
8 | "cms",
9 | "craftcms",
10 | "craft-plugin",
11 | "fieldtype",
12 | "preparse",
13 | "twig"
14 | ],
15 | "support": {
16 | "docs": "https://github.com/jalendport/craft-preparse/blob/master/README.md",
17 | "issues": "https://github.com/jalendport/craft-preparse/issues"
18 | },
19 | "license": "MIT",
20 | "authors": [
21 | {
22 | "name": "Jalen Davenport",
23 | "homepage": "https://jalendport.com/"
24 | },
25 | {
26 | "name": "André Elvan",
27 | "homepage": "https://www.vaersaagod.no"
28 | }
29 | ],
30 | "require": {
31 | "craftcms/cms": "^5.0.0",
32 | "nystudio107/craft-code-editor": "^1.0.0",
33 | "php": "^8.2.0"
34 | },
35 | "autoload": {
36 | "psr-4": {
37 | "jalendport\\preparse\\": "src/",
38 | "aelvan\\preparsefield\\": "src/",
39 | "besteadfast\\preparsefield\\": "src/"
40 | }
41 | },
42 | "extra": {
43 | "name": "Preparse",
44 | "handle": "preparse-field",
45 | "schemaVersion": "1.1.0",
46 | "hasCpSettings": false,
47 | "hasCpSection": false,
48 | "changelogUrl": "https://github.com/jalendport/craft-preparse/blob/master/CHANGELOG.md",
49 | "components": {
50 | "preparseFieldService": "jalendport\\preparse\\services\\PreparseFieldService"
51 | },
52 | "class": "jalendport\\preparse\\PreparseField"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/PreparseField.php:
--------------------------------------------------------------------------------
1 | preparsedElements = [
58 | 'onBeforeSave' => [],
59 | 'onPropagate' => [],
60 | 'onMoveElement' => [],
61 | ];
62 |
63 | // Register our fields
64 | Event::on(
65 | Fields::class,
66 | Fields::EVENT_REGISTER_FIELD_TYPES,
67 | static function (RegisterComponentTypesEvent $event) {
68 | $event->types[] = PreparseFieldType::class;
69 | }
70 | );
71 |
72 | // Before save element event handler
73 | Event::on(
74 | Elements::class,
75 | Elements::EVENT_BEFORE_SAVE_ELEMENT,
76 | function (ElementEvent $event) {
77 | if ($event->element->getIsRevision()) {
78 | return;
79 | }
80 |
81 | /** @var Element $element */
82 | $element = $event->element;
83 | $key = $element->id . '__' . $element->siteId;
84 |
85 | if (!isset($this->preparsedElements['onBeforeSave'][$key])) {
86 | $this->preparsedElements['onBeforeSave'][$key] = true;
87 |
88 | $content = self::$plugin->preparseFieldService->getPreparseFieldsContent($element, 'onBeforeSave');
89 |
90 | if (!empty($content)) {
91 | $this->resetUploads();
92 | $element->setFieldValues($content);
93 | }
94 |
95 | unset($this->preparsedElements['onBeforeSave'][$key]);
96 | }
97 | }
98 | );
99 |
100 | // After propagate element event handler
101 | Event::on(
102 | Element::class,
103 | Element::EVENT_AFTER_PROPAGATE,
104 | function (ModelEvent $event) {
105 | /** @var Element $element */
106 | $element = $event->sender;
107 |
108 | if ($element->getIsRevision()) {
109 | return;
110 | }
111 |
112 | $key = $element->id . '__' . $element->siteId;
113 |
114 | if (!isset($this->preparsedElements['onPropagate'][$key])) {
115 | $this->preparsedElements['onPropagate'][$key] = true;
116 |
117 | $content = self::$plugin->preparseFieldService->getPreparseFieldsContent($element, 'onPropagate');
118 |
119 | if (!empty($content)) {
120 | $this->resetUploads();
121 |
122 | if ($element instanceof Asset) {
123 | $element->setScenario(Element::SCENARIO_DEFAULT);
124 | }
125 |
126 | $element->setFieldValues($content);
127 | $success = Craft::$app->elements->saveElement($element, true, false);
128 |
129 | // if no success, log error
130 | if (!$success) {
131 | Craft::error('Couldn’t save element with id “' . $element->id . '”', __METHOD__);
132 | }
133 | }
134 |
135 | unset($this->preparsedElements['onPropagate'][$key]);
136 | }
137 | }
138 | );
139 |
140 | // After move element event handler
141 | Event::on(
142 | Structures::class,
143 | Structures::EVENT_AFTER_MOVE_ELEMENT,
144 | function (MoveElementEvent $event) {
145 | /** @var Element $element */
146 | $element = $event->element;
147 | $key = $element->id . '__' . $element->siteId;
148 |
149 | if (self::$plugin->preparseFieldService->shouldParseElementOnMove($element) && !isset($this->preparsedElements['onMoveElement'][$key])) {
150 | $this->preparsedElements['onMoveElement'][$key] = true;
151 |
152 | if ($element instanceof Asset) {
153 | $element->setScenario(Element::SCENARIO_DEFAULT);
154 | }
155 |
156 | $success = Craft::$app->getElements()->saveElement($element, true, false);
157 |
158 | // if no success, log error
159 | if (!$success) {
160 | Craft::error('Couldn’t move element with id “' . $element->id . '”', __METHOD__);
161 | }
162 |
163 | unset($this->preparsedElements['onMoveElement'][$key]);
164 | }
165 | }
166 | );
167 | }
168 |
169 | /**
170 | * @param $msg
171 | * @param string $level
172 | * @param string $file
173 | */
174 | public static function log($msg, string $level = 'notice', string $file = 'Preparse')
175 | {
176 | try
177 | {
178 | $file = Craft::getAlias('@storage/logs/' . $file . '.log');
179 | $log = "\n" . date('Y-m-d H:i:s') . " [{$level}]" . "\n" . print_r($msg, true);
180 | FileHelper::writeToFile($file, $log, ['append' => true]);
181 | }
182 | catch(Exception $e)
183 | {
184 | Craft::error($e->getMessage());
185 | }
186 | }
187 |
188 | /**
189 | * @param $msg
190 | * @param string $level
191 | * @param string $file
192 | */
193 | public static function error($msg, string $level = 'error', string $file = 'Preparse')
194 | {
195 | static::log($msg, $level, $file);
196 | }
197 |
198 | /**
199 | * Fix file uploads being processed twice by craft, which causes an error.
200 | *
201 | * @see https://github.com/jalendport/craft-preparse/issues/23#issuecomment-284682292
202 | */
203 | private function resetUploads()
204 | {
205 | $_FILES = [];
206 | UploadedFile::reset();
207 | }
208 | }
209 |
210 | class_alias(PreparseField::class, \aelvan\preparsefield\PreparseField::class);
211 | class_alias(PreparseField::class, \besteadfast\preparsefield\PreparseField::class);
212 |
--------------------------------------------------------------------------------
/src/fields/PreparseFieldType.php:
--------------------------------------------------------------------------------
1 | ''],
75 | ['columnType', 'string'],
76 | ['columnType', 'default', 'value' => ''],
77 | ['decimals', 'number'],
78 | ['decimals', 'default', 'value' => 0],
79 | ['textareaRows', 'number'],
80 | ['textareaRows', 'default', 'value' => 5],
81 | ['parseBeforeSave', 'boolean'],
82 | ['parseBeforeSave', 'default', 'value' => false],
83 | ['parseOnMove', 'boolean'],
84 | ['parseOnMove', 'default', 'value' => false],
85 | ['displayType', 'string'],
86 | ['displayType', 'default', 'value' => 'hidden'],
87 | ['allowSelect', 'boolean'],
88 | ['allowSelect', 'default', 'value' => false],
89 | ]);
90 | }
91 |
92 | /**
93 | * @return array|string
94 | * @throws Exception
95 | */
96 | public function getContentColumnType(): array|string
97 | {
98 | if ($this->columnType === Schema::TYPE_DECIMAL) {
99 | return Db::getNumericalColumnType(null, null, $this->decimals);
100 | }
101 |
102 | return $this->columnType;
103 | }
104 |
105 | /**
106 | * @return null|string
107 | * @throws LoaderError
108 | * @throws RuntimeError
109 | * @throws SyntaxError|Exception
110 | */
111 | public function getSettingsHtml(): ?string
112 | {
113 | $columns = [
114 | Schema::TYPE_TEXT => Craft::t('preparse-field', 'Text (stores about 64K)'),
115 | Schema::TYPE_MEDIUMTEXT => Craft::t('preparse-field', 'Mediumtext (stores about 16MB)'),
116 | Schema::TYPE_INTEGER => Craft::t('preparse-field', 'Number (integer)'),
117 | Schema::TYPE_DECIMAL => Craft::t('preparse-field', 'Number (decimal)'),
118 | Schema::TYPE_FLOAT => Craft::t('preparse-field', 'Number (float)'),
119 | Schema::TYPE_DATETIME => Craft::t('preparse-field', 'Date (datetime)'),
120 | ];
121 |
122 | $displayTypes = [
123 | 'hidden' => 'Hidden',
124 | 'textinput' => 'Text input',
125 | 'textarea' => 'Textarea',
126 | ];
127 |
128 | // Render the settings template
129 | return Craft::$app->getView()->renderTemplate(
130 | 'preparse-field/_components/fields/_settings',
131 | [
132 | 'field' => $this,
133 | 'columns' => $columns,
134 | 'displayTypes' => $displayTypes,
135 | 'existing' => $this->id !== null,
136 | ]
137 | );
138 | }
139 |
140 | /**
141 | * @param mixed $value
142 | * @param ElementInterface|null $element
143 | *
144 | * @return string
145 | * @throws LoaderError
146 | * @throws RuntimeError
147 | * @throws SyntaxError|Exception
148 | */
149 | public function getInputHtml(mixed $value, ?ElementInterface $element = null): string
150 | {
151 | // Get our id and namespace
152 | $id = Craft::$app->getView()->formatInputId($this->handle);
153 | $namespacedId = Craft::$app->getView()->namespaceInputId($id);
154 |
155 | // Render the input template
156 | $displayType = $this->displayType;
157 | if ($displayType !== 'hidden' && $this->columnType === Schema::TYPE_DATETIME) {
158 | $displayType = 'date';
159 | }
160 | return Craft::$app->getView()->renderTemplate(
161 | 'preparse-field/_components/fields/_input',
162 | [
163 | 'name' => $this->handle,
164 | 'value' => $value,
165 | 'field' => $this,
166 | 'id' => $id,
167 | 'namespacedId' => $namespacedId,
168 | 'displayType' => $displayType,
169 | ]
170 | );
171 | }
172 |
173 | /**
174 | * @inheritdoc
175 | */
176 | public function getSearchKeywords(mixed $value, ElementInterface $element): string
177 | {
178 | if ($this->columnType === Schema::TYPE_DATETIME) {
179 | return '';
180 | }
181 | return parent::getSearchKeywords($value, $element);
182 | }
183 |
184 | /**
185 | * @inheritdoc
186 | * @throws InvalidConfigException
187 | */
188 | public function getPreviewHtml(mixed $value, ElementInterface $element): string
189 | {
190 | if (!$value) {
191 | return '';
192 | }
193 |
194 | if ($this->columnType === Schema::TYPE_DATETIME) {
195 | return Craft::$app->getFormatter()->asDatetime($value, Locale::LENGTH_SHORT);
196 | }
197 |
198 | return parent::getPreviewHtml($value, $element);
199 | }
200 |
201 | /**
202 | * @inheritdoc
203 | * @throws \Exception
204 | */
205 | public function normalizeValue(mixed $value, ?ElementInterface $element = null): mixed
206 | {
207 | if ($this->columnType === Schema::TYPE_DATETIME) {
208 | if ($value && ($date = DateTimeHelper::toDateTime($value)) !== false) {
209 | return $date;
210 | }
211 | return null;
212 | }
213 | return parent::normalizeValue($value, $element);
214 | }
215 |
216 | /**
217 | * @inheritdoc
218 | */
219 | public function modifyElementsQuery(ElementQueryInterface $query, mixed $value): void
220 | {
221 | if ($this->columnType === Schema::TYPE_DATETIME) {
222 | if ($value !== null) {
223 | /** @var ElementQuery $query */
224 | $query->subQuery->andWhere(Db::parseDateParam('content.' . Craft::$app->getContent()->fieldColumnPrefix . $this->handle, $value));
225 | }
226 | }
227 | parent::modifyElementsQuery($query, $value);
228 | }
229 |
230 | /**
231 | * @inheritdoc
232 | */
233 | public function getContentGqlType(): Type|array
234 | {
235 | if ($this->columnType === Schema::TYPE_DATETIME) {
236 | return DateTimeType::getType();
237 | }
238 | return parent::getContentGqlType();
239 | }
240 | }
241 |
242 | class_alias(PreparseFieldType::class, \aelvan\preparsefield\fields\PreparseFieldType::class);
243 | class_alias(PreparseFieldType::class, \besteadfast\preparsefield\fields\PreparseFieldType::class);
244 |
--------------------------------------------------------------------------------
/src/icon-mask.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/icon.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/migrations/m190226_225259_craft3.php:
--------------------------------------------------------------------------------
1 | update('{{%fields}}', ['type' => PreparseFieldType::class], ['type' => 'PreparseField_Preparse']);
19 | }
20 |
21 | /**
22 | * @inheritdoc
23 | */
24 | public function safeDown()
25 | {
26 | echo "m190226_225259_craft3 cannot be reverted.\n";
27 | return false;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/migrations/m240711_230833_jalendport.php:
--------------------------------------------------------------------------------
1 | update('{{%fields}}', ['type'=> PreparseFieldType::class], ['type' => $oldType]);
24 |
25 | // Don’t make the same config changes twice
26 | $projectConfig = Craft::$app->getProjectConfig();
27 | $schemaVersion = $projectConfig->get('plugins.preparse-field.schemaVersion', true);
28 |
29 | if (version_compare($schemaVersion, '1.1.0', '>='))
30 | {
31 | return true;
32 | }
33 |
34 | $fields = $projectConfig->get('fields') ?? [];
35 |
36 | foreach ($fields as $fieldUid => $field)
37 | {
38 | if ($field['type'] === $oldType)
39 | {
40 | $field['type'] = PreparseFieldType::class;
41 | try {
42 | $projectConfig->set("fields.{$fieldUid}", $field);
43 | } catch (Exception|ErrorException $e) {
44 | return false;
45 | }
46 | }
47 | }
48 |
49 | return true;
50 | }
51 |
52 | /**
53 | * @inheritdoc
54 | */
55 | public function safeDown(): bool
56 | {
57 | echo "m240711_230833_jalendport cannot be reverted.\n";
58 | return false;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/services/PreparseFieldService.php:
--------------------------------------------------------------------------------
1 | getFieldLayout();
42 |
43 | if ($fieldLayout) {
44 | foreach ($fieldLayout->getCustomFields() as $field) {
45 | if ($field instanceof PreparseFieldType) {
46 | /** @var PreparseFieldType $field */
47 |
48 | // only get field content for the right event listener
49 | $isBeforeSave = ($eventHandle === 'onBeforeSave');
50 | $parseBeforeSave = (bool)$field->parseBeforeSave;
51 |
52 | if ($isBeforeSave === $parseBeforeSave) {
53 | $fieldValue = $this->parseField($field, $element);
54 |
55 | if ($fieldValue !== null) {
56 | $content[$field->handle] = $fieldValue;
57 | }
58 | }
59 | }
60 | }
61 | }
62 |
63 | return $content;
64 | }
65 |
66 | /**
67 | * Parses field for a given element.
68 | *
69 | * @param PreparseFieldType $field
70 | * @param Element $element
71 | *
72 | * @return null|string|DateTime
73 | * @throws Exception
74 | */
75 | public function parseField(PreparseFieldType $field, Element $element): DateTime|string|null
76 | {
77 | $fieldTwig = $field->fieldTwig;
78 | $columnType = $field->columnType;
79 | $decimals = $field->decimals;
80 | $fieldValue = null;
81 |
82 | $elementTemplateName = 'element';
83 |
84 | if (method_exists($element, 'refHandle')) {
85 | $elementTemplateName = strtolower($element->refHandle());
86 | }
87 |
88 | // Enable generateTransformsBeforePageLoad always
89 | $generateTransformsBeforePageLoad = Craft::$app->config->general->generateTransformsBeforePageLoad;
90 | Craft::$app->config->general->generateTransformsBeforePageLoad = true;
91 |
92 | // save cp template path and set to site templates
93 | $oldMode = Craft::$app->view->getTemplateMode();
94 | Craft::$app->view->setTemplateMode(View::TEMPLATE_MODE_SITE);
95 |
96 | // render value from the field template
97 | try {
98 | $vars = array_merge(['element' => $element], [$elementTemplateName => $element]);
99 | $fieldValue = Craft::$app->view->renderString($fieldTwig, $vars);
100 | } catch (\Exception $e) {
101 | Craft::error('Couldn’t render value for element with id “'.$element->id.'” and preparse field “'.
102 | $field->handle.'” ('.$e->getMessage().').', __METHOD__);
103 | }
104 |
105 | // restore cp template paths
106 | Craft::$app->view->setTemplateMode($oldMode);
107 |
108 | // set generateTransformsBeforePageLoad back to whatever it was
109 | Craft::$app->config->general->generateTransformsBeforePageLoad = $generateTransformsBeforePageLoad;
110 |
111 | if (null === $fieldValue) {
112 | return null;
113 | }
114 |
115 | if ($columnType === Schema::TYPE_FLOAT || $columnType === Schema::TYPE_INTEGER) {
116 | if ($decimals > 0) {
117 | return number_format(trim($fieldValue), $decimals, '.', '');
118 | }
119 |
120 | return number_format(trim($fieldValue), 0, '.', '');
121 | } else if ($columnType === Schema::TYPE_DATETIME) {
122 | $fieldValue = \trim($fieldValue);
123 | if (!$fieldValue || !$date = DateTimeHelper::toDateTime($fieldValue, true)) {
124 | // Return an empty string rather than null to clear out existing DateTime value (null would mean "no change")
125 | return '';
126 | }
127 | return $date;
128 | }
129 |
130 | return $fieldValue;
131 | }
132 |
133 | /**
134 | * Checks to see if an element has a preparse field that should be saved on move
135 | *
136 | * @param Element $element
137 | *
138 | * @return bool
139 | */
140 | public function shouldParseElementOnMove(Element $element): bool
141 | {
142 | $fieldLayout = $element->getFieldLayout();
143 |
144 | if ($fieldLayout) {
145 | foreach ($fieldLayout->getCustomFields() as $field) {
146 | if ($field instanceof PreparseFieldType) {
147 | $parseOnMove = $field->parseOnMove;
148 |
149 | if ($parseOnMove) {
150 | return true;
151 | }
152 | }
153 | }
154 | }
155 |
156 | return false;
157 | }
158 | }
159 |
160 | class_alias(PreparseFieldService::class, \aelvan\preparsefield\services\PreparseFieldService::class);
161 | class_alias(PreparseFieldService::class, \besteadfast\preparsefield\services\PreparseFieldService::class);
162 |
--------------------------------------------------------------------------------
/src/templates/_components/fields/_input.twig:
--------------------------------------------------------------------------------
1 | {#
2 | /**
3 | * Preparse Field plugin for Craft CMS 4.x
4 | *
5 | * Field Input
6 | */
7 | #}
8 |
9 | {% import "_includes/forms" as forms %}
10 |
11 | {% set displayType = displayType ?? 'hidden' %}
12 |
13 | {% if displayType == 'hidden' %}
14 |
19 | {% else %}
20 | {# Setup our field #}
21 | {% if displayType == 'date' %}
22 | {{ forms.dateTimeField({
23 | value: value,
24 | disabled: true,
25 | }) }}
26 |
31 | {% elseif displayType == 'textarea' %}
32 | {{ forms.textarea({
33 | value: value,
34 | disabled: not field['allowSelect'],
35 | readonly: field['allowSelect'],
36 | rows: field['textareaRows']
37 | }) }}
38 | {% else %}
39 | {{ forms.text({
40 | value: value,
41 | disabled: not field['allowSelect'],
42 | readonly: field['allowSelect']
43 | }) }}
44 | {% endif %}
45 | {% endif %}
46 |
--------------------------------------------------------------------------------
/src/templates/_components/fields/_settings.twig:
--------------------------------------------------------------------------------
1 | {#
2 | /**
3 | * Preparse Field plugin for Craft CMS 4.x
4 | *
5 | * Field Settings
6 | */
7 | #}
8 |
9 | {% import "_includes/forms" as forms %}
10 | {% import "codeeditor/codeEditor" as codeEditor %}
11 |
12 | {% set monacoOptions = {
13 | } %}
14 | {% set codeEditorOptions = {
15 | wrapperClass:"monaco-editor-background-frame"
16 | } %}
17 | {{ codeEditor.textareaField( {
18 | label: "Twig code to parse"|t,
19 | instructions: "Enter the twig code that you want to parse after the entry has been saved.\nIf the column type is set to Date (datetime), the parsed Twig should output a date formatted as `Y-m-d H:i:s`."|t,
20 | id: 'fieldTwig',
21 | name: 'fieldTwig',
22 | value: field['fieldTwig'],
23 | class: 'code',
24 | rows: 10,
25 | }, "CodeField", monacoOptions, codeEditorOptions) }}
26 |
27 | {% set columnType %}
28 | {{ forms.select({
29 | id: 'columnType',
30 | name: 'columnType',
31 | options: columns,
32 | value: field['columnType'],
33 | }) }}
34 | {% endset %}
35 |
36 | {{ forms.field({
37 | label: "Column Type"|t,
38 | instructions: "The underlying database column type to use when saving content."|t,
39 | id: 'columnType',
40 | warning: (existing ? "Changing this may result in data loss."|t),
41 | }, columnType) }}
42 |
43 | {{ forms.textField({
44 | label: 'Decimals'|t,
45 | instructions: "Only relevant if the column type is Number (float)."|t,
46 | id: 'decimals',
47 | name: 'decimals',
48 | size: 2,
49 | maxlength: 2,
50 | value: field['decimals']
51 | }) }}
52 |
53 | {{ forms.lightswitchField({
54 | label: "Parse before save"|t,
55 | instructions: "If you turn this on, the field will be parsed before the element is saved."|t,
56 | id: 'parseBeforeSave',
57 | name: 'parseBeforeSave',
58 | on: field['parseBeforeSave'],
59 | onLabel: "Yes"|t,
60 | offLabel: "no"|t
61 | }) }}
62 |
63 | {{ forms.lightswitchField({
64 | label: "Parse on move"|t,
65 | instructions: "If you turn this on, elements that contain this field will be resaved when an element is moved in a structure. Necessary if you use .parent() or similar structure-related code in your twig."|t,
66 | id: 'parseOnMove',
67 | name: 'parseOnMove',
68 | on: field['parseOnMove'],
69 | onLabel: "Yes"|t,
70 | offLabel: "no"|t
71 | }) }}
72 |
73 | {% set displayType %}
74 | {{ forms.select({
75 | id: 'displayType',
76 | name: 'displayType',
77 | options: displayTypes,
78 | value: field['displayType'],
79 | }) }}
80 | {% endset %}
81 |
82 | {{ forms.field({
83 | label: "Display Type"|t,
84 | instructions: "Select how this field is to be shown in the edit page.\nIf the column type is set to Date (datetime), any other option than \"Hidden\" will render the field value in a disabled datepicker."|t,
85 | id: 'displayType',
86 | }, displayType) }}
87 |
88 | {{ forms.textField({
89 | label: 'Textarea rows'|t,
90 | instructions: "Only relevant if the display type is Textarea."|t,
91 | id: 'textareaRows',
92 | name: 'textareaRows',
93 | size: 2,
94 | maxlength: 2,
95 | value: field['textareaRows']
96 | }) }}
97 |
98 | {{ forms.lightswitchField({
99 | label: "Allow text selection"|t,
100 | instructions: "If you turn this on and the field is visible, the output of the field will be selectable by the user."|t,
101 | id: 'allowSelect',
102 | name: 'allowSelect',
103 | on: field['allowSelect'],
104 | onLabel: "Yes"|t,
105 | offLabel: "no"|t
106 | }) }}
107 |
--------------------------------------------------------------------------------