Leaderboard
Popular Content
Showing content with the highest reputation on 09/14/2018 in all areas
-
The focus this week was on covering the queue of issue reports, and a whole lot of progress was made. Plus some other useful additions can be found ProcessWire 3.0.113. This post covers all the details— https://processwire.com/blog/posts/processwire-3.0.113-core-updates/7 points
-
Hello @matjazp The metadata for postal formatting of the addresses comes from Google's libaddressinput project. I note that the "SI-" prefix to the Slovenian postal code is actually coming from Google's postal address format metadata for Slovenia. Please reference line 1324 of the formats.php file and you'll see that "...%nSI- %Z..." appears in the format string. This forces the literal "SI- " to appear at the start of a new line, just before the zip/postal code. Essentially this is an upstream library issue and you can open an issue against libaddressinput to attempt to get it resolved, though I note there is an old, closed, issue regarding the prefix to the Slovenian postal code. However, I anticipated situations like this and there is a mechanism built in to the module code you can use to override the upstream format. If you edit your local copy of formats_overrides.php and add the following to the returned array, you will be able to customise the format as you require until Google fixes (or doesn't) the upstream repo... 'SI' => [ 'fmt'=>'%N%n%O%n%A%n%Z %C', // Prevent "SI - " from prefixing the postal code. ], The metadata for Slovenia marks non of the fields as mandatory - so you should find this input works for you as is. In fact, from version 1.0.2, you need to rename the example.formats_overrides.php file to formats_overrides.php as I am currently working on making the installed overrides file persist across upgrades (something it doesn't do quite correctly yet.) Also; just in case you haven't worked out how to suppress the country in the input and output of domestic addresses, you should be able to do so by setting the config options for the street address field as follows... Allowable countries: select just Slovenia. Show destination country field?: Set to "Never". Regarding benefits over a free-form textarea, here are some I can think of... Address subfields can be used in PW selectors: $addresses = $pages->find('address.locality=Paris'); Automatic compliance with layout requirements (including capitalisation and required fields) of the destination countries postal format (where correct in the meta data). Slovenia seems quite relaxed in which fields are required and which should be capitalised. Many European countries require certain fields, and require them to be output capitalised when used as postal addresses. Aids data cleaning on input over a free-form textarea (lots of my users type with caps-lock on, for example!) Automatic zipcode/regex application to subfields. Direct access to subfields, if you want to use them in your own template output other than in a full address label. Assorted format() functions for single/multiline, plain/html output. Automatic inclusion of address microformat metadata to the fields. I did read various UI/UX forums about this issue before I started, many of them came to the conclusion that a free-form input is best from the UX point of view - and I can understand that point of view. This module is my attempt to meld as close to a free-form input experience as I can, with structured data that accepts just about any country's postal code format. I hope that helps. Best wishes, Steve4 points
-
3 points
-
Hello there, I've started using ProcessWire at work a while ago and I have been really enjoying building modular, clean and fast sites based on the CMS (at work, I usually post as @schwarzdesign). While building my first couple of websites with ProcessWire, I have written some useful helper functions for repetitive tasks. In this post I want to showcase and explain a particular function that generates a responsive image tag based on an image field, in the hope that some of you will find it useful :) I'll give a short explanation of responsive images and then walk through the different steps involved in generating the necessary markup & image variations. I want to keep this beginner-friendly, so most of you can probably skip over some parts. What are responsive images I want to keep this part short, there's a really good in-depth article about responsive images on MDN if you are interested in the details. The short version is that a responsive image tag is simply an <img>-tag that includes a couple of alternative image sources with different resolutions for the browser to choose from. This way, smaller screens can download the small image variant and save data, whereas high-resolution retina displays can download the extra-large variants for a crisp display experience. This information is contained in two special attributes: srcset - This attribute contains a list of source URLs for this image. For each source, the width of the image in pixels is specified. sizes - This attribute tells the browser how wide a space is available for the image, based on media queries (usually the width of the viewport). This is what a complete responsive image tag may look like: <img srcset="/site/assets/files/1015/happy_sheep_07.300x0.jpg 300w, /site/assets/files/1015/happy_sheep_07.600x0.jpg 600w, /site/assets/files/1015/happy_sheep_07.900x0.jpg 900w, /site/assets/files/1015/happy_sheep_07.1200x0.jpg 1200w, /site/assets/files/1015/happy_sheep_07.1800x0.jpg 1800w, /site/assets/files/1015/happy_sheep_07.2400x0.jpg 2400w" sizes="(min-width: 1140px) 350px, (min-width: 992px) 480px, (min-width: 576px) 540px, 100vw" src="/site/assets/files/1015/happy_sheep_07.1200x0.jpg" alt="One sheep"> This tells the browser that there are six different sources for this image available, ranging from 300px to 2400px wide variants (those are all the same image, just in different resolutions). It also tells the browser how wide the space for the image will be: 350px for viewports >= 1140px 480px for viewports >= 992px 540px for viewports >= 576px 100vw (full viewport width) for smaller viewports The sizes queries are checked in order of appearance and the browser uses the first one that matches. So now, the browser can calculate how large the image needs to be and then select the best fit from the srcset list to download. For browsers that don't support responsive images, a medium-sized variant is included as the normal src-Attribute. This is quite a lot of markup which I don't want to write by hand every time I want to place an image in a ProcessWire template. The helper function will need to generate both the markup and the variations of the original image. Building a reusable responsive image function Let's start with a function that takes two parameters: a Pageimage object and a standard width. Every time you access an image field through the API in a template (e.g. $page->my_image_field), you get a Pageimage object. Let's start with a skeleton for our function: function buildResponsiveImage( Pageimage $img, int $standard_width ): string { $default_img = $img->maxWidth($standard_width); return '<img src="' . $default_img->url() . '" alt="' . $img->description() . '">'; } // usage example echo buildResponsiveImage($page->my_image_field, 1200); This is already enough for a normal img tag (and it will serve as a fallback for older browsers). Now let's start adding to this, trying to keep the function as flexible and reusable as possible. Generating alternate resolutions We want to add a parameter that will allow the caller to specify in what sizes the alternatives should be generated. We could just accept an array parameter that contains the desired sizes as integers. But that is not very extendible, as we'll need to specify those sizes in each function call and change them all if the normal size of the image in the layout changes. Instead, we can use an array of factors; that will allow us to set a reasonable default, and still enable us to manually overwrite it. In the following, the function gets an optional parameter $variant_factor. // get the original image in full size $original_img = $img->getOriginal() ?? $img; // the default image for the src attribute, it wont be upscaled $default_image = $original_img->width($standard_width, ['upscaling' => false]); // the maximum size for our generated images $full_image_width = $original_img->width(); // fill the variant factors with defaults if not set if (empty($variant_factors)) { $variant_factors = [0.25, 0.5, 0.75, 1, 1.5, 2]; } // build the srcset attribute string, and generate the corresponding widths $srcset = []; foreach ($variant_factors as $factor) { // round up, srcset doesn't allow fractions $width = ceil($standard_width * $factor); // we won't upscale images if ($width <= $full_image_width) { $srcset[] = $original_img->width($width)->url() . " {$width}w"; } } $srcset = implode(', ', $srcset); // example usage echo buildResponsiveImage($page->my_image_field, 1200, [0.4, 0.5, 0.6, 0.8, 1, 1.25, 1.5, 2]); Note that for resizing purposes, we want to get the original image through the API first, as we will generate some larger alternatives of the images for retina displays. We also don't want to generate upscaled versions of the image if the original image isn't wide enough, so I added a constraint for that. The great thing about the foreach-loop is that it generates the markup and the images on the server at the same time. When we call $original_img->width($width), ProcessWire automatically generates a variant of the image in that size if it doesn't exist already. So we need to do little work in terms of image manipulation. Generating the sizes attribute markup For this, we could build elaborate abstractions of the normal media queries, but for now, I've kept it very simple. The sizes attribute is defined through another array parameter that contains the media queries as strings in order of appearance. $sizes_attribute = implode(', ', $sizes_queries); The media queries are always separated by commas followed by a space character, so that part can be handled by the function. We'll still need to manually write the media queries when calling the function though, so that is something that can be improved upon. Finetuning & improvements This is what the function looks like now: function buildResponsiveImage( Pageimage $img, int $standard_width, array $sizes_queries, ?array $variant_factors = [] ): string { // get the original image in full size $original_img = $img->getOriginal() ?? $img; // the default image for the src attribute, it wont be upscaled $default_image = $original_img->width($standard_width, ['upscaling' => false]); // the maximum size for our generated images $full_image_width = $original_img->width(); // fill the variant factors with defaults if not set if (empty($variant_factors)) { $variant_factors = [0.25, 0.5, 0.75, 1, 1.5, 2]; } // build the srcset attribute string, and generate the corresponding widths $srcset = []; foreach ($variant_factors as $factor) { // round up, srcset doesn't allow fractions $width = ceil($standard_width * $factor); // we won't upscale images if ($width <= $full_image_width) { $srcset[] = $original_img->width($width)->url() . " {$width}w"; } } $srcset = implode(', ', $srcset); return '<img src="' . $default_img->url() . '" alt="' . $img->description() . '" sizes="' . $sizes_attribute . '" srcset="' . $srcset . '">'; } It contains all the part we need, but there are some optimizations to make. First, we can make the $sizes_queries parameters optional. The sizes attribute default to 100vw (so the browser will always download an image large enough to fill the entire viewport width). This isn't optimal as it wastes bandwidth if the image doesn't fill the viewport, but it's good enough as a fallback. We can also make the width optional. When I have used this function in a project, the image I passed in was oftentimes already resized to the correct size. So we can make $standard_width an optional parameter that defaults to the width of the passed image. if (empty($standard_width)) { $standard_width = $img->width(); } Finally, we want to be able to pass in arbitrary attributes that will be added to the element. For now, we can just add a parameter $attributes that will be an associative array of attribute => value pairs. Then we need to collapse those into html markup. $attr_string = implode( ' ', array_map( function($attr, $value) { return $attr . '="' . $value . '"'; }, array_keys($attributes), $attributes ) ); This will also allow for some cleanup in the way the other attributes are generated, as we can simply add those to the $attributes array along the way. Here's the final version of this function with typehints and PHPDoc. Feel free to use this is your own projects. /** * Builds a responsive image element including different resolutions * of the passed image and optionally a sizes attribute build from * the passed queries. * * @param \Processwire\Pageimage $img The base image. * @param int|null $standard_width The standard width for this image. Use 0 or NULL to use the inherent size of the passed image. * @param array|null $attributes Optional array of html attributes. * @param array|null $sizes_queries The full queries and sizes for the sizes attribute. * @param array|null $variant_factors The multiplication factors for the alternate resolutions. * @return string */ function buildResponsiveImage( \Processwire\Pageimage $img, ?int $standard_width = 0, ?array $attributes = [], ?array $sizes_queries = [], ?array $variant_factors = [] ): string { // if $attributes is null, default to an empty array $attributes = $attributes ?? []; // if the standard width is empty, use the inherent width of the image if (empty($standard_width)) { $standard_width = $img->width(); } // get the original image in full size $original_img = $img->getOriginal() ?? $img; // the default image for the src attribute, it wont be // upscaled if the desired width is larger than the original $default_image = $original_img->width($standard_width, ['upscaling' => false]); // we won't create images larger than the original $full_image_width = $original_img->width(); // fill the variant factors with defaults if (empty($variant_factors)) { $variant_factors = [0.25, 0.5, 0.75, 1, 1.5, 2]; } // build the srcset attribute string, and generate the corresponding widths $srcset = []; foreach ($variant_factors as $factor) { // round up, srcset doesn't allow fractions $width = ceil($standard_width * $factor); // we won't upscale images if ($width <= $full_image_width) { $srcset[] = $original_img->width($width)->url() . " {$width}w"; } } $attributes['srcset'] = implode(', ', $srcset); // build the sizes attribute string if ($sizes_queries) { $attributes['sizes'] = implode(', ', $sizes_queries); } // add src fallback and alt attribute $attributes['src'] = $default_image->url(); if ($img->description()) { $attriutes['alt'] = $img->description(); } // implode the attributes array to html markup $attr_string = implode(' ', array_map(function($attr, $value) { return $attr . '="' . $value . '"'; }, array_keys($attributes), $attributes)); return "<img ${attr_string}>"; } Example usage with all arguments: echo buildResponsiveImage( $page->testimage, 1200, ['class' => 'img-fluid', 'id' => 'photo'], [ '(min-width: 1140px) 350px', '(min-width: 992px) 480px', '(min-width: 576px) 540px', '100vw' ], [0.4, 0.5, 0.6, 0.8, 1, 1.25, 1.5, 2] ); Result: <img class="img-fluid" id="photo" srcset="/site/assets/files/1/sean-pierce-1053024-unsplash.480x0.jpg 480w, /site/assets/files/1/sean-pierce-1053024-unsplash.600x0.jpg 600w, /site/assets/files/1/sean-pierce-1053024-unsplash.720x0.jpg 720w, /site/assets/files/1/sean-pierce-1053024-unsplash.960x0.jpg 960w, /site/assets/files/1/sean-pierce-1053024-unsplash.1200x0.jpg 1200w, /site/assets/files/1/sean-pierce-1053024-unsplash.1500x0.jpg 1500w, /site/assets/files/1/sean-pierce-1053024-unsplash.1800x0.jpg 1800w, /site/assets/files/1/sean-pierce-1053024-unsplash.2400x0.jpg 2400w" sizes="(min-width: 1140px) 350px, (min-width: 992px) 480px, (min-width: 576px) 540px, 100vw" src="/site/assets/files/1/sean-pierce-1053024-unsplash.1200x0.jpg" alt="by Sean Pierce"> Now this is actually too much functionality for one function; also, some of the code will be exactly the same for other, similar helper functions. If some of you are interested, I'll write a second part on how to split this into multiple smaller helper functions with some ideas on how to build upon it. But this has gotten long enough, so yeah, I hope this will be helpful or interesting to some of you :) Also, if you recognized any problems with this approach, or can point out some possible improvements, let me know. Thanks for reading!2 points
-
Hello, this line would only get one item. $items = $pages->get("template=portal-files")->portal_bestanden; may be with this would work $items = $pages->find("template=portal-files"); foreach($items as $item){ foreach($item->portal_bestanden as $document){ echo $document->name; } }2 points
-
New version fixes a few bugs in the last release, especially regarding the keyboard shortcuts on Windows. Along these lines, I have also changed the shortcuts for the History Stack - they are now ALT+PageUp and ALT+PageDown. This means there is no longer the need to use the meta key (CMD or WIN key) for this which was conflicting on some systems. If you're on a Macbook without PU and PD keys, just use: fn + ALT + PageUp/Down A big thanks to @Robin S for helping with all the Windows testing on this! I think this is really starting to work really nicely now - I almost want to use it over my code editor ? Anyway, please let me know if you find any issues.2 points
-
Upgraded from1.0.3 to 1.0.4, the content of the formats_overrides.php is preserved. Pages: Successfully copied C:/inetpub/wwwroot/site/modules/.FieldtypeStreetAddress/formats_overrides.php to C:\inetpub\wwwroot\site\modules\FieldtypeStreetAddress/formats_overrides.php1 point
-
There are many ways to do this. You could use the API to loop through pages found via a selector, or perhaps you could use the AdminActions module's Page Manipulator action, or if the pages are all under the one parent you could use the BatchChildEditor module.1 point
-
1 point
-
My module was also adding other attributes beforehand, eg classes, but that was too restricting, and IDEs were complaining about missing img attributes. That's why I decided to rewrite it to generate the srcset attribute only that you can use the way you need. It even works with lazysizes bgset attribute that I often use.1 point
-
1 point
-
The new Console panel fullscreen mode now has keyboard shortcuts to collapse the code or results panes quickly. Here you can see me toggling all code, all results, and the split of both. Shortcuts are: CTRL+SHFT+ Up arrow - collapses code pane Down arrow - collapses results pane Left arrow - to restore split to what it was before you initiated any pane collapses. Hopefully you'll find this a really quick way to get to the view you need.1 point
-
I see, in this case I guess it's just two different approaches toward the same goal ? All your examples use the data-srcset attribute to be consumed by lazysizes, though the syntax is identical with the regular srcset attribute, so I'd say at the core the functionality is similar.1 point
-
I know this is an old thread, but since it looks unsolved I thought I would share what worked for me in case people are searching the forums. Here are the steps I took: renamed mystyles.js to customstyles.js located at site/modules/InputfieldCKEditor/customstyles.js edited customstyles.js to change the text from mystyles to customstyles.... CKEDITOR.stylesSet.add( 'custom-styles', [ edit the field settings for the field to point to the new customstyles.js The problem for me was that I needed to change the prefix as well from mystyles:/site/modules/InputfieldCKEditor/customstyles.js to customstyles:/site/modules/InputfieldCKEditor/customstyles.js They have to match the file name. Hope that helps someone.1 point
-
MarkupSrcSet doesn't use JS to generate the markup. I guess the JSON format that misled you, in fact the module was rewritten a year ago, see this post: https://processwire.com/talk/topic/12981-markupsrcset/?do=findComment&comment=1501291 point
-
Hi, thank you for the write up. ? For me it looks you found the right balance for beginners and experienced users. A following second part would be very nice and useful. Additionally to that, if you don't mind, I would be interested in a third part too. This one should cover how the function(s) recognize or act upon changes of a focus point or zoom value, if used. Does it recognize it, and recreate all variations? Do you also include / provide or already use a function to remove exactly the previously created variations? Maybe useful during developing a site, when my first used sizes need to be adjusted. (Otherwise I would have a lot unused variations). If you don't use this already, I think it could be done very easy by using a suffix for the variations.1 point
-
Also just as a heads up, I'm in the process of converting this site profile into a module, since I'm struggeling to keep it up to date on my sites and thus should be much easier to update in the future1 point
-
This post takes a comprehensive look at the new Verified URL Fieldtype added to the ProcessWire ProFields package. We also review updates for the latest version of ProcessWire, 3.0.112 on the dev branch: https://processwire.com/blog/posts/processwire-3.0.112-and-new-verified-url-fieldtype/1 point
-
I do, as processwire doesn't have this functionality out of the box. I need a way to review reported links or pages in the admin panel, when someone clicks to report a link, a new report is created for me to review. I have to review these links or pages in the admin panel. The only way for me to have a page in the admin panel where I review all the reported pages (or links), is to create a module. Believe me, you don't. If you'd followed my link, you would have realized that too. Ranting without reading the help given won't get you closer to a solution. As for the time issues you mention: there's always the choice between doing things quick&dirty and doing things right, especially in programming. Why do people build regular pages on Wordpress? Because they know it and get a quick solution to an immediate problem, but in the long run, they fight against the software they are using because it wasn't meant for this kind of thing. When I estimated that the time would, initially, be roughly the same for using PW and another framework, I was taking into account starting at zero with both, since there's yet no clickading thingy that plucks ideas from a brain and automagically adds it to a web page. ProcessWire is a framework too, and it also has its concepts and best practices. They aren't that many, but they need to be understood. The docs do give a good introduction to them, and there's plenty of information in the forum if you search for the keywords if you need more explanation. Here's a short step-by-step approach to solving your task without a custom module and just one non-core module: Preparations Download and Install the AdminCustomPages module from the module repository The template with the fields used to store reports Create a new field "reported_page" and set the field type as Page, in the settings select "single page or null page" Create a new field "report_message" and set the field type to Text Create a new field "report_read" and set the field type to Checkbox Create a new template "pagereport" in the backend and add the three fields you just created, ignore the message that there isn't a template file present The backend interface Create a file named "_pagereport_backend.php" in site/templates Go to "Page" and expand the tree to "Admin" -> "Pages", then click "New" next to that Set the title of the new page "Manage Reported Pages" and save. The template will automatically be set to "admin". Now, as the process for this template, select "ProcessAdminCustomPages". You can now select the "_pagereport_backend.php" file you created earlier. Click "Publish" The frontend page with the reporting form Create a file named "enterreport.php" in site/templates Create a new template and check "enterreport" Save Create a new page under "home", title it "Report Page" and select the template "enterreport" On the "Settings" tab, check "hidden" so it won't appear in searches and lists Publish You're all set up now, all you need is the PHP code and markup now. Edit "_pagereport_backend.php" and enter the following code, which will render a table with all reports, has links to the reported page and lets you directly mark them as read. It uses the core module MarkupAdminDataTable so you don't have to build the table manually. <?php if($input->get->mark_id_read) { $report = $pages->get($input->get->mark_id_read); $report->of(false); $report->report_read = 1; $report->save(); } $children = $page->children("sort=report_read, sort=-created"); $table = $modules->get("MarkupAdminDataTable"); $table->setEncodeEntities(false); $table->headerRow(array( "Reported", "Page", "Message", "Read" )); foreach($children as $report) { $table->row(array( strftime("%Y-%m-%d %H:%M", $report->created), "<a href='{$report->reported_page->url}' target='_blank'>{$report->reported_page->title}</a>", $report->report_message, $report->report_read ? "Yes" : "<a href='$page->url?mark_id_read={$report->id}'>No</a>" )); } echo "<h1>Reports to manage:</h1>\n" . $table->render(); Now, edit the "enterreport.php" file and add the code to enter a report: <?php if($input->get->page_id) { $pg = $pages->get($input->get->page_id); ?> <h1>Report page <?= $pg->title ?> (<?= $pg->url ?>)</h1> <form method="POST" action="<?= $page->url ?>"> <input type="hidden" name="page_id" value="<?= $input->get->page_id ?>"> Please leave a short reason why you are reporting this page (e.g. SPAM or offensive):<br /> <input type="text" name="report_message" size="80"><br /> <input type="submit" name="submit_report" value="Report this page"> </form> <?php } else if($input->post->submit_report) { $pg = $pages->get($input->post->page_id); $prnt = $pages->get("name=manage-reported-pages"); $report = new Page(); $report->template = "pagereport"; $report->parent = $prnt; $report->name = (new DateTime)->format("Ymd-Hisu"); $report->reported_page = $input->post->page_id; $report->report_message = $input->post->report_message; $report->save(); echo "<h1>Page '{$pg->title}' has been reported. Thank you!</h1><a href='#' onclick='window.close();'>Close this window</a>"; } else { echo "<p>No page given. If this problem persists when you try to report a page, please contant the administrator.</p>"; } All that's left to do is add the link to the report page somewhere in your template file, e.g. in a basic profile in _foot.php: <a href="<?= $pages->get("name=report-page")->url ?>?page_id=<?= $page->id ?>" target="_blank">Report this page</a> Creating a module would just mean wrapping most of the things I described here into an install routine and adding some shortcut routines (e.g. for rendering the markup that links to the report page form). You can now easily tweak the interface, e.g. by filtering the children in _pagereport_backend.php by "report_read!=1" to only show unread reports. You can add a field (e.g. the client IP) to the reportpage template and need only two lines in enterreport.php to render and save it and two in _pagereport_backend.php to display it (column header and value). Each report is actually a page underneath Manage Reported Pages, and you can even use PW's regular page tree to view or delete reports.1 point
-
Just thought I'd add the solution to this problem, for reference. Turns out there's a file called /site/assets/cache/LazyCron.lock (or something along those lines) preventing the cron functionality from working when there are PHP errors in my cron logic, which can happen during development of course. Fixing the errors and deleting the lock file restores functionality.1 point
-
The plugin's description links to this page, so I thought I should point out that there's an error in the code example above (MyHook instead of MyFunc). See: https://processwire.com/api/modules/lazy-cron/ for updated information.1 point