Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation since 06/20/2022 in all areas

  1. 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!
    27 points
  2. @joe_g No, you can specify this for each template if you want to. Edit a template and see the "files" tab. Or you could just use your _main.php as a controller that loads different layout files depending on some field value or condition that you determine. Sure you can. Use PHP include(), $files->include() or $files->render() with whatever files or order you want. Regions can be replaced, appended, prepended, removed, etc. as many times as they are output. You can do it individually for templates, see my response for #1. Though I usually structure my templates in a way that the _main.php is the "base" that's generic enough to not have ever needed to define alternate files. I think markup regions are the best way myself. Once you start using them a lot, I suspect you'll find you don't want to use anything else. That's been my experience at least. I don't know exactly what you mean by nested regions. You'd have to provide more details on what you are doing before we could say if you are using something in an intended way or not.
    5 points
  3. 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/MinifyPageRender
    4 points
  4. This week I've been busy developing a site. It's the same one as the last few weeks, which is an established site moving out of WordPress and into ProcessWire. Next week the whole thing is getting uploaded to its final server for collaboration and for client preview. I've been pretty focused on getting that ready and don't have any core updates to report this week, though should next week. One thing about the prior version of this site (and perhaps many WordPress sites) is that there wasn't much structure to the pages used in the site, and hundreds of unrelated pages in the site confusingly live off the root, such as /some-product/ and /some-press-release/ and /some-other-thing/. There's no apparent structure or order to it. And those pages that do have some loose structure in the wp-admin have URLs that don't represent that structure on the front-end. There's very little relation between the structure one sees in the wp-admin and the structure that one sees in the URLs, or in the front-end navigation. They all seem to be completely unrelated. That's one thing that I've tried to fix, so that there is some logic and structure rather than having a bunch of unrelated pages all in the same bucket (is this common in WordPress?) But there's one big caveat. We didn't want to change anything about the actual URLs that are used on the site. This is a site with a long history, a lot of incoming links, and a lot of search traffic. The current URLs have been in place a long time and we didn't want to introduce more redirects into the site (there are already a ton of 301 redirects accumulated over time). So we wanted to make sure the existing URLs in the new ProcessWire-powered site are identical to what they were in the WordPress site. That might seem difficult to do, going from an unstructured WordPress site into a highly structured ProcessWire site... but actually it's very simple. Here's how: Making ProcessWire render pages from WordPress URLs We created a new URL field named "href" and added it to most of the site's templates. For established pages that came in from the old WP site, this "href" field contains the WordPress URL for the page. Depending on the page, it contains values like /some-product/ or /some-press-release/, /some-country/some-town/, etc. In most cases this is different from the actual ProcessWire URL, since the page structure is now much more orderly in the back-end. And for newly added pages, we'll be using the more logical ProcessWire URL. But for all the old WordPress pages, we'll make them render from their original URL. This is certainly preferable from an SEO standpoint but also helps to limit the redirects in the site. In order to make $page->url return the old/original WordPress URL (value in $page->href), a hook was added to the /site/init.php file: /** * Update page paths and URLs to use the 'href' field value on pages that have it * */ $wire->addHookBefore('Page::path', function(HookEvent $event) { $page = $event->object; /** @var Page $page */ $href = $page->get('href'); if(!$href) return; // skip if page has no 'href' value $event->return = $href; $event->replace = true; }); Now we've got $page->url calls returning the URLs we want, but how do we get ProcessWire to accept those URLs for rendering pages? The first thing we'll need to do is enable URL segments for the homepage template. We do this by going to: Setup > Templates > home > URLs > and check the box to enable URL segments. Save. Then we need to edit our /site/templates/home.php to identify when URL segments are present and render the appropriate page, rather than the homepage: $href = $input->urlSegmentStr; if($href) { $href = '/' . trim($href, '/') . '/'; $page = $pages->get("href=$href"); if(!$page->id || !$page->viewable()) wire404(); $wire->wire('page', $page); // set new $page API var include($page->template->filename); // include page's template file } else { // render homepage output } As you can see from the above, when URL segments are present, we find a page that has an "href" field value matching those URL segments ($input->urlSegmentStr). If we don't find one, we stop with a 404. But if we do find one, then we set the $page API variable to it and then include its template file, making that page render rather than the homepage. If there is no $input->urlSegmentStr present then of course we just render the homepage. That's it! By using these little bits of code snippets to replace ProcessWire's URL routing, now all the URLs of the old WordPress site are handled by ProcessWire. Like most things in ProcessWire, there's more than one way to accomplish this… We could have used URL/path hooks, or we could have hooked before Page::render to take over homepage requests with URL segments, before the homepage even got involved. Or perhaps we could have hooked in even earlier, to something in the ProcessPageView module or PagesRequest class. Or we could have used an existing module. Any of these might be equally good (or even better) solutions, but I just went with what seemed like the simplest route, one that I could easily see and adjust. Plus, it'll work in any version of ProcessWire. The actual solution I used is a little more than what's presented here, as it has a few fallbacks for finding pages and scanning redirect lists, plus passes along remaining pagination/URL segments to rendered pages. I'm guessing most don't need that stuff, and it adds a decent chunk of code, so I left that out. But there are a couple of optional additions that I would recommend adding in a lot of cases: Forcing pages to only render from their "href" URL and not their ProcessWire URL (optional) Our existing hooks ensure that any URLs output for pages having "href" values are always based on that "href" value. But what if someone accesses a given page at its ProcessWire path/url? The page would render normally. We might want to prevent that from happening, ensuring that it only ever renders at its defined "href" URL instead. If the page is requested at its ProcessWire URL, then we redirect to its "href" URL. We can do that by adding the following code to our /site/templates/_init.php file: if($page->id > 1) { // ensure that we are rendering from the 'href' URL // rather than native PW url, when href is populated $href = $page->get('href'); $requestUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; if($href && $requestUrl && strpos($requestUrl, $href) === false) { // href value is not present in request URL, so redirect to it $session->redirect($config->urls->root . ltrim($href, '/')); } } Enforcing uniqueness and slashes in "href" values (optional) It's worthwhile to enforce our "href" values being consistent in having both a leading and trailing slash, as well as making sure they are always unique, so no two pages can have the same "href" value. To do that, I added this hook to my /site/ready.php file (/site/init.php would also work): /** * Ensure that any saved 'href' values are unique and have leading/trailing slashes * */ $wire->addHookAfter('Pages::savePageOrFieldReady', function(HookEvent $event) { $page = $event->arguments(0); /** @var Page $page */ $fieldName = $event->arguments(1); /** @var string $fieldName */ if($fieldName === 'href' || in_array('href', $page->getChanges())) { // the href field is being saved $href = $page->get('href'); if(!strlen($href)) return; // make sure value has leading and trailing slashes if(strpos($href, '/') === 0 && substr($href, -1) === '/') { // already has slashes } else { $href = '/' . trim($href, '/') . '/'; $page->set('href', $href); } // make sure that the 'href' value is unique $pages = $event->object; /** @var Pages $pages */ $p = $pages->get("id!=$page->id, href=$href"); if($p->id && !$p->isTrash()) { $page->set('href', ''); $page->error( "Path of '$href' is already in use by page $p->id “{$p->title}” - " . "Please enter a different “href” path and save again" ); } } }); That's all for this week. Thanks for reading, have a great weekend!
    4 points
  5. I created a simple translation module for a project we're currently working on and I thought I would share it in case it's useful for anyone else as it's a request we've come across a few times recently. It's a basic page translation module using Google Translate to replace their now retired embeddable page translation widget. The module outputs a simple, un-styled, dropdown language selector that routes the visitor to a translated version of the current page via translate.google.com. From there you can then navigate the whole site in the your language of choice. There's also a method to output a single language-specific translation URL for any supported language using the language ISO code. Download, usage and supported language list available on my Github here: https://github.com/mrjcgoodwin/MarkupGoogleTranslate Caveat 1: Google seem to be trying to encourage use of their Google Translation API, so the continued working of this module is completely at their mercy - I can imagine they may discourage this usage at some point, but who knows! Caveat 2: I haven't yet deciphered what all the parameters are used for in the constructed Google Translate URL. It's possible there may be some sites/scenarios where the URL doesn't work. Feel free to report if you find any issues.
    4 points
  6. @bernhard Admittedly, I had never really paid attention to php://input, so this thread prompted me to google it. As I understand it, that stream always contains the raw request body, so anything could be in there, however only two content-types end up in $_POST and thus also in PW’s $input->post: application/x-www-form-urlencoded and multipart/form-data. For these, in addition to sorting everything into an associative array, PHP also takes care of unescaping the URL encoding. When OP wants to send Json, I’m assuming he quite sensibly declares application/json. PHP doesn’t know this content-type (plus, the whole thing is a nameless Json object, so what would be the key for $_POST’s associative array?), so nothing happens automagically. This answer on SO gives a lengthy overview on how to handle php://input. If you’re asking about the client-side fetch() API, I have to confess I didn’t test the code I posted, but the idea is to create a request with the content-types application/x-www-form-urlencoded or multipart/form-data mentioned above. Indeed, for some reason I thought it would be necessary to specify the former content-type, but it’s actually set automatically when URLSearchParams are used for the request body (at least in Firefox). Likewise, when the body is of type FormData, the default content-type will be multipart/form-data. As for when to send a body, it’s just the HTTP request’s content, so I guess whenever you want to transmit data to the server, i.e. usually with POST (or PUT or something). When dealing with third party APIs you gotta do the weird things they want you to, of course. If it’s your own PHP/ProcessWire site you send requests to, I’d say it doesn’t really matter whether it’s application/x-www-form-urlencoded or multipart/form-data (or URLSearchParams or FormData for that matter), since both deliver keys and values that end up in $_POST/$input->post. In this case I would just use whatever is more convenient to assemble in JS, i. e. if the data actually comes from a HTML form, you can pass the form element to FormData’s constructor and you’re done. Sorry if I’m not telling you anything new here 😅 Trial and error makes up a large chunk of my modus operandi as well m)
    3 points
  7. @bernhard I use redis alongside PW on some client sites (using php-resque for background jobs like email composition and send) - though I do not use this particular module in production yet. I think bitpoet is aiming for an API-compatible drop-in for wirecache that leverages the lower latency of redis' in-memory accesses.
    3 points
  8. Hi @Ivan Gretsky, I will at one point for sure, but that will be late in the year. I‘m currently hiking across the U.S. and won‘t get my hands on a computer until October.
    3 points
  9. I am still working on this one. It works great and is quite flexible now. I made a couple of improvements. Now the data to style the items is stored directly on the item with $page->meta(). This way, cloning pages and page reference fields work out of the box. Also there is no need for hidden fields anymore. The style panel now supports adding custom classes and assigning styles to them. These classes can be used globally on all pages (a css class is also a page). The style panel now supports selecting html tags to style tags globally across the whole site. I did a lot of refactoring to make the code cleaner and more efficient. There are still things where I am looking for feedback before releasing an open beta for this module: Sharing blocks and migrating pages I played with the core ProcessPagesExportImport and the migrator module and it seems that it's not exporting the meta() data, which is a bummer. So for now I might not be able to have a migration feature out of the box. Maybe someone else can take a look at this, but it seems to be too much hassle for me now. Importing/exporting the whole database with ProcessDatabaseBackups works as expected though. Also I did develop some block modules that you can install optionally, these will create the needed templates and fields for that block and give you a quick way to start working with PageGrid (without the need to create your own templates or install a site profile), more on that later. (Item pages are based on a block template). Structure of PageGrid items/pages I decided to always store the items under the admin page in a seperate parent, instead of making the parent customisable (like PageTable is doing). So there is a clear separation between pages for your site and item pages. PageGrid then creates a structure looking like this (makes it easy to access the pages): Admin – PageGrid Items – Home items (name: for-1213) – Item Editor – Item XY – Another Page items (name: for-1315) – Item Group – Item XY – Item XY – Item Editor The structure of these pages is an exact representation of the PageGrid items/pages (sorting, child/parent relation, hidden/published). While working with PageGrid via the UI this is not so important, but I think it will make interacting with the item pages via API more flexible and clean. Adding a page to any of the item pages via API or the backend also adds the page to PageGrid. If you delete a page containing a PageGrid field, the items also get deleted. You can also sort via API (or manually under the Admin page) and the changes are reflected for the items as well. The connection is via a name like for-1213, where 1213 is the ID of the page holding the PageGrid field (inspired by repeaters). I like this setup because it gives you a lot of options to access items via API and keeps the pagetree clean. Also it will make it easier to handle migrations for pages containing a PageGrid field in the future (just import/export the page and the item page container). One downside, though, is that the methods of PageTable like $page->pt->add($newpage) or $page->pt->remove($newpage) are no longer working (PageTable is using a PageArray that is saved as the field value). Also this will not work with multiple PagGrid fields on one page (A PageGrid field can however have a page reference field to another page containing a PageGrid field, as well as having nested pages, so I see no benefit of supporting multiple fields). You can add a page to the item parent like this: // Add an PageGrid item via API $itemParent = $pages->get("for-$page->id"); $itemParent->add($newpage) I am not decided if my approach is better or if I should just stick with the PageTable methods (keeping the items stored as PageArray inside the field). Since PageTable can only have one parent that you select in the field settings, all items will than be stored under the same parent, which is a bit messy. Not sure if any of that makes sense to you 🙂 Any feedback is welcome. More info and website is coming soon!
    3 points
  10. 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
  11. https://www.autohaus-bendel.at/ Highlights/Features: New Logo and matching Backend using module AdminStyleRock Very good google- and performance-ratings thx to UIkit and ProCache Flexible content pages (using private module RockMatrix) Cars are fetched automatically from an austrian cardealer API and converted into ProcessWire pages with a custom filter page: https://www.autohaus-bendel.at/fahrzeuge/ Forms with honeypots and live validation (using private module RockForms based on nette forms) Web-Coach The client gets automated reminders if he does not add new content to the website. Thx @wbmnfktr Bendel Web Coach The last news entry was created 21 days ago. There must be something new that will interest your clients ;) > To the website > Create news entry Animated page transitions using barba.js Migrations and deployment using RockMigrations. Debugging using TracyDebugger. 😎
    2 points
  12. Hi @alexm, Yes, the method moved. It is in the docs here. However, currently something has messed up the JavaScript in the docs so the content is not loading. Here's the code from that example: <?php namespace ProcessWire; /** @var WireArray $orderLineItems */ $orderLineItems = $padloper->getOrderLineItems(); /** @var array $orderTaxTotals */ $orderTaxTotals = $padloper->getOrderTaxTotals($orderLineItems); foreach ($orderTaxTotals as $taxShortName => $taxValue) { echo "<span>{$taxShortName}: </span><span>" . $padloper->renderCartPriceAndCurrency($taxValue) . "</span><br>"; } Exactly.
    2 points
  13. I've got a scenario where I use ProcessWire as a CRM for a client and they have several companies using this now, so it's important to be able to update things easily across all installations. A tricky one recently was working out how to manage translations via API to avoid going into several installations, enabling language support and translating the file manually (in this case they wanted to tweak a message in LRP's confirmation routine). On your master installation, do your translation as normal Export to CSV Upload the CSV file somewhere where your updater code can reference it - I created a folder in site/assets called "uploads" for some CRM-specific stuff, so I added another folder under there for ' Use code similar to the below in your updater script on other installations to first create the JSON file to be translated (it may not already exist) and then import your CSV file and tidy up afterwards - something like the below: <?php $this->modules->refresh(); // I've found this helps first, mostly for modules that have just been pushed to the Git repo but doesn't hurt to do it every time $this->modules->install('LanguageSupport'); // This wasn't yet installed $languages = $this->wire()->languages; $default = $languages->get('default'); // We only use the default language as it's only for the UK market $default->of(false); // It will complain without outputformatting switched off under some scenarios $translator = new LanguageTranslator($default); $translator->addFileToTranslate('site/modules/LoginRegisterPro/LoginRegisterProConfirm.php'); // This generates the JSON file that you would see in PW on the translations page $languages->importTranslationsFile($default, $this->config->paths->assets . 'uploads/imports/default-site.csv'); // This imports the CSV we exported from our master installation unlink($this->config->paths->assets . 'uploads/imports/default-site.csv'); // And we don't need the file afterwards I appreciate it's not a particularly in-depth tutorial, but may help someone out there with a similar need. Our CRM installations are all on an Amazon EC2 instance under separate subdomains, so separate databases too and there's a shell script I use to iterate all the installation folders, get the latest updates from the Github repo and then run an updater script that will do more complex updates like the above code or perhaps alter tables etc. This may get trickier in future if we shift to using something like Docker, or that may make life easier, but at the moment this is all nicely manageable the way it is.
    2 points
  14. I think you want something like this: // Get all the tags that are used in this client's videos $tags_in_use = new PageArray(); foreach($client->videos as $video) { $tags_in_use->add($video->tags); } // Sort the tags if you like $tags_in_use->sort('title'); // Output the used tags in the sidebar foreach($tags_in_use->sort('title') as $tag) { // ... }
    2 points
  15. PolishWire - very nice 😂👍
    2 points
  16. Well, you can just send your POST request in such a way that the Json will be available as $input->post['myData'], that is, as FormData or UrlEncoded: fetch('https://example.com', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' }, body: new URLSearchParams({ 'myData': JSON.stringify(someObject) }) }); or const formData = new FormData(); formData.append('myData', JSON.stringify(someObject)); fetch('https://example.com', { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, body: formData }); If your object is just a simple key-value affair, you may actually want to send it that way and not as Json at all, because you’ll be able to use the sanitizer methods for each value. With the Json you have to parse it and then sanitize the properties, watch out for properties you didn’t want etc.
    2 points
  17. Just keeping this thread up to date in case anyone is searching for how to do this still (and I know I'll forget so it will probably be me), the ProcessPageList module now has a configuration option to let you hide pages from users:
    2 points
  18. Hi Inxentas, You need to get InputfieldCKEditor instead of InputfieldTextarea. $field = wire('modules')->get('InputfieldCKEditor'); $field->columnWidth = 100; $field->attr("id+name", "source_text"); $field->label = "Source Text"; $field->icon = "fa-align-left"; $field->value = "<h1>Yeah HTML!</h1>";
    2 points
  19. Hey and thanks for the interest. I am head of community at CrowdSec and I had never heard about ProcessWire before this, so thanks for pointing me to it. I haven't heard of anyone working on a bouncer (or parsers or scenarios) for ProcessWire. I won't rule out that anyone is, but I doubt it. Could I ask what motivates you to specifically ask for a bouncer? I would say that it probably also would make sense to have a parser that's able to parse PW logs and a scenario able to detect attacks such at bruteforce access to the PW backend (assuming that there is one) for instance. Both the Wordpress and Magento bouncer is really a specialized version of the generic PHP bouncer. Here's an article on how to use it with Drupal but in reality it works with anything PHP. If more people are interested in PW support it would be a great idea to join our Discord and talk about it there. I hope this was to any help. If I can do more please let me know.
    2 points
  20. Hey @Pete thx for sharing. Have you seen my new language module? For me it sounds like it could help you so I'd be happy to hear your thoughts...
    2 points
  21. 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
  22. Hi there, I just discovered markup regions while looking for some templating solution. I've got a couple of layouts that are shared across a bunch of regular PW templates, and I'm looking for a way to abstract this, so that i don't need to have the same code duplicated across multiple places. I would like to keep the HTML "direct", so rather <div class="<?=$somePhp?>"> than, say echo "<div class=\'{$somePhp\}"> I think that rules out all regular delayed output strategies. Markup regions seemed perfect on the first look, but there are some limitations, if I understand it right 1. There is only one global markup region file (ie. _main). I can't use an arbitrary template file for some specific situation (like _myArticleLayout, for example) 2. I cant inherit templates, or chain, or stack them, or similar. I can't apply first _main, and then another one _article, for example 3. Related to this, I can only do appendTemplateFile globally in config.php, but I was hoping I can do this in the top of each template, or maybe even for parts of templates Now, some questions. Is there a better way to do this? Another templating engine? Is there a way I can use the magnificent markup region replacement engine myself, separately? Or did I simply misunderstood how this works? As a sidenote it seems I can't get nested regions to work, perhaps because I'm not using this the intended way? many thanks
    1 point
  23. Oh nice - wish I'd looked harder before doing it my own way as that sounds ideal 😄
    1 point
  24. Theoretically, yes. However, you would need to be aware of any laws similar to the EU Digital Goods regulations. Not closely related, but I forgot to mention (and you are probably aware of this) that we also have tax overrides that can be applied per category of products or shipping. Glad you got it sorted.
    1 point
  25. That's the correct syntax, because you do not set a ProcessWire $config property but a server locale setting.
    1 point
  26. Thanks for the info! I cannot answer that but a search engine that does not even list processwire.com on the first page is weird: https://yep.com/web?q=ProcessWire for this query, I get "processwire.com/blog/posts/pw-login-for-facebook/ " as the 19th item in the search result list, and that is the closest thing to finding "processwire.com". Sure, "github.com/processwire/processwire" on the top of the list but still...
    1 point
  27. 1 point
  28. Sure 🙂 <html> <head> <?php echo $rockfrontend->scripts('head') ->add('/wire/modules/AdminTheme/AdminThemeUikit/uikit/dist/js/uikit.min.js') ->add('//unpkg.com/alpinejs', 'defer') ->addIf('/path/to/debug.js', $config->debug) ->render(); echo $rockfrontend->styles('head') ->add('/wire/modules/AdminTheme/AdminThemeUikit/uikit/src/less/uikit.theme.less') // add all styles (CSS/LESS) inside /site/template/sections ->addAll('sections') ->render(); ?> </head> <body> <?= $rockfrontend->render("sections/header.latte") ?> <?= $rockfrontend->render("sections/main.latte") ?> <?= $rockfrontend->render("sections/footer.latte") ?> </body> </html> I've used that module (RockFrontend) for several projects now and I'll release it soon. If you or anybody else wants to try it out and give me some feedback write me a PM!
    1 point
  29. We recently launched DOMiD Labs, a new microsite for our existing client DOMiD. We already built the main site for DOMiD in 2019, as shown in our previous post here and featured in ProcessWire Weekly #296. The new DOMiD Labs site belongs to a project to plan participative museum concepts for the upcoming migration museum in Cologne, Germany. Concept, design and implementation by schwarzdesign - web agency in Cologne, Germany. We're using an in-house starter project to bootstrap new ProcessWire projects. This allows us to deliver smaller projects on a low budget while still providing an extensive, modular content system and to spend some time polishing the features our clients care about. Notable features of the site include: A modular content builder utilizing the excellent Repeater Matrix field. A blog area with a paginated news index page (not yet online). Multi-language support (English site yet to follow). Q&A sections using accordion elements. A contact form built with the Form Builder module. Fully configurable navigation and footer components. Custom translation management for interface translations. Learn more: domidlabs.de
    1 point
  30. To make a POST request using the Fetch API, you need to pass the 'method: POST' to the fetch() method as the second parameter: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch | https://reqbin.com/code/javascript/ricgaie0/javascript-fetch-api-example fetch('https://domain.com/api', { method: 'POST' }) .then(resp => resp.json()) .then(json => console.log(json))
    1 point
  31. As I've never used redis I had to do some research what it is and what it does... Here's a good video that gives you a quick overview: Would be interesting to hear why/where/when one could use this module and what benefit one would get vs. using WireCache 🙂
    1 point
  32. Any reason why you installed the latest version on top of the older version instead of overwriting the files in the original install?
    1 point
  33. This is a game changer already. Thank you for your hard work as always!!! Going to check this out. It's like Christmas in summer!
    1 point
  34. @kongondo awesome news!! Apologies for the slow reply!! Excited to test this out 😁
    1 point
  35. There are already good guides on the internet on how to update ProcessWire! Look here: How to upgrade ProcessWire to the latest version or here ProcessWire Upgrade (ProcessWireUpgrade) - ProcessWire Module
    1 point
  36. 1 point
  37. Hi Simi, Can you explain how to do this? Thank you in advance!
    1 point
  38. I had a look at it and it wasn‘t that complicated to implement. I have now a version of the ProcessPagesExportImport module that support export/import of $page->meta(): https://github.com/jploch/ProcessPagesExportImport would be nice to have this in the core
    1 point
  39. Data is fetched via cron every night and then PW pages are created so that the client can enrich those pages with data that is not part of the API but is helpful on the website (eg a PDF sheet for the car or the point of contact): API-Fields are read-only. If a car is created by the cron and the point of contact is empty, the client gets a link to directly edit this page and select the POC 🙂 If cars are sold, the cron automatically trashes those pages on the website. Ah... I forgot a nice detail! They have a custom branded PDF viewer:
    1 point
  40. It turned 25GB into 11GB by the way. There's still a lot of photos in there 🙂
    1 point
  41. One other thing - I am also keeping a copy of the original uploads folder just in case. I've set max resolutions in various CMS' since 2001 and in later years you wonder why you ever set it as low as 800x800 for example 😄 This way I have the originals if I ever need them again, but I think for the content in question, 1600px max width/height should be absolutely fine. There will also likely be a write-up for this project - it's going to be a while longer though before it's ready.
    1 point
  42. Hey folks! Took a couple of late nights, but managed to turn this old gist of mine into a proper module. The name is SearchEngine, and currently it provides support for indexing page contents (into a hidden textarea field created automatically), and also includes a helper feature ("Finder") for querying said contents. No fancy features like stemming here yet, but something along those lines might be added later if it seems useful (and if I find a decent implementation to integrate). Though the API and selector engine make it really easy to create site search pages, I pretty much always end up duplicating the same features from site to site. Also – since it takes a bit of extra time – it's tempting to skip over some accessibility related things, and leave features like text highlighting out. Overall I think it makes sense to bundle all that into a module, which can then be reused over and over again 🙂 Note: markup generation is not yet built into the module, which is why the examples below use PageArray::render() method to produce a simple list of results. This will be added later on, as a part of the same module or a separate Markup module. There's also no fancy JS API or anything like that (yet). This is an early release, so be kind – I got the find feature working last night (or perhaps this morning), and some final tweaks and updates were made just an hour ago 😅 GitHub repository: https://github.com/teppokoivula/SearchEngine Modules directory: https://modules.processwire.com/modules/search-engine/ Demo: https://wireframe-framework.com/search/ Usage Install SearchEngine module. Note: the module will automatically create an index field install time, so be sure to define a custom field (via site config) before installation if you don't want it to be called "search_index". You can change the field name later as well, but you'll have to update the "index_field" option in site config or module settings (in Admin) after renaming it. Add the site search index field to templates you want to make searchable. Use selectors to query values in site search index. Note: you can use any operator for your selectors, you will likely find the '=' and '%=' operators most useful here. You can read more about selector operators from ProcessWire's documentation. Options By default the module will create a search index field called 'search_index' and store values from Page fields title, headline, summary, and body to said index field when a page is saved. You can modify this behaviour (field name and/or indexed page fields) either via the Module config screen in the PocessWire Admin, or by defining $config->SearchEngine array in your site config file or other applicable location: $config->SearchEngine = [ 'index_field' => 'search_index', 'indexed_fields' => [ 'title', 'headline', 'summary', 'body', ], 'prefixes' => [ 'link' => 'link:', ], 'find_args' => [ 'limit' => 25, 'sort' => 'sort', 'operator' => '%=', 'query_param' => null, 'selector_extra' => '', ], ]; You can access the search index field just like any other ProcessWire field with selectors: if ($q = $sanitizer->selectorValue($input->get->q)) { $results = $pages->find('search_index%=' . $query_string . ', limit=25'); echo $results->render(); echo $results->renderPager(); } Alternatively you can delegate the find operation to the SearchEngine module as well: $query = $modules->get('SearchEngine')->find($input->get->q); echo $query->resultsString; // alias for $query->results->render() echo $query->pager; // alias for $query->results->renderPager() Requirements ProcessWire >= 3.0.112 PHP >= 7.1.0 Note: later versions of the module may require Composer, or alternatively some additional features may require installing via Composer. This is still under consideration – so far there's nothing here that would really depend on it, but advanced features like stemming most likely would. Installing It's the usual thing: download or clone the SearchEngine directory into your /site/modules/ directory and install via Admin. Alternatively you can install SearchEngine with Composer by executing composer require teppokoivula/search-engine in your site directory.
    1 point
  43. 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
  44. 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
  45. This is impressive and fulfills a lot of requirements I am looking for this type of page builder. I particularly like the use of blocks as pages (maintaining the PW flexibility to reference these blocks from other pages), that styles are saved separately in JSON and a stylesheet in generated for each page (which you can check in the source code of your example), and above all the use of CSS grids that allow much more complex and visually interesting layouts. I wouldn't let my clients touch this kind of interface unless they're design-savvy, so I'm glad that design features can be disabled for certain roles. Congrats!
    1 point
  46. 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
  47. Important security update! Hi RockGrid users, I'm very sorry for that, but I have to announce a security update. If you are using RockGrid on a public site please upgrade to v0.0.22 (Fieldtype) immediately. It is a simple but important update: https://github.com/BernhardBaumrock/FieldtypeRockGrid/commit/0be2086139c84f775937246ed2985ac4c4a3e9c3; The proplem exists on all RockGrid fields with AJAX turned ON. In this case it was theoretically possible to expose the field data to a user that should not be allowed to see this data (in the worst case even a guest user) if the user knew how to do it and he also knew the name of the rockgrid field. The update now restricts access for AJAX field data to superusers only. You can easily adjust that via simple hooks: // rockgrid access control $wire->addHookAfter("InputfieldRockGrid::access", function(HookEvent $event) { // all grid data is accessible for all logged in users $event->return = $this->user->isLoggedin(); }); Or more granular via the fieldname: // rockgrid access control $wire->addHookAfter("InputfieldRockGrid::access", function(HookEvent $event) { $field = $event->arguments(0); $user = $this->user; $access = $event->return; switch($field) { case 'field1': case 'field2': case 'field3': $access = $user->isLoggedin(); break; case 'field4': $access = ($user->name == 'foo'); break; } $event->return = $access; }); Field 1-3 is allowed for logged in users, field4 only for user foo and all other fields only for superusers (default rule). I'm not totally happy any more with several aspects of RockFinder and RockGrid, but it is the best option I have so far (until I can build something totally new, maybe with tabulator if tests work well). Special thx to @Zeka for bringing this issue to my attention by coincidence in the other topic!
    1 point
  48. I see your points - I started that way too. Then I realized it's much easier to handle multi-language strings like success message, etc so I added a GUI. I still think this is an improvement, I can make adjustments much easier in the admin when needed. But it's not a requirement, you can use it purely from code too. The form process part looks like this: if ($nfh->checkSuccess()) { $nfh->saveToPage($nfh->getTitle('name, email')); $nfh->sendAdminEmail(); $nfh->getResponse(); exit; } Here saveToPage, sendEmail, sendAdminEmail methods can have parameters so you don't need to use the corresponding admin page. If there's a page for it, then adding a form is as easy as $nfh = addForm(1066); echo $nfh->form; // or $nfh->renderForm() The module loads the form and form field files from page ID 1066 name. Anyway, I think I'll clean up it a bit and share as I see there's a need for it.
    1 point
  49. did a total rewrite of the module and will release it the next weeks when i'm done
    1 point
×
×
  • Create New...