Search the Community
Showing results for 'runtime'.
-
I'm not sure about this. That should only be the case on regular runtime migrations, but when doing a modules refresh all migrations should fire. I'm using RM Deployments and they perform a backup before each deployment. Is that what you are looking for?
-
Hey @herr rilke thx for your interest. Unfortunately nobody else showed interest so it looks like a video would not be interesting to a lot. I did that as well, but what I don't like with that approach is that if you move a block you have to adjust all classes of all blocks which is tedious. My approach works similar but different 😛 I use a dedicated block type solely for the color. This block sets a custom runtime flag (like $site->bgColor) and all blocks then add a custom class based on that bgColor flag's value. So once I have a color block with setting "muted" all following blocks might get the class "bg-muted", for example. Then when the next color block comes up it might set the bgColor flag to "primary" which would tell all following blocks to add the class "bg-primary". Now the challenge is to make that work with spacings and with frontend editing. On frontend editing the problem to solve is with sorting blocks, because classes come from php and sorting is done on the client with JS. So you need to add a little js to adjust classes once sorting happens. In my project this is done like this: // section color updates on sort document.addEventListener("rocksortable-added", (event) => { let sortable = event.detail; sortable.option("onMove", (e) => { setTimeout(() => { let el = e.dragged; let container = el.closest("[sortable]"); let children = container.children; let colorClass = null; // loop all items and update color classes for (let i = 0; i < children.length; i++) { let child = children[i]; // don't change class of dragged item if (child.classList.contains("sortable-drag")) continue; // update colorClass on every rpb-color block if (child.classList.contains("rpb-color")) { colorClass = child.getAttribute("data-col"); continue; } // update colorclass of element if (!colorClass) colorClass = "white"; child.classList.remove("has-bg-white"); child.classList.remove("has-bg-muted"); child.classList.remove("has-bg-primary"); child.classList.remove("has-bg-secondary"); child.classList.add(`has-bg-${colorClass}`); } }, 0); }); }); For block spacings I've switched my approach from PHP based calculations to CSS based calculations. The trick is to only use margin-top for blocks, because then you can define different margins for blocks based on the previous block like this: .bg-white + .bg-muted { margin-top: 20px; } This way you can achieve a lot of flexibility in your design with very little complexity in code. Hope that helps!
-
A Fieldtype for dynamic options that are generated at runtime via a hook. Configuration Inputfield type You can choose from a range of inputfield types on the Details tab of a Dynamic Options field. Your field will return a string or an array depending on if the selected input field type is for "single item selection" or "multiple item selection". Maximum number of items You can define a maximum number of items the field is allowed to contain. The core inputfields supported by this module will become disabled once the limit is reached. This option is only applicable if you have selected an inputfield type that is for multiple item selection. Format as Pagefile/Pageimage object(s) If the field will store paths/URLs to Pagefiles/Pageimages then you can enable this option to have the formatted value be a Pagefile/Pageimage object for "single" fields or an array of Pagefile/Pageimage objects for "multiple" fields. There is a related Select Images inputfield module that allows you to visually select image thumbnails. Defining selectable options Selectable options for a Dynamic Options field should be set in a FieldtypeDynamicOptions::getSelectableOptions hook in /site/ready.php. The hook should return an array of options as 'value' => 'label'. An example hook is shown on the Details tab of a Dynamic Options field: $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); if($field->name === 'your_field_name') { $event->return = [ 'red' => 'Red', 'green' => 'Green', 'blue' => 'Blue', ]; } }); Formatted value If a Dynamic Options field uses a "single" input type then its formatted value is a string, and if it uses a "multiple" input type then its formatted value is an array. The unformatted value of a Dynamic Options field is always an array. Also see the Configuration section above for description of an option to have the formatted value be Pagefile/Pageimage object(s). Examples of possible uses $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); // Select from the "files" field on the page if($field->name === 'select_files') { $options = []; foreach($page->files as $file) { // Value is basename, label is description if one exists $options[$file->basename] = $file->get('description|basename'); } $event->return = $options; } // Select from files in a folder if($field->name === 'select_folder_files') { $options = []; $path = $event->wire()->config->paths->root . 'my-folder/'; $files = $event->wire()->files->find($path); foreach($files as $file) { // Value is full path, label is basename $options[$file] = str_replace($path, '', $file); } $event->return = $options; } // Select from non-system templates if($field->name === 'select_template') { $options = []; foreach($event->wire()->templates as $template) { if($template->flags & Template::flagSystem) continue; $options[$template->id] = $template->name; } $event->return = $options; } // Select from non-system fields if($field->name === 'select_field') { $options = []; foreach($event->wire()->fields as $field) { if($field->flags & Field::flagSystem) continue; $options[$field->id] = $field->name; } $event->return = $options; } // Select from FormBuilder forms if($field->name === 'select_formbuilder_form') { $form_names = $event->wire()->forms->getFormNames(); // Use form names as both keys and values $event->return = array_combine($form_names, $form_names); } }); https://github.com/Toutouwai/FieldtypeDynamicOptions https://processwire.com/modules/fieldtype-dynamic-options/
- 21 replies
-
- 22
-
TinyMCE itself supports this via the style_formats setting, where you would define a "class" value rather than a "classes" value, e.g. style_formats: [ { title: 'Table row 1', selector: 'tr', class: 'tablerow1' } ] But PW's implementation of TinyMCE doesn't provide a way to directly control this setting and instead parses the contents of the "Custom style formats CSS" config textarea into the style_formats setting. Situations like this are why I think PW should provide a hookable method allowing the array of data that becomes the TinyMCE settings to be modified at runtime, as mentioned in this issue: https://github.com/processwire/processwire-issues/issues/1981
- 1 reply
-
- 4
-
This is a module that is similar in purpose to the Runtime Markup module. I made it primarily for my own use but am posting it here because I had a request to add it to the modules directory. Fieldtype Runtime Only Not a proper fieldtype because it doesn't save data to the database. The fieldtype only exists to provide a convenient way to add an inputfield to templates that will render some markup at runtime. For a field named "my_field"... Inputfield markup will be rendered from a file at /site/templates/RuntimeOnly/my_field.php. In addition to the standard ProcessWire variables this file receives: $page - the page being edited. $field - the Field object. $inputfield - the Inputfield object. JS file /site/templates/RuntimeOnly/my_field.js will be added to admin if that file exists. CSS file /site/templates/RuntimeOnly/my_field.css will be added to admin if that file exists. Tips Output formatting Output formatting for $page will be off in the context of Edit Page so if you want to use the formatted value of a field in your RuntimeOnly code you should use $page->getFormatted(). E.g. $value = $page->getFormatted('some_field_name'); Repeaters If the RuntimeOnly field is used inside a Repeater field then you can get the Repeater page it is on via $inputfield->hasPage. E.g. $repeater_page = $inputfield->hasPage; // Use $repeater_page as needed echo "The name of the repeater page is $repeater_page->name"; https://github.com/Toutouwai/FieldtypeRuntimeOnly https://modules.processwire.com/modules/inputfield-runtime-only/
- 30 replies
-
- 10
-
A big shout-out and thank-you to @bernhard for keeping on improving the RockFronend. (and the whole Rock-EcoSystem) continuously. I asked for a minify-feature and here it is! I am using RockFrontend in every project now as it combines some must-have features for frontend development (IMHO!). Today a new feature was added (or lets say improved as auto-minify was included before) The Minify Feature (for Styles and Scripts!) See the Wiki entry here Lets start simple: How do I include stylesheets with RockFrontend in the first place? RockFrontend always had the feature to bundle stylesheets via the styles() function. For example I am loading some LESS partials into my header like this. You don't have to use LESS files, regular CSS files work the same. Note: Since RockFrontend offers LESS support you can add those files directly, no need to compile them and add the CSS files. For this you have to install the ProcessWire Less module. <? $rockfrontend->styles() ->add($config->urls->templates . 'styles/less/uikit/_custom-import.less') // add single LESS or CSS file ->addAll($config->urls->templates . 'styles/less/project') // point to folder to include files automatically ->addAll($config->urls->templates . 'styles/less/custom') ?> The result in this case is a single compiled CSS file that will be included in your head automatically. RockFrontend is very smart. You don't have to include tons of partial LESS project files here. Just use the addAll() function and point to a folder where your assets are saved and the module does the import for you. This is how my folder structure looks like. If I create new LESS files in there, they will be added and compiled automatically at runtime. How to minify For debugging and development purposes I don't use the minify feature. Instead I use it on the staging server exclusively. To generate a minified version of your stylesheet just add minify(true) <? $rockfrontend->styles() ->add($config->urls->templates . 'styles/less/uikit/_custom-import.less') ->addAll($config->urls->templates . 'styles/less/project') ->addAll($config->urls->templates . 'styles/less/custom') ->minify(true); ?> If you want to chain the minify function it to your debug-mode state you can do it like this (my preferred solution). <? $rockfrontend->styles() ->add($config->urls->templates . 'styles/less/uikit/_custom-import.less') ->addAll($config->urls->templates . 'styles/less/project') ->addAll($config->urls->templates . 'styles/less/custom') ->minify(!$config->debug); ?> That's it! Does minify work with Scrips? Yes, exactly the same. But you make use of the scripts() function in this case. <? $rockfrontend->scripts() ->add($config->urls->templates . 'scripts/script1.js') ->add($config->urls->templates . 'scripts/script2.js') ->add($config->urls->templates . 'scripts/script3.js') ->minify(!$config->debug); ?> Note that these script files are not bundled (even if you chose minify false). Instead they all come out as minified versions separately. I find that this workflow I straight forward and it combines some of the best features that RockFrontend offers! If you combine this with the awesome autorefresh feature, frontend development becomes a breeze!
-
I came back to the thread to share some experiences and they are very similar to what @sebibu is running into. Latte introduces a lot of challenges with scoping and augments how you interact with ProcessWire. This is most pronounced for me when working with different inheritance types provided by Latte. When working with `include` there is no scope so anything that is added using {include 'file_name.latte'} will have access to the parent variables. This includes $page and $wire. Unfortunately the limitations with Latte is that you can't define blocks inside them, they only take parameters in the {include} statement. This is fine for most applications, but if you have a reusable component, such as a modal where the markup for the modal stays the same but complex contents should be contained in that markup, it would best be served by using {embed} because you can define something like {block content}{/block}. So while variables are available in primary template files, using all available features in Latte starts to lock out ProcessWire. Files included using {embed} are isolated in scope, so anything you want/need available in that file must be passed as parameters or added as rendered content within a {block}stuff here{/block}. This can get laborious for developers that are used to having ProcessWire's global variables and functions available anywhere. From my experience, there are two options. {embed 'file_to_embed.latte', page: $page, wire: $wire, foo: 'foo', bar: 'bar } {block content}waddap{/block} {/embed} You can choose to add parameters to an {embed} file that provide ProcessWire's API. I'm not entirely a fan of this because it feels laborious and if we're just going to have to pass the ProcessWire API into Latte components to overcome the limited scope, then it's just an extra step that ends up adding additional parameters passed to components all over your templates very repetitiously. I'm also used to working around the concept of only providing objects the specific data they are supposed to work with like best practices for dependency injection containers. The second option is to add custom functions to Latte using a hook at runtime. This method allows you to declare functions that will be available globally within all types of Latte reusability methods. Here's an example of a hook file on my current project. I have a dedicated latte_functions_extension.php file where I can declare these in an organized way. <?php declare(strict_types=1); namespace ProcessWire; use Latte\Extension; final class CustomLatteFunctions extends Extension { public function getFunctions(): array { return [ // Latte templating paths 'definitions' => fn (string $file) => $this->createComponentPath('definitions', $file), 'embeds' => fn (string $file) => $this->createComponentPath('embeds', $file), 'imports' => fn (string $file) => $this->createComponentPath('imports', $file), 'layout' => fn (string $file) => $this->createPath('layouts', $file), 'partial' => fn (string $file) => $this->createPath('partials', $file), // Expose ModernismWeekSite.module.php as site() 'site' => fn () => wire('modules')->get('ModernismWeekSite'), // Ensure that wire() is available in all components 'wire' => fn (?string $property = null) => wire($property), ]; } /** * Creates a path for including a component * @param string $file Dot notation subdir and filename, * @return string */ private function createComponentPath(string $componentSubdir, string $file): string { return $this->createPath("components/{$componentSubdir}", $file); } /** * Creates a component file path for a given filename does not require .latte suffix * @param string $templatesSubdir Name of directory in /site/templates * @param string $file Name of .latte file that exists in the directory */ private function createPath(string $templatesSubdir, string $file): string { !str_ends_with($file, '.latte') && $file = "{$file}.latte"; return wire('config')->paths->templates . "{$templatesSubdir}/{$file}"; } } $wire->addHookAfter( "RockFrontend::loadLatte", fn (HookEvent $e) => $e->return->addExtension(new CustomLatteFunctions), ); I've defined a 'wire' function that will be available in every component with a parameter that allows you to use it like you would expect to such as 'wire('modules')'. I have a custom site module so I've exposed that as 'site()'. If you wanted to make it easier to work with modules in your templates and included files you could define a more terse 'modules' function: <?php final class CustomLatteFunctions extends Extension { public function getFunctions(): array { return [ // ... 'modules' => fn (string $moduleName) => wire('modules')->get($moduleName), ]; } } I feel that there is a tradeoff when using Latte in ProcessWire. There are some great features in Latte, but it requires introducing abstractions and feature management to make Latte act like ProcessWire, like manually defining functions. This just means that you'll have to keep a balance of complexity/abstraction vs. using as minimal enough of a approach to keep it sane. The other challenge here is that now there can be a deviation between where the native ProcessWire API is used in Latte and other places where it isn't. So some files will use $modules, and other files will use modules(), and it's not clear whether that's referencing the ProcessWire functions API, or whether it's leveraging Latte's custom functions extension feature. Something to keep in mind when determining how other files will be included/rendered in other files. In my case I have two examples that brought this challenge out for me today. Here's one // Native behavior provided by the module // Does not work everywhere due to scoping in Latte. This caused an issue when trying to embed forms // in a modal within a {block} {$htmxForms->render($page->field_name)} // With one level of abstraction using a custom function // Because this replicates how ProcessWire provides the wire() function natively, the usage feels // natural and predictable, especially for core behavior, but this introduces a lot of verbosity // that starts to make files pretty messy {wire('modules')->get('FormBuilderHtmx')->render($page->field_name)} // With two levels of abstraction in Latte via a custom function // This looks better and still adheres to the syntax of the ProcessWire functions API // The issue is that every native ProcessWire function has to be manually replicated in our custom // functions hook class. Managing this in the long term requires extra work and cognitive load {modules('FormBuilderHtmx')->render($page->field_name)} // With 3 levels of abstraction // This has restored the feel of the variables provided by the module, but again we have to track // And implement them on an as-needed basis to manage them within the context of usage in Latte {htmxForms()->render($page->fieldName)} The level of abstraction you choose depends on how much customization you want vs. how much extra work it will take to maintain simplicity by hiding complexity. The other functions, 'embeds', 'definitions', 'imports', etc. are to overcome the relative paths all over the place in Latte. // In my home.latte file {layout './layouts/main.latte')} {var $collapseNavOnScroll = true} {import './components/definitions/headlines.latte'} {import './components/definitions/event_activity_card.latte')} {block subnav} {embed './components/embeds/event_subnav.latte', eventPage: $page->eventPage()}{/embed} {/block} // ...etc // Becomes {layout layout('main')} {var $collapseNavOnScroll = true} {import definitions('headlines')} {import definitions('event_activity_card')} {block subnav} {embed embeds('event_subnav'), eventPage: $page->eventPage()}{/embed} {/block} // etc. // In RPB blocks {embed '../../../components/embeds/example.latte', content: $block->body()}{/embed} {embed embeds('example'), content: $block->body()}{/embed} This really helps when working with Latte templates that embed components that have nested embeds and imports because the functions are generating absolute paths that Latte can handle. With these functions, I don't have to think about relative paths anywhere. As for the directory structure that I chose that requires the different paths, here's what it looks like: /templates ...etc /components /definitions /embeds /imports ...etc I chose that method because Latte has many different ways of including, embedding, and importing code from other files. It made more sense to organize my code by how Latte treats it. It wasn't my first choice, but this overcomes confusion that I was experiencing when working with all of the files sharing the same components directory. Without this type of organization it can be challenging to because of scoping and how {embed}, {include}, {define}, and {import} behave differently. Some accept parameters, export values, or use blocks, but not all of them do. So having a "modal.latte" component file that provides a {block content}{/block} next to 'button.latte' that doesn't render anything and only exports items created using {define}, next to a file that is only added to a template using {include} that doesn't take parameters or provide blocks had me jumping between files a lot and slows down development checking to see that the file is being used correctly. Just sharing some of my experiences in case it helps anyone else out. If anyone sees anything here that can be done better or if I'm missing out on features that Latte has that don't require some extra steps, let me know!
-
New blog post: Introducing the Custom Fields Module
FireWire replied to ryan's topic in News & Announcements
So this is actually something I cooked up to "mark" certain subfields within the custom field and pretty much the entire cause of this approach I took. Your include tip is pretty ingenious, I never would have thought of that. These are good tips. As for the use case of addClass, it's definitely a hacky method of attaching some additional data to each subfield. I have a Pages::saveReady hook that loops through groups of fields on the page and creates a supplemental index field for the SearchEngine module. It's only necessary for fields that aren't compatible with the module out of the box. I needed a way to indicate which subfields of custom fields shouldn't be added to the search index so in the custom field config I added 'input:nosearchindex'. It's just a workaround to indicate which fields should be excluded at configuration but which wouldn't be known at runtime. Really appreciate the response, very helpful! -
New blog post: Introducing the Custom Fields Module
FireWire replied to ryan's topic in News & Announcements
@ryan Is there a way to make the custom field children iterable? I have a script that loops through a couple of fieldtypes on a page where I don't know the names of the custom field children at runtime. My solution requires a little digging to access the '_custom_property_name' by drilling down into $customFields->defs()->getInputfields()->children() then looping through the subfield defs and accessing the value using $page->{$customField->name}->{$subfieldDef->_custom_property_name} I might be missing a better way to do that. I'm coding so much that my brain is going to melt and might have missed the right way to do it. -
Request for Input: What features should a PW calendar module have?
monollonom replied to bernhard's topic in Modules/Plugins
It’s an interesting mix indeed. Would you say these copies act as aliases but with their own date? Does it require a special template or is it that your module dynamically add a page reference field to the copies and then your module add hooks to get the main event’s data at runtime? What is very nice is it makes it super easy for the editor to edit a specific copy. Among many other benefits 🙂 The only downside I can think of in your setup is what if a recurring event gets cancelled? Is there a way to easily delete the copies? If you have a page reference field it shouldn’t be too hard actually... Maybe? If I rephrase what’s happening: you create the event, set its date to "recurring" and go ahead and create your copies. Then you save the page. If you edit it again, you have the "recurring" date type selected with the options to create new ones, but no indications of the copies previously created. Is this correct? -
Request for Input: What features should a PW calendar module have?
bernhard replied to bernhard's topic in Modules/Plugins
First milestone reached!!! 😍😍 Yesterday I spent the whole day with refactoring the JS of the rrule gui and I added some more options to cover hopefully 99.9 of all necessary scenarios. The gui now has a toggle to switch between simple and advanced mode to keep things as clean as possible and not overwhelm the users for simple rrules like "every week for 4 times". At the moment 80 hours have gone into this module (excluding the daterange input, which was already done some time ago). 💪🤯 Here my answers to the open questions: I don't think there is an ideal solution to this problem. I even found a bug in google calendar when comparing and playing around!!! I decided against a fake "never" option, as it does something different than what the UI shows. So in my case if the user does not enter an end date or a max number of events it get's limited to 100. This limit is configurable in the module's settings, though 😎 I've also added the dates of the first and last event beside the resulting rrule to make it clear what is going to happen. Additionally the user will get a table of all events that will be created. Sometimes - but not always! rrules are tricky 😄 - the first occurrence is the main event itself. In that case the table shows a hopefully clear indicator: Yes and no, I'm using a mix to somehow get the best of both worlds. I'm creating real pages for the events, but I'm only populating the date field. All other fields (like shown with the title in the video) will be inherited from the main event at runtime. This makes it possible to have complex event descriptions (like one built with RockPageBuilder) with images etc. and when creating 1000 recurring events it will still only consume the space of one event + 1000 date inputs (but not 1000 copies of images). I hope the video answers that? 🙂 -
Hi, I'm trying to change a template file at runtime based on the logged in users name. in addition to the /site/templates/ folder I have a /site/templates-dev/ folder, and under that is a folder for each developer. so, if I'm logged in as `timmy` and a file exists at `/site/templates-dev/timmy/article.php` then that file should be used, otherwise use `/site/templates/article.php`. in /site/ready.php I have $fp = $config->path('site') . 'templates-dev/' . $user->name . '/' . $page->template . '.php'; if(file_exists($fp)) { //echo "found tpl!"; $page->template->filename($fp); } If I uncomment the `echo` statement then I can see that it's finding the alternate template but if I view the page it's just blank (status 200, 'this request has no response data available'). Is anyone able to point me in the right direction please? Thanks, Martin.
-
Remove field from page edit or any repeater field within it.
elabx posted a topic in General Support
I had though of this hook: $wire->addHookBefore("ProcessPageEdit::buildFormContent", function ($event) { wire()->addHookBefore('Inputfield::render', function ($e) { $inputfield = $e->object; if (strpos($inputfield->name, "some_field") !== false && wire('modules')->SettingsModule->use_some_field == true) { $inputfield->label = "sample text"; $inputfield->collapsed = Inputfield::collapsedHidden; } }); }); I would have though that setting collapsed property before render would not allow the field to render. I want to handle some fields visibility in a settings module I use to manage enabling and disabling features in ProcessWire. An alternative strategy I do right now is that on the module save config I check for the module settings value and set the field to have it's input collapsed to hidden, but I'd like to think there is a more dynamic way at runtime? Does anybody have something like this working? -
Hello There fellow PW users. I am writing to get your advice on how to figure this problem out. I have never used the Front-end Editing functions of PW before so i tought i try it out. Using Ryans Front-end Page Editor module version 0.0.6. And i am using option A (automatic editing) method described in the documentation together with: <?PHP /* In my setup 'main_markdown_content' is a textarea field with a 'Markdown/Parsedown' textformatter. and inputfieldtype 'Textarea' selected on details tab for the field. Also content type is 'Unknown/Text' is selected. I get a warning when editing this field: -"This field currently allows Markup/HTML in formatted output. If you do not intend to allow Markup/HTML, please select the “HTML Entity Encoder” for “Text formatters”. But the field is storing Markdown and not HTML, as i understand it, the markdown gets rendered into HTML by the textformatter at runtime when outputting the field in my template ? */ /* using Option A (automatic editing) */ echo($page->main_markdown_content); ?> So far so good, it works, the trouble comes when the field is saved when clicking "Save" button on the frontend. For some reason new line endings are added to every line in my Markdown. Lets say this is the markdown (sorry for the swedish): # Sommar 2024 Sommar och sol, värme och regn om vart annat, för att inte tala om pollen. /EyeDentify. ## Uppdateringar - **2024-07-13:** Har uppdaterat Bulma.JS och önskar glad sommar. - **2024-04-10:** Önskar en trevlig vår. - **2023-12-24:** Önskar God Jul och Gott nytt år. - **2023-01-11:** Det är ett nytt år och jag fortsätter jobba på siten. Har planer för att utveckla den. Mer om det senare. And that after saving the frontend field ends up looking like this when i check it in the Admin editor: # Sommar 2024 Sommar och sol, värme och regn om vart annat, för att inte tala om pollen. /EyeDentify ## Uppdateringar - **2024-07-13:** Har uppdaterat Bulma.JS och önskar glad sommar. - **2024-04-10:** Önskar en trevlig vår. - **2023-12-24:** Önskar God Jul och Gott nytt år. - **2023-01-11:** Det är ett nytt år och jag fortsätter jobba på siten. Har planer för att utveckla den. Mer om det senare. (data above is copied exactly from the field after a save and it adds extra spaces.) Which is not what i want, the textformatter outputs the HTML as expected but it has more line endings and spaces between rows. I have to edit out the extra spaces in the Admin backend editor to make it go back to my original Markdown. I can´t figure out what is happening so i turned the editing of for now. Would realy like to be able to use it. You PW gurus have any ideas to what is going on ? If you want any other info to help me, just say so and i will try to supply it. Thanks again! /EyeDentify
-
Easily insert any complex HTML, Javascript or PHP output in your ProcessWire content by creating your own Hanna code tags. This module is based loosely on the WordPress Hana Code Insert plugin. A Hanna code tag looks like [[hello_world]]. A Hanna code tag with attributes looks like [[hello_world foo=bar" bar="foo]] using HTML style attributes or [[hello_world foo=bar, bar=foo]] using ProcessWire selector style attributes. After installing the module, you define your Hanna codes in Setup > Hanna Code. These Hanna codes that you define can then be entered within your body copy (or other text where you allow) and they will be replaced with the values defined or generated by your Hanna code. A common use case is to embed scripts or other bits of HTML or codes that would usually be stripped out by an editor like TinyMCE. However, Hanna codes can be more than just static snippets--they can be dynamic PHP or Javascript that outputs different things according to the request. PHP-based Hanna codes have access to the entire ProcessWire API. Hanna code accepts named attributes in the tag that can be passed as variables to PHP and Javascript Hanna codes. These attributes can be specified either in HTML attribute format or ProcessWire selector format. In either case, quotes should be used around the attribute value when the value contains whitespace or a comma. How to install Place the module files in /site/modules/TextformatterHannaCode/ In your admin, click Modules > Check for new modules Click install for TextformatterHannaCode Now to go Setup > Fields and locate the Textarea field(s) that you want to use Hanna codes with ("body" for instance). When editing the field, click the details tab, and select "Hanna Code" as the Textformatter. Save. Now go to Setup > Hanna Code and start defining your Hanna Codes! You may want to use one of the examples from this document to get started. Tag format Below is a Hanna code tag named hello_world with no attributes. If you pasted this into your body copy, you would get whatever the replacement value is that you defined. [[hello_world]] Below is a Hanna code tag named hello_world being passed attributes of foo, bar and foobar. If this were a PHP-based Hanna code, it would receive the variables $foo, $bar and $foobar: [[hello_world foo="bar" bar="foo" foobar="foo bar"]] Below is the same Hanna code tag as above, but with attributes more like ProcessWire selectors. You can use whatever format you prefer. Just note that unlike regular ProcessWire selectors, quotes (single or double) are required around any value that has whitespace. [[hello_world, foo=bar, bar=foo, foobar="foo bar"]] How to use Please make sure that you have completed the How to install section first. Then in your admin, go to Setup > Hanna Codes. Each Hanna code that you add has a type of either: Text/HTML, Javascript or PHP. The Text/HTML type is literally self explanatory in that your [[custom-tag]] is replaced with exactly the text you paste in. Anywhere that you type your [[custom-tag]] in your body copy will be replaced with exactly the static text you defined. More power opens up with the Javascript and/or PHP types of codes. These codes execute at runtime and thus can contain specific logic to produce different results. In fact, PHP Hanna codes have access to the entire ProcessWire API and are executed in the same manner as template files. Your PHP-based Hanna code should simply "echo" or "print" the replacement value. PHP example Create a new Hanna code with the name "children". Select "PHP" as the type. Paste in the following for the code: foreach($page->children as $child) { echo "<p><a href='$child->url'>$child->title</a>"; } Now go and edit a page that has children. In the body copy, enter [[children]] in the place where you want the output to appear. View the page, and you should see the rendered list of links to children. PHP example, part 2 Now lets take the above example further... Go back and edit your "children" Hanna code, as we are going to modify it to respond to a "parent" attribute. Change the code to this: if(isset($parent)) { // If $parent is an ID or path, lets convert it to a Page $parent = $pages->get($parent); } else { // otherwise lets assume the current page is the parent $parent = $page; } foreach($parent->children as $child) { echo "<p><a href='$child->url'>$child->title</a>"; } Go back and edit the page where you previously inserted the [[children]] tag, and change it to: [[children, parent=1]] (specifying the homepage) or [[children, parent=/path/to/some/parent/]] if you want to try something else. View the page and you should now see it showing the children of the homepage (or of another parent you specified). Please see the Javascript and PHP usage notes on the Hanna code entry screen. Security There are major security implications with a tool that will let you enter unfiltered text and code from your web browser. As a result, Hanna codes are meant for definition only by superusers and we recommend keeping it that way. Download Download the Hanna Code module from the ProcessWire modules page or from GitHub.
-
Best way to move a Processwire site to a new server 2020?
da² replied to modifiedcontent's topic in General Support
@BIMAQ-Admin Take care that this properties in the destination site/config.php are the same as the original site: $config->userAuthSalt = 'xxx'; $config->tableSalt = 'xxx'; The procedure could be more simple, no need to install PW then delete the database. Just rsync the files between 2 servers (with some exclusions for sessions and cache), and use mysql_dump to copy database from old server to new one. This is how I replicate my dev environment to the staging server, except I also exclude site/config.php after it was initially uploaded and adapted to server config. Example of database copy from source server to destination, to run on source server: mysqldump --add-drop-database -uSQL_USER_SOURCE -pSQL_PASSWORD_SOURCE --databases DATABASE_NAME | ssh MY_SSH_USER@DESTINATION_SERVER_IP "mysql -uSQL_USER_DESTINATION -pSQL_PASSWORD_DESTINATION" rsync, to run on source server (BUILD_DIRECTORY is the main PW directory path, /var/www/SITE_NAME/html/ is the destination directory: rsync -avh --delete-delay --exclude-from=deploy-excludes.txt -e ssh BUILD_DIRECTORY SSH_USER@DESTINATION_SERVER_IP:/var/www/SITE_NAME/html/ deploy-excludes.txt: site/assets/cache/* site/assets/sessions/* I'm not 100 % sure about syntax for "cache/*", I use "site/assets/cache/" in my case. Idea in your case is to copy empty folder without content, in case PW doesn't create it at runtime. Note that I added a SSH key on destination server, it's why I don't need to specify SSH user and password in commands. -
This week I've bumped the dev branch version to 3.0.241. Relative to the previous version, this one has 29 commits with a mixture of issue resolutions, new features and improvements, and other minor updates. A couple of PRs were also added today as well. This week I've also continued work on the FieldtypeCustom module that I mentioned last week. There were some questions about its storage model and whether you could query its properties from $pages->find() selectors (the answer is yes). Since the properties in a custom field are not fixed, and can change according to your own code and runtime logic, it doesn't map to a traditional DB table-and-column structure. That's not ideal when it comes to query-ability. But thankfully MySQL (5.7.8 and newer) supports a JSON column type and has the ability to match properties in JSON columns in a manner similar to how it can match them in traditional DB columns. Though the actual MySQL syntax to do it is a little cryptic, but thankfully we have ProcessWire selectors to make it simple. (It works the same as querying any other kind of field with subfields). MySQL can also support this with JSON encoded in a more traditional TEXT column with some reduced efficiency, though with the added benefit of supporting a FULLTEXT index. (Whereas the JSON column type does not support that type of index). For this reason, FieldtypeCustom supports both JSON and TEXT/MEDIUMTEXT/LONGTEXT column types. So you can choose whether you want to maximize the efficiency of column-level queries, or add the ability to perform text matching on all columns at once with a fulltext index. While I'm certain it's not as efficient as having separate columns in a table, I have been successfully using the same solution in the last few versions of FormBuilder (entries), and have found it works quite well. More soon. Thanks for reading and have a great weekend!
- 2 replies
-
- 26
-
Custom Inputfield Dependencies A module for ProcessWire CMS/CMF. Extends inputfield dependencies so that inputfield visibility or required status may be determined at runtime by selector or custom PHP code. Overview Custom Inputfield Dependencies adds several new settings options to the "Input" tab of "Edit Field". These are described below. Note that the visibility or required status of fields determined by the module is calculated once at the time Page Edit loads. If your dependency settings refer to fields in the page being edited then changes will not be recalculated until the page is saved and Page Edit reloaded. Usage Install the Custom Inputfield Dependencies module. Optional: for nice code highlighting of custom PHP install InputfieldAceExtended v1.2.0 or newer (currently available on the 'dev' branch of the GitHub repo). The custom inputfield dependencies are set on the "Input" tab of "Edit Field". Visibility Show only if page is matched by custom find Use InputfieldSelector to create a $pages->find() query. If the edited page is matched by the selector then the field is shown. Show only if page is matched by selector As above, but the selector string may be entered manually. Show only if custom PHP returns true Enter custom PHP/API code – if the statement returns boolean true then the field is shown. $page and $pages are available as local variables – other API variables may be accessed with $this, e.g. $this->config In most cases $page refers to the page being edited, but note that if the field is inside a repeater then $page will be the repeater page. As there could conceivably be cases where you want to use the repeater page in your custom PHP the module does not forcibly set $page to be the edited page. Instead, a helper function getEditedPage($page) is available if you want to get the edited page regardless of if the field in inside a repeater or not. $edited_page = $this->getEditedPage($page); Required The settings inputfields are the same as for Visibility above, but are used to determine if the field has 'required' status on the page being edited. https://github.com/Toutouwai/CustomInputfieldDependencies http://modules.processwire.com/modules/custom-inputfield-dependencies/
- 65 replies
-
- 20
-
Hi @Spinbox, This has been reported before but I have never found a solution. This is a runtime field used by Padloper in various places to embed editable content from other sources in the current page edit. E.g. in the case of products, to load variants. Without 'InputfieldRepeaterItem' class uploading variant images does not work, i.e. they show up but do not get saved and just disappear. I'll ask Ryan for his thoughts.
- 1 reply
-
- 1
-
I found the solution – of course right after posting ? One simply has to wrap the latte markup into {try} ... {/try} {try} {func($var)} {/try} And if you want to log the errors, you can do the following: $loggingHandler = function (\Throwable $e, \Latte\Runtime\Template $template) { wire()->log->save('latte_errors', $e->getMessage()); }; $latte->setExceptionHandler($loggingHandler);
-
What a great writeup! Thank you for putting your time into this. It could be part of the official docs for page classes. I would be interested in your reasoning behind naming the base class BasePage. In the docs that you linked, Ryan suggests DefaultPage. In https://github.com/processwire/processwire/blob/3cc76cc886a49313b4bfb9a1a904bd88d11b7cb7/wire/config.php#L202 it says: So are you using BasePage to not have every Page be based automatically on DefaultPage? I'm asking this, because I am using the DefaultPage class quite a lot and in some cases it caused some minor unexpected behaviours because the class is used for every Page. Would love to see how you implement properties in your base page class. Specifically how and where do you assign them? I utilize the loaded() method to assign those properties and found that it added lots of overhead because loaded() is called for every page that is loaded in a PW lifecycle when using it on the DefaultPage class. And that seemed a bit expensive to me, especially if construction of some properties requires more complex and expensive logic. Here's an example on how I implement custom properties on runtime class DefaultPage extends Page { // trait that provides methods for responsive images use ResponsiveImages; // define statuses that should be excluded from loadingcustom properties const excludeStatuses = array( Page::statusTrash, Page::statusUnpublished, Page::statusDraft, ); public $introTitle; public $introText; // set custom properties in loaded public function loaded() { // load custom properties only for allowed statuses if (!empty(array_intersect(array_keys($this->status(true)), self::excludeStatuses))) return; $this->set('introTitle', $this->text); $this->set('introText', nl2br($this->textarea)); } } I found that loaded() is called for every page regardless of their status. So I prevent loaded() from assigning properties for some statuses where the properties are not needed. Why I want to have custom properties in the first place? Almost all fields in my PW installations get very generic names like text, textarea, rte, image, text2 etc. I am using those fields in different templates for different purposes. Custom properties are a nice way to access those generic fields in a more meaningful manner and have them available in intellisense.
-
That is very interesting, thank you! For others: The `setting()` function is defined here. It's just a one-line wrapper around the `wireSetting()` function, which is define here. (`wireSetting()` is a small wrapper around a static var named `$settings`.) Here's the way `setting()` is described: /** * Get or set a runtime site setting * * This is a simple helper function for maintaining runtime settings in a site profile. * It simply sets and gets settings that you define. It is preferable to using ProcessWire’s * `$config` or `config()` API var/function because it is not used to store anything else for * ProcessWire. It is also preferable to using a variable (or variables) because it is always * in scope and accessible anywhere in your template files, even within existing functions. * * *Note: unlike other functions in the Functions API, this function is not related to API variables.* Perfect!
-
@MarkE If it's a Process module (i.e. extends the Process class) then that's a module designed to be an application in the admin, and that module is only executed when clicked on in the navigation (assuming the user has permission for it). It sounds like you also need a module that has the "autoload" enabled, meaning that it either loads every time PW boots, or under some autoload condition that you define. Process modules aren't meant to be "autoload" modules. Process modules are interactive applications, creating and processing forms in the admin. Autoload modules hook into ProcessWire and adjust runtime behavior. These are very different responsibilities, so you want 2 modules: one Process module and one autoload module. For instance, there's FormBuilder and ProcessFormBuilder, UserActivity and ProcessUserActivity, ProCache and ProcessProCache, etc. If you absolutely had to have it all in a Process module, you could, but you'd have to do your own permission check in the execute() method(s), and your module would appear in the admin navigation even for people that didn't have access to it. It's cleaner to do it with two modules, and one can install/uninstall the other just by using the "installs" property in your module info for one of them, and "requires" for the other.
-
This week the core dev branch version remains at 3.0.239 but relative to last week, it contains several optimizations and improvements. Details can be found in the dev branch commit log. I've also moved two Textformatter modules out of ProFields and into GitHub and the modules directory. These include: Auto Links This Textformatter module automatically links your specified phrases/words to your specified URLs. This is a potential SEO and accessibility tool for creating automatic contextual links with little effort. If there are pages that you commonly link to in your site from your textarea/rich text fields, then this Textformatter can save you some effort, automatically linking to those URLs. Text Blocks This Textformatter module enables you to assign a name to any block/region of text in a Textarea field. They are defined by typing start_name where you want the block to start, and stop_name where you want the block to stop. The block(s) of text can then be shown in any other Textarea field at runtime (site-wide) simply by typing the block name on its own line in the format show_name. Note that the word "name" in all of these examples would be whatever you've decided to name the block. Both modules have been updated just for public release with a new version. Both are considered Stable, as they have both been running without incident for several years. These modules were moved from ProFields to the public modules directory for three reasons. First is that they don't consume hardly any support resources, so they don't need to be commercially supported modules anymore. Second is that I'd like to keep ProFields focused more on field related modules (Fieldtype, Inputfield, and related) rather than Textformatter modules. Though admittedly the TextBlocks module does blur the line a bit, as it promotes potential greater reusability with existing Textarea/TinyMCE/CKEditor fields. Third is that there's already a lot in ProFields and I wanted to make room for new modules, such as the recently added PageEditChildren, which is part of ProFields at least in the short term. The FunctionalFields module may come out of ProFields well, as it hasn't required much in terms of support resources recently, though it is a useful Fieldtype/Inputfield module that is very much in keeping with the theme of ProFields, so I'm still debating on that one. Thanks for reading and have a great weekend!
- 1 reply
-
- 22
-
Add general information to specific template pages in admin
virtualgadjo replied to TomPich's topic in General Support
Hi, honestly for this kind of thing i use one of those two modules ? https://processwire.com/search/?q=runtime&t=Modules Robin's one is a little more recent but both work fine have a nice day