Jump to content

Search the Community

Showing results for 'runtime'.

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


Forums

  • Welcome to ProcessWire
    • News & Announcements
    • Showcase
    • Wishlist & Roadmap
  • Community Support
    • Getting Started
    • Tutorials
    • FAQs
    • General Support
    • API & Templates
    • Modules/Plugins
    • Themes and Profiles
    • Multi-Language Support
    • Security
    • Jobs
  • Off Topic
    • Pub
    • Dev Talk

Product Groups

  • Form Builder
  • ProFields
  • ProCache
  • ProMailer
  • Login Register Pro
  • ProDrafts
  • ListerPro
  • ProDevTools
  • Likes
  • Custom Development

Find results in...

Find results that contain...


Date Created

  • Start

    End


Last Updated

  • Start

    End


Filter by number of...

Joined

  • Start

    End


Group


AIM


MSN


Website URL


ICQ


Yahoo


Jabber


Skype


Location


Interests

  1. 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?
  2. 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!
  3. 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
  4. 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!
  5. 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!
  6. @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.
  7. 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?
  8. 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? 🙂
  9. 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?
  10. @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.
  11. 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
  12. 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.
  13. 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!
  14. 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);
  15. 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.
  16. @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.
  17. 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!
  18. 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
  19. @ryan - I wonder if PW might automatically include those gc tweaks when Debian is detected? Maybe via something like this. Perhaps it could be run at install, rather than runtime if you're concerned about performance although it is only about 1ms. if (strtolower(substr(PHP_OS, 0, 5)) === 'linux') { $vars = array(); $files = glob('/etc/*-release'); foreach ($files as $file) { $lines = array_filter(array_map(function($line) { // split value from key $parts = explode('=', $line); // makes sure that "useless" lines are ignored (together with array_filter) if (count($parts) !== 2) return false; // remove quotes and new lines $parts[1] = str_replace(array('"', "'", "\n"), '', $parts[1]); return $parts; }, file($file))); foreach ($lines as $line) { $vars[$line[0]] = $line[1]; } } d($vars['ID']); }
  20. You can use https://processwire.com/modules/fieldtype-runtime-markup/ for that.
  21. 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!
  22. I'm not sure I understand ... For one request you can store your variable wherever you want (and where it is allowed), for example you can set $config->foo = "Foo!" somewhere and later you can access that runtime property. On the next request $config->foo will be null again unless you set it somewhere. Until PHP8.2 it was possible to use $wire for that as well, eg $wire->foo = "Foo!", but since PHP8.2 setting dynamic properties is deprecated.
  23. I've done some searching for an answer on this as I can't believe it has not come up before: My problem is that I would like to use a "selector" field (i.e. the interactive selector builder) to compare, for example, a datetime field with 'today'. The issue is that I want it to operate dynamically, so the comparator needs to be stored as 'today' and only converted when the selector is run. This works fine if I just use a text field instead of the selector field and allow the user to input a selector string - e.g. "myDatetime>today", but in the selector builder the comparison field requires an absolute date - a string entry of a relative date is impossible. However, for many reasons, I would prefer not to have a user attempting to enter a selector string. Has anyone tried something similar? If not, I'm toying with the idea of writing a module to extend InputfieldDatetime. That would permit the storing of a valid string as well as an actual date. The string would be only converted to a date at runtime. This would allow entering a date as, for example, 'last month' such that when the field is formatted it would be displayed as a date one month before the execution date. This contrasts with the present module where, although you can default to 'today', I think that is converted to a timestamp at the time of entry and stored as such. Any thoughts on that?
  24. On the dev branch this week we have a good collection of issue fixes and feature requests. The dev branch commit log has all the details. One feature added this week which I think could come in very handy is #520 (via @Jonathan Lahijani) which adds the ability to hide individual images in an images field. When an image is hidden, you can see and work with it in the admin, but it gets removed from the field value on the front-end of the site at runtime, effectively hiding it. I know I'll use this a lot, particularly on photo galleries where I may want to remove an image or two from appearing in the grid of photos, but don't necessarily want to delete them. Images can be hidden (or unhidden) from the Actions select of each image, where you'll see a "Hide" option (or an "Unhide" option if the image is already hidden). Hidden images are also dimmed out when viewing the images field in the admin. On the API side, you can hide or unhide images and files using $image->hidden(true) to hide, $image->hidden(false) to unhide, and $image->hidden() to get a true or false as to whether or not the image is hidden. Though this will only be useful on unformatted field values, since hidden images are automatically removed from formatted field values. The same can be used with regular file fields, but we don't currently have a UI/interface for hiding or unhiding items from regular (non-image) file fields. Likely we'll add one soon, but I figured it's likely to get more use with image fields than file fields, so figured we'd start there. More next week. Thanks for reading and have a great weekend!
  25. This is my approach to combining CSS files for improved page speed. The implementation is straightforward; you only need to add one PHP file to your server and make a few tweaks. My setup: -site/ -templates/ - inc/ - styles/ _init.php Include the CombinedCSS.php file at the top of my _init.php: include_once('./inc/CombinedCSS.php'); _main.php Add the following to the <head> section: <?php $cssFiles = [ paths()->templates . 'styles/uikit.min.css', paths()->templates . 'styles/uikit.ext.css', paths()->templates . 'styles/main.css' ]; echo CombinedCSS::CSS($cssFiles); ?> CombinedCSS.php <?php namespace ProcessWire; /** * Import the RuntimeException class for throwing runtime exceptions. */ use RuntimeException; /** * Class CombinedCSS * * A class for combining and minifying CSS files and generating a link tag for the combined CSS. */ class CombinedCSS { /** * @var string|null The output directory for saving the combined CSS file. */ private static $outputDirectory; /** * @var string|null Hash of the concatenated content of CSS files. */ private static $filesHash; /** * Initializes the output directory if it is not already set. */ private static function initOutputDirectory() { if (!isset(self::$outputDirectory)) { self::$outputDirectory = paths()->templates . 'styles/'; } } /** * Checks if a directory is writable. * * @param string $directory The directory path to check. * @return bool True if the directory is writable, false otherwise. */ private static function isWritableDirectory($directory) { // Check if the directory exists if (!is_dir($directory)) { return false; } // Check if the directory is writable if (!is_writable($directory)) { return false; } return true; } /** * Combines and minifies an array of CSS files. * * @param array $files An array of CSS file paths. * @return string The combined and minified CSS content. */ private static function combineAndMinifyCSS(array $files): string { $combinedCss = ''; foreach ($files as $file) { // Read the content of each CSS file $cssContent = file_get_contents($file); // Minify the CSS content (you can replace this with your preferred minification logic) $minifiedCss = self::minifyCSS($cssContent); // Append the minified CSS to the combined CSS content $combinedCss .= $minifiedCss; } return $combinedCss; } /** * Minifies CSS content. * * @param string $css The CSS content to be minified. * @return string The minified CSS content. */ private static function minifyCSS(string $css): string { // Replace this with your preferred CSS minification logic // Example: removing comments, extra whitespaces, etc. $minifiedCss = preg_replace('/\/\*.*?\*\//s', '', $css); $minifiedCss = preg_replace('/\s+/', ' ', $minifiedCss); return $minifiedCss; } /** * Generates a unique filename for the combined CSS file based on the provided file paths. * * @param array $files An array of CSS file paths. * @return string The generated combined CSS filename. */ private static function generateCombinedFilename(array $files): string { return md5(implode('', $files)) . '.css'; } /** * Saves the combined CSS content to a file. * * @param string $filename The filename for the combined CSS file. * @param string $content The combined CSS content to be saved. * * @throws RuntimeException If there is an error writing the file to disk. */ private static function saveToFile(string $filename, string $content): void { // Save the combined CSS content to the specified file $filePath = self::$outputDirectory . DIRECTORY_SEPARATOR . $filename; if (file_put_contents($filePath, $content) === false) { $error = error_get_last(); $errorMessage = isset($error['message']) ? $error['message'] : 'Unknown error'; throw new RuntimeException('Failed to write the combined CSS file to disk. Error: ' . $errorMessage); } } /** * Generates a link tag for the combined CSS file based on the provided file paths. * * @param array $files An array of CSS file paths. * @return string The link tag for the combined CSS file. * @throws RuntimeException If the output directory is not writable. */ public static function CSS(array $files): string { // Initialize the output directory self::initOutputDirectory(); // Ensure the output directory is writable if (!self::isWritableDirectory(self::$outputDirectory)) { throw new RuntimeException('The output directory is not writable.'); } // Combine and minify CSS files $combinedCss = self::combineAndMinifyCSS($files); // Check if the files have changed by comparing their hash $currentFilesHash = md5($combinedCss); $combinedFilename = ''; if ($currentFilesHash !== self::$filesHash) { // If the files have changed, generate a unique filename for the combined CSS file $combinedFilename = self::generateCombinedFilename($files); // Save the combined CSS content to a file self::saveToFile($combinedFilename, $combinedCss); // Update the files hash self::$filesHash = $currentFilesHash; } else { // If the files have not changed, check if the file exists on disk $combinedFilename = self::generateCombinedFilename($files); $filePath = self::$outputDirectory . DIRECTORY_SEPARATOR . $combinedFilename; if (!file_exists($filePath)) { // If the file doesn't exist, save the combined CSS content to a file self::saveToFile($combinedFilename, $combinedCss); } } // Return the link tag return '<link rel="stylesheet" href="' . urls()->templates . 'styles/' . $combinedFilename . '">'; } } Notes: Ensure that you have writing permissions for the $outputDirectory location. The combined CSS file will be generated and saved only if the files have changed or if the file doesn't exist on disk. The order of the combined file will adhere to the sequence specified in the array. Modify the hardcoded paths based on your requirements. Any suggestions for improvements are welcome! Cheers!
×
×
  • Create New...