Leaderboard
Popular Content
Showing content with the highest reputation on 06/27/2022 in all areas
-
This week core updates are focused on resolving issue reports. Nearly all of the 10 commits this week resolve one issue or another. Though all are minor, so I'm not bumping the version number just yet, as I'd like to get a little more in the core before bumping the version to 3.0.202. This week I've also continued development on this WP-to-PW site conversion. On this site hundreds of pages are used to represent certain types of vacations, each with a lot of details and fields. Several pages in the site let you list, search and filter these things. When rendering a list of these (which a lot of pages do), it might list 10, 20, 100 or more of them at once on a page (which is to say, there can be a few, or there can be a lot). Each of the items has a lot of markup, compiled from about a dozen fields in each list item. They are kind of expensive to render in terms of time, so caching comes to mind. These pages aren't good candidates for full-page caches (like ProCache, etc.) since they will typically be unique according to a user's query and filters. So using the $cache API var seems like an obvious choice (or MarkupCache). But I didn't really want to spawn a new DB query for each item (as there might be hundreds), plus I had a specific need for when the cache should be reset — I needed it to re-create the cache for each rendered item whenever the cache for it was older than the last modified date of the page it represents. There's a really simple way to do this and it makes a huge difference in performance (for this case at least). Here's a quick recipe for how to make this sort of rendering very fast. But first, let's take a look at the uncached version: // find items matching query $items = $pages->find('...'); // iterate and render each item foreach($items as $item) { echo " <!-- expensive to render markup here ---> <div class='item'> <a href='$item->url'>$item->title</a> ...and so on... </div> "; } That looks simple, but what you don't see is everything that goes in the <div class="item">...</div> which is a lot more than what you see here. (If we were to take the above code literally, just outputting url and title, then there would be little point in caching.) But within each .item element more than a dozen fields are being accessed and output, including images, repeatable items, etc. It takes some time to render. When there's 100 or more of them to render at once, it literally takes 5 seconds. But after adding caching to it, now the same thing takes under 100 milliseconds. Here's the same code as above, but hundreds of times faster, thanks to a little caching: // determine where we want to store cache files for each list item $cachePath = $config->paths->cache . 'my-list-items/'; if(!is_dir($cachePath)) wireMkdir($cachePath); // find items matching query $items = $pages->find('...'); // iterate and render each item foreach($items as $item) { $file = $cachePath . "$item->id.html"; // item's cache file if(file_exists($file) && filemtime($file) > $page->modified) { // cache is newer than page's last mod time, so use the cache echo file_get_contents($file); continue; } $out = " <!-- expensive to render markup here ---> <div class='item'> <a href='$item->url'>$item->title</a> ...and so on... </div> "; echo $out; // save item to cache file so we can use it next time file_put_contents($file, $out, LOCK_EX); } This is a kind of cache that rarely needs to be completely cleared because each item in the cache stays consistent with the modification time of the page it represents. But at least during development, we'll need to occasionally clear all of the items in the cache when we make changes to the markup used for each item. So it's good to have a simple option to clear the cache. In this case, I just have it display a "clear cache" link before or after the list, and it only appears to the superuser: if($user->isSuperuser()) { if($input->get('clear')) wireRmdir($cachePath, true); echo "<a href='./?clear=1'>Clear cache</a>"; } I found this simple cache solution to be very effective and efficient on this site. I'll probably add a file() method to the $cache API var that does the same thing. But as you can see, it's hardly necessary since this sort of cache can be implemented so easily on its own, just using plain PHP file functions. Give it a try sometime if you haven't already. Thanks for reading and have a great weekend!4 points
-
thx @szabesz Hi @Andy thx for your kind words! well... I like to do thinks in code rather than clicking around a GUI, because then I have all in GIT and can automatically deploy it to production. In addition to that I love how you can write form code once and get frontend and backend validation of your forms automatically. The next point is that I don't like the embed methods via Iframe and I never got used to the other output method - how is it called? Direct output? Another point is that I try to avoid hook hell as much as possible. Hooks are great, but I started to adopt concepts where things that belong together are in the same file or folder. That's why every form that I create for RockForms is one single PHP file, that defines all the necessary pieces (fields, after submit action like sending an email, markup for the frontend, error messages...). <?php namespace ProcessWire; /** @var RockForms $rockforms */ $form = $rockforms->form('newsletter', [ 'token' => false, // disable csrf for use with procache! ]); $form->setMarkup("field(email)", "<div class='sr-only'>{label}</div>{control}{errors}"); $form->getElementPrototype()->addClass('mb-12'); $form->addText("email", "E-Mail Adresse") ->setHtmlAttribute("class", "text-gray-dark w-full focus:border-violet focus:ring-violet") ->setHtmlAttribute("placeholder", "Ihre E-Mail Adresse") ->isRequired(); $form->addMarkup("<button type='submit' class='btn btn-sm btn-pink !px-12 mt-6'>Newsletter abonnieren</button>"); if($form->isSuccess()) { $values = $form->getValues(); if($form->once()) { /** @var RockMails $mails */ $mails = $this->wire('modules')->get('RockMails'); $mails->mail('newsletter') ->to('office@example.com') ->subject("New Newsletter Subscription") ->bodyHTML($form->dataTable()) ->send(); $this->log('sent mail'); } $form->success('<span style="color:black;">Thank you for your subscription</span>'); } return $form; This is an example for an easy newsletter subscription form. For me it is also better to code my own module because then I have a lot more freedom and can add extensions and new features while working on any project that uses the module. For example the $form->dataTable() is something I need very often (send form data via mail or show form data in the backend). I guess I'll release this as commercial module soon - if anybody reads this and is interested in a closed alpha write me a PM ?2 points
-
I looked at my HTML output today and all this chaotic whitespace triggered my OCD. This module simply hooks into Page::render and removes whitespaces from frontend HTML with a simple regex replace. I mostly put this together for cosmetics. I like my View source neat and tidy. This is not well tested yet, please open an issue if you run into problems. GitHub: https://github.com/timohausmann/MinifyPageRender1 point
-
This is my first post so, Hello everyone. Ryan, I want to congratulate a great CMS. I attach my translation for the Polish language. The translation is fairly complete, but it requires some adjustments. Hidden deeper setting are not yet translated. And also the Polish translation for TinyMCE. TinyMCE will not work without it. https://github.com/PawelGIX/ProcessWire-Language-Pack-Polish--pl-PL- EDIT: I added a link to github. ProcessWire-Language-Polish.zip tinymce_language_Polish.zip1 point
-
Thanks! Updates works ok.1 point
-
Great idea @Anonimas I have integrated both. You can change/customize the text for the required fields with setRequiredText() method. $form->setRequiredText('This is my customized text, that you have to fill out all required fields.') I have also added EOT for better readability. Please download and install the module once more. Thanks for your ideas.1 point
-
With adding ".PHP_EOL;" after every field on $form->render() would help reading page source easier, because now all form is one long line.1 point
-
Hi, I'm using https://html5up.net/story template and want to use unmodified css file. It wraps all form fields for inline fields alignment (name and email in example) and css use "form > .fields > .field ". If Div with class "fields" is outside form, then it don't works. I tested with new version and it works! Thank you for update! Also it would be useful to add something like setRequiredText(), because now I disabled default and added my text manually before form. Something like setErrorMsg() and setSuccessMsg().1 point
-
Hi @siaweb, Do visitors have access to the pages of the "assortment_name.home_menu=1" selector? Did you try with include=all in the find()? Looks like an issue of access control: https://processwire.com/docs/selectors/#access_control I personally think it would be better to resolve the access of the pages instead of using include=all, but you do you ?1 point
-
Hi Anonimas Inspired by your question I have added a new method to add the wrapper div over all form fields (including hidden fields too). $form->setFormElementsWrapper()->setAttribute('class', 'mycustomclass'); By default a unique CSS id will be added, but as you can see in the example above, you can also add a class attribute (or other attributes) too. <form> <div id="formelementswrapper" class="mycustomclass"> .... </div> </form> To use this method, please download version 2.0.9 from GitHub. Alternatively replace the Form.php. This file includes all the changes and additions. BTW could you please tell me, what is the intention for you to wrap all form fields in an extra div? Best regards Jürgen1 point
-
1 point
-
I just released a new extension module AppApiPage (waits for approval), which handles the initial steps from my post above completely automatic. You install AppApi and AppApiPage. That makes the /api/page route available and you only have to add the code on top of your template php to add a custom JSON output to your pages. <?php // Check if AppApi is available: if (wire('modules')->isInstalled('AppApi')) { $module = $this->wire('modules')->get('AppApi'); // Check if page was called via AppApi if($module->isApiCall()){ // Output id & name of current page $output = [ 'id' => wire('page')->id, 'name' => wire('page')->name ]; // sendResponse will automatically convert $output to a JSON-string: AppApi::sendResponse(200, $output); } } // Here continue with your HTML-output logic... I hope that this makes it even simpler to add a full-blown JSON api to new and existing pages.1 point
-
Hello all! I guess I'm a little late to the party, but here's an explanation to my AppApi approach. I basically wanted to achieve exactly the same thing with my projects as well: A universal JSON api that I make all public ProcessWire pages also queryable as JSON. For this I use the combination of my Twack module and AppApi. Twack is a component system that allows me to get a JSON output from each ProcessWire page in addition to the standard HTML output. Twack registers a route that allows to query each Page from the PageTree (via ID or Path). But for this route you don't need the Twack module, of course. As here it would work only with the AppApi module: routes.php: <?php namespace ProcessWire; require_once wire('config')->paths->AppApi . 'vendor/autoload.php'; $routes = [ 'page' => [ ['OPTIONS', '{id:\d+}', ['GET', 'POST', 'UPDATE', 'DELETE']], ['OPTIONS', '{path:.+}', ['GET', 'POST', 'UPDATE', 'DELETE']], ['OPTIONS', '', ['GET', 'POST', 'UPDATE', 'DELETE']], ['GET', '{id:\d+}', PageApiAccess::class, 'pageIDRequest'], ['GET', '{path:.+}', PageApiAccess::class, 'pagePathRequest'], ['GET', '', PageApiAccess::class, 'dashboardRequest'], ['POST', '{id:\d+}', PageApiAccess::class, 'pageIDRequest'], ['POST', '{path:.+}', PageApiAccess::class, 'pagePathRequest'], ['POST', '', PageApiAccess::class, 'dashboardRequest'], ['UPDATE', '{id:\d+}', PageApiAccess::class, 'pageIDRequest'], ['UPDATE', '{path:.+}', PageApiAccess::class, 'pagePathRequest'], ['UPDATE', '', PageApiAccess::class, 'dashboardRequest'], ['DELETE', '{id:\d+}', PageApiAccess::class, 'pageIDRequest'], ['DELETE', '{path:.+}', PageApiAccess::class, 'pagePathRequest'], ['DELETE', '', PageApiAccess::class, 'dashboardRequest'] ] ]; You can use this class to get the page-outputs: <?php namespace ProcessWire; class PageApiAccess { public static function pageIDRequest($data) { $data = AppApiHelper::checkAndSanitizeRequiredParameters($data, ['id|int']); $page = wire('pages')->get('id=' . $data->id); return self::pageRequest($page); } public static function dashboardRequest() { $page = wire('pages')->get('/'); return self::pageRequest($page); } public static function pagePathRequest($data) { $data = AppApiHelper::checkAndSanitizeRequiredParameters($data, ['path|pagePathName']); $page = wire('pages')->get('/' . $data->path); return self::pageRequest($page); } protected static function pageRequest(Page $page) { $lang = SELF::getLanguageCode(wire('input')->get->pageName('lang')); if (!empty($lang) && wire('languages')->get($lang)) { wire('user')->language = wire('languages')->get($lang); } else { wire('user')->language = wire('languages')->getDefault(); } if (!$page->viewable()) { throw new ForbiddenException(); } return $page->render(); } private static function getLanguageCode($key) { $languageCodes = [ 'de' => 'german', 'en' => 'english' ]; $code = '' . strtolower($key); if (!empty($languageCodes[$key])) { $code = $languageCodes[$key]; } return $code; } } On top of your ProcessWire-template file you have to check if an api-call is active. If so, output JSON instead of HTML: <?php // Check if AppApi is available: if (wire('modules')->isInstalled('AppApi')) { $module = $this->wire('modules')->get('AppApi'); // Check if page was called via AppApi if($module->isApiCall()){ // Output id & name of current page $output = [ 'id' => wire('page')->id, 'name' => wire('page')->name ]; // sendResponse will automatically convert $output to a JSON-string: AppApi::sendResponse(200, $output); } } // Here continue with your HTML-output logic... That should be everything necessary to enable your ProcessWire-templates to output JSON. You will have an /api/page/ endpoint, which can be called to get the JSON-outputs. /api/page/test/my-page -> ProcessWire page 'my-page' in your page-tree under test/ /api/page/42 -> ProcessWire page with id 42 /api/page/ -> root-page And a small addition: If you want to query the page files also via api, have a look at my AppApiFile module. With it you can query all images via an /api/file/ interface.1 point
-
I still don't get what you are looking for. I thought I understand your need: Making the process more standardized and easier for non-devs meaning more click click instead of writing code. That's what I tried to solve with my proof of concept module. But you said "that looks great" in your first post and then turned around and it does not seem to be what you are wanting... Eh nothing, all is ready, we have even more with JWT implementation. It's just missing a function which return the pages tree. Something like less than 10 lines of code. This question was targeted to @wbmnfktr to better understand him. When we where talking about easy json feed the very first time my answer was: Url hook + findRaw + json_encode... ( https://processwire.com/talk/topic/19112-module-page-query-boss/?do=findComment&comment=221874 ) I don't agree on this one. I have not tried the module (because I have not had the need), but it looks complicated to me. Something that definitely would take longer than 2 min even just to understand how to set it up. That's why I built my module - super simple to setup, no code necessary. But it is still not what @wbmnfktr is looking for, so I'm confused and it feels like we are going in circles...1 point
-
Same here. I feel like we have everything and try to understand what is missing and what others to differently (better)...1 point
-
Could you please explain how this would look like? E.g you visit /mysite/admin/rest/ ? and that responds with JSON? My feeling is that we already have the tools to do this. Just trying to think of the practical implementation.1 point
-
I'm still curious how that "out of the box" would look like and how other platforms are working in that regard... As a detailed step by step use case.1 point
-
Is SSG not mostly being used by coders, git-users, documentation, etc. ? Is SSG really something to use for a client website ?1 point
-
1 point
-
If you are interested I can take the time to publish a draft of a profile which you could use as starting point, and IMO it's better and easily than other solution that I could have tested. Basically it use a modified version of InertiaAdapter made by @clsource and let you code your app inside ProcessWire's template and using pages for everything fetched dynamicaly and internally, as JSON. You can see it "in action" in this example url: https://blog.sekretservices.com/ (Do not take care of the issue while refreshing the blog post due to hanna codes, it's an old version) For example, from your template you build the page properties as a simple PHP array, let's say I want to fetch the title of the page: $component = "Home/BlogPost"; $properties = [ // accessible from app components with $page.props 'name' => $page->name, 'title' => $page->title, 'subtitle' => $page->subtitle, 'content' => $page->body, 'author' => ucFirst($page->createdUser->fullname[0])."."." ".ucFirst($page->createdUser->surname), 'date_created' => $page->created, 'date_relative' => $datetime->relativeTimeStr($page->created) ]; Then from a component BlogPost.`{svelte, vue, react}` (or whatever) you can simply retrieve the page's props like: (Svelte example) <script> import {inertia, page} from '@inertiajs/inertia-svelte'; export const { title:title, body:body } = $page.props </script> <div class="blog-post"> <h1 class="font-sans mt-8 text-center font-bold">{@html title}</h1> <div>{@html body}</div> </div> Look at this sample, at the page's tree and the routes prop from the console : About the workflow, it's not really different from what you should be used to. You write your logic in template in PHP then write some HTML/JS in your component. Also, hot module replacement (HMR) is working ? A word about server side rendering (called SSR and which is needed to get best SEO results), it's working for React and VUE, still a WIP for Svelte ? Get a note of ?on every lighthouse performance. Bundled with ViteJS, powered by ProcessWire, I am glad to say that it's the best stack I have ever built and used ?? Edit: A small note, but the better one. You don't have to build your app again on every change on the backend, I mean for example when you add a new page, it's work out-of-the-box, you create and page, and it's available to your JS components. Edit2: To understand better, here we can achieve what you already used/saw with Laravel. Having a `src` folder with the JS things, another folder with the Laravel `app` and running artisan and some npm command. Here, in dev mode, you run `yarn dev` and you are ready. Simple as that.1 point
-
As far as I understand, Headless CMS and SSG aren't the same thing really. So which one do you really want? If you'd like to use an SPA framework like React, you'd probably want headless. If you'd like to automagically create static pages, it's a typical SSG setup. For headless, I'd try the AppApi module. For SSG, I'd use Pro Cache. I'm not sure what to make of your example comparison with WP and taxonomies. In PW, you create your own taxonomy or data model. It's easy to create JSON output from your PW instance. Use URL segments, and handle each /json request to your own script. It's ridiculously easy to create JSON feeds (just a bunch of URLs that match your selector). Is that all you really need? Sorry if I misunderstand you, but there's way more to a headless CMS than just having a URL feed. Again, I don't get what you really want in the end. If you need static sites, what good are "JSON feeds" for you?1 point
-
For anyone interested in how you might do this by modifying the SQL query that's created from a PW selector, here is one way: In your template file or wherever: // A field name is supplied in a custom "empty_first" item in the $options array // The field name supplied here must also be used as a sort value in the selector string $items = $pages->find('template=news_item, sort=-date_1, limit=20', ['empty_first' => 'date_1']); In /site/ready.php: $wire->addHookAfter('PageFinder::getQuery', function(HookEvent $event) { $options = $event->arguments(1); // Return early if there is no "empty_first" item in the $options array if(!isset($options['empty_first'])) return; // Get the DatabaseQuerySelect object /** @var DatabaseQuerySelect $dqs */ $dqs = $event->return; // Prepend a custom ORDER BY $dqs->orderBy("ISNULL(_sort_{$options['empty_first']}.data) DESC", true); // Return the modified query $event->return = $dqs; });1 point
-
Already done The fields are there for really simple tables and the files for more complex ones. The module will include all files related to a field automatically (PHP, js and CSS).1 point
-
did a total rewrite of the module and will release it the next weeks when i'm done1 point