-
Posts
366 -
Joined
-
Last visited
-
Days Won
18
Everything posted by MoritzLost
-
get all field values even on following paginated pages
MoritzLost replied to froot's topic in API & Templates
Great, didn't know limit=0 would work, good to know! You don't need the second row, just a less verbose and error-prone alternative to the procedural code in your initial post ? I prefer this functional style, but in the end, it doesn't even matter. -
get all field values even on following paginated pages
MoritzLost replied to froot's topic in API & Templates
The problem is that $rows only holds the items of the current page, as per your pagination settings. To get all items, you need to explicitly overwrite the limit and offset resulting from your pagination settings and the current page. Then you can use that to create a complete list of categories. Something like this should work: $rows = $pages->get('path/to/parent/page')->protable('start=0, limit=9999'); $categories = array_unique(array_filter($rows->each('category'))); Note the high limit which overrides the default limit set by the pagination. Not pretty, but I don't think it's possible to override the limit to none. Just make sure the limit is higher than the number of values you ever expect to have in your field. -
I don't think you can do counts aggregated columns with ProcessWire selectors. There's Pages::count of course, but that only returns a single count. This isn't the most efficient solution, but should be fine for a couple hundred or thousand pages: $results = $pages->find('template=your_template'); $years = array_filter(array_map(function ($p) { $year = $p->getUnformatted('your_date_field'); return $year ? wire('datetime')->date('Y', $year) : null; }, $results->getArray())); $pagesPerYear = array_count_values($years); Below that you'll have to use raw SQL I think. Might also be possible with RockFinder3, not sure (@bernhard?)
-
The reason it's not working is that you're giving fopen a relative path. This works fine with your test file if it's placed in the webroot, since the path is relative to that. But inside a template file, the relative path does not resolve correctly. For a static file, you can use $config->paths to retrieve an absolute base path that will always work: $f = fopen($config->paths->files . "1619/dundee.csv", "r"); If you want to read a CSV file uploaded to a page field, you can use Pagefile::filename instead: $filename = $page->your_csv_field->filename(); $f = fopen($filename, "r"); Make sure your CSV field is set to single file output. If your field allows multiple files, use $page->your_csv_field->first()->filename() instead.
-
Sanitizing Hashtags from # to %23 to create a tweetable tweet?
MoritzLost replied to hollyvalero's topic in General Support
By 'convert that tweet as postable' you mean format it to post it to the Twitter API? In this case, what you want to use urlencode instead of the sanitizer. Depending on what you want to do rawurlencode might be a better option. It encodes spaces as %20 instead of plus signs, which is technically correct according to the relevant RFC. -
Just a little follow-up to this topic, my new ProcessWire tutorial site processwire.dev (see the discussion thread) is built as a static site, using 11ty. It was super fun to build, and easy to get that coveted 100/100 lighthouse score with ? I see the following advantages of using a static site generator for this project: The big one (at least for me): All the content is under version control! (see the source code here). Having the content stored as markdown files also means I could use my regular code editor (VS Code) to seamlessly switch between templating / developent and writing (though of course that only applies to developers). Being able to search all files (i.e. the entire site) was also super useful for correcting common typos and stuff like that. The site fits comfortably in the free tier on Netlify, so hosting is completely free for me (except the .dev domain, of course). But since it's a static site there's zero vender lock-in, I could switch to Vercel or any regular webhosting package in a few minutes. Having a build step on a linux environment with NodeJS enables all sorts of fun stuff that wouldn't be feasible to do on the fly, especially on shared webhosting packages with a limited environment. For instance, I wrote a fun little script that generates preview images using a special template and a headless browser that takes screenshots programmatically using Puppeteer. See the generated example image below. Not super necessary, but fun ? I published the script on NPM (generate-preview-images) if you want to give it a try, though it's a super alpha version and not really documented yet. So yeah, I think static site generators are going places. 11ty in particular is pretty cool, though it's kind of barebones at the moment and still missing some features that you already have in ProcessWire.
-
processwire.dev — Tutorials for web development with ProcessWire
MoritzLost replied to MoritzLost's topic in Tutorials
@monollonom Thanks! I really recommend giving Twig a try, once you get used to it you can never go back ? That's actually an area I don't have a good solution for yet. The problem is that all the template and field configuration lives only in the database, so it's not easy to put under version control. The simplest solution is to include a database dump in the version control, but that is a one-way street. Diverging versions of the database dump are really awkward to merge, so it's impossible for multiple people to work on a project independently and merge their work into one repository later, because they will have different database states that can't really be merged liked diverging versions of source code. Also, it doesn't help with continuous deployment (developing additional feature in a dev environment and updating a live site), because importing the database dump would overwrite the actual site content on the live site as well. The other solution are migration modules like RockMigrations which encode all changes as config files or migration scripts. However, writing those migrations is just so much work, especially with all the field properties which aren't really documented consistently. Mostly it just doesn't feel worth it, especially since it doesn't really solve the problem of multiple people working on a project independently of each other. I'm not even sure if there is a great solution for this. Fundamentally I would prefer if the entire template and field configuration was file-based so it could be versioned. But of course that's a completely different system design. -
textformatter Automatically link page titles
MoritzLost replied to MoritzLost's topic in Modules/Plugins
@wbmnfktr Thanks for the update, glad you got it working! Not sure how removing the textformatters and adding them again should change anything, but as long as it's working now ... ? -
Hi everyone, I'm happy to present my latest project, which is a collection of guides and tutorials for web development with ProcessWire written by me. https://processwire.dev/ What is this? I have written several tutorials in this forum, and I wanted a central place to collect all my tutorials and put them in a logical order. processwire.dev is exactly that, a curated list of tutorials for different topics related to ProcessWire development. I have revised, updated and expanded most of my existing tutorials. There are also some completely new tutorials. Notable topics How to integrate Composer in your ProcessWire sites, and a general explainer for namespaces and autoloading. A two-part guide to using Twig with ProcessWire and adding custom functionality. How to create flexible content modules with Repeater Matrix fields and Twig. A general guide to performance optimization for ProcessWire. A starter guide for the "ProcessWire mindset" - how to structure your content. ... and much more! What's next? I hope this will be a useful resource to all of you fine people. Please note that these tutorials are very much opinionated, and they reflect my personal experience and development practices. So if you disagree with some of my conclusions, that's perfectly fine! I'm happy to discuss all my recommendations and approaches with you, so let me know if you have any feedback, suggestions or error corrections! I plan to expand this resource over time and already have some new topics planned. If you have suggestions for new topics, go ahead and post them here as well! Start reading now: processwire.dev
-
Page accessible with unique one-time access codes
MoritzLost replied to nuel's topic in General Support
I have built a couple of similar systems, it's actually not that difficult. You don't need a module for it, you can just write out the logic in your PHP template (I prefer to use custom page classes for stuff like this, but the template file will do fine). A good trick to get started is to separate the requirements into individual tasks that you can finish one by one. In your case, I would divide the requirements into the following tasks and questions: Create a data structure to hold your access codes, i.e. either a field or new template. (Optional) How and when are codes created? Manually or automated? For example, when a new concert is created in the system, you could generate a number of access codes automatically, or create a code when a reservation comes in. (Optional) How are codes distributed? For example, you could select a random code (or generate a new one, see above) when a registration comes in and send it in an autoresponse email. Create an interface for entering your individual code on a concert page, and validate the codes. Store the authorization (i.e. a valid entered code) so it can only be used by one person. Then you can think about how to solve those problems using either custom code or the tools that come with ProcessWire. Here are some suggestions: I usually use a Table field (part of the ProFields module), because it allows you to store a code alongside with information on it (like expiration date, used/available flag, etc) in a single field. I would have one dedicated template for access-protected events (in your case, concerts) and add one Table field to it which holds the codes for this event. You can solve this with simple hooks. For example, automatically creating a number of access codes when a new concert is created is only a matter of hooking Pages::add. Or if you want to create codes on demand during registration, hook into the relevant part of your registration process to create the codes when needed. I would build a simple registration form using FormBuilder, which already includes input validation, payments processing and autoresponder. Again, you can use hooks to insert an individual access code into the autoresponse email. You'll want to have a flag in your codes table which keeps track of whether this code has been used or not, to make sure each code can be used only once. You can do this in PHP directly in the template file: Check if the user is already authorized for this concert (see below), and if so, display the concert's content / stream / whatever. If the user is NOT authorized, display a form for entering the access code. If a code was submitted, check if the code is valid (i.e. it exists on this page, has not been activated yet — this should be a separate flag in your Table field — etc). If it is, store the authorization for this user (see below) and display the concerts content. You can use multiple approaches for this, depending on how serious you are about preventing misuse. A simple approach would be to store the authorization in a cookie or session. This would allow the same user to close and reopen the page (though using the session would mean the codes only stay active for the current browser session). This is pretty simple, but of course has it's limitations. For example, what if users want to switch from the desktop to a mobile phone? Distinguishing between one user switching devices and multiple users sharing an access code will be difficult. Another possibility is to user ProcessWire user accounts, requiring users to create an account and then adding the authorization for individual concerts to their account directly. Using accounts you could make access codes obsolete entirely. Of course, you'll have to find a solution against account sharing (I'm not sure if ProcessWire allows multiple simultaneous sessions for one account by default). Note that none of those solutions stop a really determined bad actor from sharing their access to your concerts, or the content you provide. The only solutions for this is to integrate DRM, which is really difficult and not really feasible for small websites. But both the solutions above should work mostly fine. Ok this text got longer than I intended, and I know it's not a complete solution. But you see the point – by breaking down the requirements into individual parts the system becomes much easier to build. You can pick and choose from existing solutions (ProcessWire user accounts, the FormBuilder and ProFields module, etc) and combine them with custom logic using hooks or the template file. The rest is just a matter of experience with ProcessWire and PHP. If you get stuck on coding specific steps of this system, make sure to post them here, there's always a good solution! -
It's impossible to tell why the icon is not working without looking at the source code. My best guess is that there is a global setting that controls the Email address to show in the footer, and that someone has changed that setting. On my sites, global site elements (like icons in the footer) will have corresponding options on a hidden 'settings' page where you could, for example, change the Email address or remove it. Does your site have such a settings page? Otherwise, you'll have to check the template file (the footer is likely a partial or mixin somewhere in your templates folder) to see why it's not working anymore. There are also numerous JavaScript errors in the console, so something might be going wrong there. You'll want to have your developer have a look at that, it could create any number of issues for your site. The SSL certificate is configured correctly and you can visit the site with HTTPS: https://riedl-whirlpool.de/infrarotkabinen/ All you need is a redirect from HTTP to HTTPS. You can do that using ProcessWire's .htaccess file, which should be in the root directory of your site. Uncomment these two lines, which will automatically redirect all HTTP requests to HTTPS.
-
textformatter Automatically link page titles
MoritzLost replied to MoritzLost's topic in Modules/Plugins
@wbmnfktr Hm, if you're using the latest version (4.1.0), there are a couple of settings you can check (Modules -> Configure -> TextformatterPageTitleLinks). Here's a list of all settings and what they do: https://github.com/MoritzLost/TextformatterPageTitleLinks#all-settings A couple of possible reasons: Have you selected the templates you want to be linkable? With no templates selected, the module does nothing. In your template output, is output formatting turned on? With output formatting disabled, textformatters aren't applied. Since you said "internal pages", are those visible (not hidden) and does the corresponding PHP template file exist? Templates without a template file are not viewable by default, so they won't get linked. In this case, you may need to check both Include hidden pages and Disable automatic visibility check. There are other options that limit which pages may be linked, especially Minimum length for linkable titles and Use case insensitive mode, make sure none of those are the culprit. Is your site multi-language? The module only links titles in the current language and doesn't fall back to the default language if the current language title is empty. So if you're checking in a non-default language and your pages have empty titles in that language, nothing will get linked. If all those settings are correct, I'd start by checking which pages the module selects as candidates for automatic links. If you're using Tracy Debugger, you can check with barDump (otherwise, just use var_dump). Insert this in TextformatterPageTitleLinks.module#L101-L103: // get all pages of the selected templates as an associative id => title array $id_title_array = $this->getTitleIdArrayForCurrentLanguage($template_ids, $options); bd($id_title_array); This should dump the array of pages (keyed by page ID) to the Tracy Debugger bar. If you don't see anything, the textformatter isn't being executed at all. If you see an empty array or the pages you want to link to aren't there, the reason is likely one of the settings above. If the pages you want to link to are included in the array but still don't get linked, it might just be the viewable check (see above). In this case, check what buildTitleMarkup returns in TextformatterPageTitleLinks.module#L147-L148. Hope this helps! If it's still not working, please post the output of the debug statements and preferably a screenshot of your module settings and I'll take a look. -
https://processwire.com/api/ref/wire-array/import/ That's correct, the sort order of a page's children is relative to that level of hierarchy, not global. So by using pages->find with the sorting order you get weird results. I don't think there is a way to get a hierarchically ordered result in one query. Do you want your page array to be ordered depth-first (first page -> children of first page -> second page -> children of second page -> ...) or depth-first (first page -> second page -> ... -> children of first page -> children of second page -> ...)? I assume you just need your $articles variable to be a flat array of all pages in their page tree order, correct? I don't think it can be done in one query, but you can use WireArray::add and WireArray::import to create your flat array of pages. For depth-first order: $articles = new \ProcessWire\PageArray(); foreach ($page->children('template=event, hide_in_programm=0') as $article) { $articles->add($article); $children = $article->children('template=event, hide_in_programm=0'); if ($children->count()) { $articles->import($children); } } For breadth-first order: $articles = new \ProcessWire\PageArray(); $parents = $page->children('template=event, hide_in_programm=0'); $articles->import($parents); foreach ($parents as $article) { $children = $article->children('template=event, hide_in_programm=0'); if ($children->count()) { $articles->import($children); } }
-
What to say to clients who ask for Yoast SEO?
MoritzLost replied to DrQuincy's topic in Getting Started
Just to go against the grain a bit: Those modules are way, way overkill for 99% of sites. Just looking at the screenshot in the Seo Maestro thread, all those options would confuse most of my clients. Who really wants or needs to manually edit the change frequency of a single page? Some of those options should also be generated automatically (Locale, based on the current language) or set globally (Site Name, for example). I get that you can control which fields to show and that it's kind of a framework which you can use for all kinds of sites. But in my experience, showing five screens of SEO settings on every page is the best way to get clients/editors to be scared of them and never use them. Three fields - title, description, preview image - are all you need most of the time. KISS. I would use external tools for this. There are many tools that are much better at this stuff than Yoast, and they look at the page as a whole, not just the body content. For example, at work we have at some points used the following tools among others, in no particular order: Ryte, Seobility, Semrush, Sistrix. And many more ... -
What do you mean by form output? Generelly, forms are used for input ? The first step is to determine which interactions you want to track. For example, you could track an event: ... whenever a form is successfully submitted. ... whenever a form is partially filled but then abandoned. ... each time somebody fills out an individual field (will result in many events). ... whenever somebody finishes an individual step of a multi-step form. Beyond that, you first need to determine what your client wants to know, in terms of specific metrics, interactions etc. Most of those you can do with simple event handlers reacting to form changes, inputs and button clicks. // find all formbuilder forms on the page const FormBuilderForms = Array.from(document.querySelectorAll('form.FormBuilder')); // track the submit button FormBuilderForms.forEach(form => form.addEventListener('submit', e => { // this assumes you're using gtag.js with transport mechanism 'beacon' gtag('event', 'form_submitted', {/** add additional information based on the form data */}); }); A bit more tricky is tracking submissions only after they've been validated server-side, since you can't catch that with JavaScript. However, after submitting a form you will usually show a success message or redirect to a dedicated thank you page, so you can just track those: // FormBuilder uses this ID for the submission message const FormBuilderSuccess = document.getElementById('FormBuilderSubmitted'); if (null !== FormBuilderSuccess) { gtag('event', 'form_submission_result', {/** track the name + result of this submission */}); } Does that answer your question? It's just a matter of determining what you want to track, and then adding appropriate event handlers with JavaScript.
-
@ngrmm Yes, you can set regular cookies as mentioned in my first paragraph, though they won't be accessible through ProcessWire's $session API. But why would you load 87kB of library bloat for something that you can do in one line? ?
-
You can't save data to the session in the browser, because session data is stored server-side. The only thing the browser stores regarding the session is the cookie that identifies the current session for the server. If you need to access the stored value both client- and server-side, you can just use a regular cookie. By omitting the expires field it will only be valid for the current browser session, the same as ProcessWire's sessions. However, if you don't need to access the value server-side (which I assume you don't for a modal), you can use sessionStorage or localStorage instead, which do the same thing as cookies but with a cleaner API. Use localStorage if you want the stored value to persist across sessions (for a dismissable modal this is probably what you want) or sessionStorage to store a value only for the current browser session. const dismissalKey = 'modal-dismissed'; // user clicks on modal button $('.modal_button').click(function(){ // 1. store dismissal in the sessionStorage OR the localStorage localStorage.setItem(dismissalKey, '1'); // 2. toggle class $('.modal').toggleClass('off'); }); // somewhere else, check if the modal has been dismissed const modalIsDismissed = Boolean(localStorage.getItem(dismissalKey));
-
variable on _main.php is undefined on template
MoritzLost replied to froot's topic in General Support
Where are you getting the error, and where are you declaring the variable? If you're trying to access a variable in the template file corresponding to the page template that has been declared in the _main.php, this won't work because the _main.php is executed last. The template files are included in the following order (assuming your config above): _init.php -> [current-template].php -> _main.php So if you want to declare a variable to use in all templates, you need to do that in the _init.php All the template files should have the ProcessWire namespace. Not sure about the undefined function errors, those functions should be available in the ProcessWire namespace. Would need to see the file to diagnose. -
Yeah nice resource, I've been using them for a while, I guess they rebranded a while from unsplash.it ? By the way, the pictures are from unsplash.com, so they are completely free and can even be used in final commercial products. Though they don't have many upload guards in place to prevent people uploading copyrighted material, so it's worth it to do a reverse image search just to make sure. There's also undraw.co for illustrations, which is nice because the illustrations are SVG files and you can customize the main color right on the website.
-
That sounds ... bad ? Are you sure there isn't a more convenient way to do this in Snipcart? Anyway, as far as I can tell your solution is producing the correct result, so the only problem is the memory consumption. You could try to build the JSON on the fly instead of pushing everything on one big array. That is, open a file handle, encode individual lines in your recursive function and write them directly to the file. Might be a bit slower, but wouldn't require holding all combinations in memory. Though you gotta take care to have valid JSON in the end, i.e. no trailing comma for the last line and stuff like that.
-
The _x function is part of ProcessWire's translation system – using it is good practice, because it allows you to change hard-coded strings (such as the language code) through the backend without touching the code, and translate them to multiple languages for multi-language sites. You're probably just missing a translation. Go to Setup -> Languages -> Portugese (might also be default depending on your language setup), then under "Site Translation Files" check if the main.php is already listed, otherwise select it through "Find files to translate". Add the correct language code there and it should appear in your HTML!
-
What exactly is your use case here? I haven't completely cracked your code yet, you want only unique combinations, right? So only one of (A + B) and (B + A)? In this case, you're looking at n^2 possible combinations (I guess that's called a Power Set, but math theory is not my strong suit). You mention 14 elements as the limit at which you run out of memory. In this case, you're looking to generate 2^14 = 16384 combinations (at least it's a step up from ALL possible combinations, which would be 14! = 87178291200 ?). Why do you need that many? Given your variable names, I assume this is for some kind of product configurator on a store page? In this case, you're not going to present 16 thousand possible combinations anyway. Couldn't you just calculate the price for a given combination on the fly? Sorry if I'm rambling, maybe you really need this. In this case, I'd say your solution needs to be a lot more imperative and less functional. Recursive functions are kinda RAM-heavy, as far as I'm aware PHP doesn't even have tail call optimizations, so all those intermediate results are staying in RAM. Also, you're passing the $addOns and $base arrays by value, so those are duplicated for every function call. So using simple for- and while-loops instead of a recursive function might already be enough to reduce memory usage a lot.
-
If you really need some serious speed increase you can of course go directly to the database instead of using $pages. Though that is a lot more work, because you'll have to manually add status flags to your query, manage multilanguage support etc. I've done this for my module Automatically link page titles, the list of linkable titles and corresponding page IDs is retrieved directly from the field_title table. See the source code for reference. If you can't optimize the query, caching might be a solution, this way only the initial access will run slowly, but subsequent requests and queries will be fast. Your query varies by $item, $eventDate and $limit, so you could create a cache key based on that and store the results through the $cache API. Something like this: $limit = $item->qty; $cacheKey = implode('-', ['bookings', $item, $eventDate, $limit]); $cache = $this->wire('cache'); $selector = "template=booking, booking_items.booking_item.id=$item, booking_date=$eventDate, booking_status!=pending|cancelled, limit=$limit"; $pp = $this->wire()->pages; $otherBookings = $cache->get( $cacheKey, WireCache::expireSave, function () use ($pp, $selector) { return $pp->find($selector); } ) Quick and untested, might need some adjustments. You can use WireCache::expireSave to invalidate the cached results whenever any page is saved, though that's not very efficient if the bookings are edited very frequently. Or use a max age of a day or so, at the risk of sometimes showing outdated results.
-
module TrelloWire - Turn your pages into Trello cards
MoritzLost replied to MoritzLost's topic in Modules/Plugins
Important update: I've just updated to module to version 2.0.0 which fixes the issues with the API requests and makes the module work correctly again. However, the module now requires ProcessWire 3.0.167 or above! Read on below for an explanation. Since publishing this module, I ran into several problems with API requests not working correctly. I found that the reason was both an issue with the way the module formats it's API requests, as well as some issues with ProcessWire's cURL implementation, which means that some requests wouldn't work correctly (namely PUT and DELETE requests). I then submitted a pull request to the ProcessWire core which fixes those issues, which were thankfully accepted by ryan. The fixes are now live and available in ProcessWire 3.0.167, and I've updated the TrelloWire module to fix some internal issues as well, so the module should now work correctly once again. Unfortunately, this means that ProcessWire versions below 3.0.167 won't work correctly. New options: Switch the network implementation used by the module There's a new configuration option available that you can use to force the module to use cURL, fopen or socket connections respectively. Check out the documentation for usage information! If you can't update to 3.0.167 ... You can still install and use the module on older ProcessWire versions (though the installation will ask for confirmation if you are below 3.0.167). The module will try to use fopen to connect to the API on any ProcessWire installation below 3.0.167, but I can't promise it will work correctly. You can also try socket connections using the configuration option above, but I couldn't get it to work in my test environment. Let me know if you need support or something isn't working for you! @maddmac Just tagging you to let you know you don't need to use the dev branch from Github anymore ? -
@adrian Hi Adrian, the latest version is throwing a warning if there are no external panels: I think this will only occur if there are no external panels, if the glob pattern is empty static::$externalPanels will be undefined (see TracyDebugger.module.php#L320-327). Should be fixed by initializing $externalPanels to an empty array (see L143). Thanks!