Leaderboard
Popular Content
Showing content with the highest reputation on 07/24/2020 in all areas
-
ProcessWire 3.0.163 adds a few new $pages hooks (see PW Weekly #323 for details), adds configurable module upload/install options (ProcessModule), and contains many other minor updates, code refactoring and optimizations. But by far, the majority of updates and commits are related to resolving more than a dozen recent issue reports. That will be the focus next week too, as the goal is to have the next master version out by the end of the month, or the first week of August. Priority focus is on any issues that might be bugs introduced between 3.0.148 (previous master) and 3.0.163, as we want to make sure at minimum we aren’t adding any new bugs from one master version to another. Regarding the new configurable module upload options, the intention here is to add additional safety by having the option of locking down the ability to install modules from the admin. As convenient as it is to be able to install and upgrade modules (during development) directly by URL, file upload or directory; the reality is that—depending on the case—it’s also not the safest thing to have on a client’s production site once development is finished. I think it’s best if module installation and upgrades are left to web developers, who are better equipped to resolve any technical issues that might arise during the process. Though it also depends on the installation, which is why I thought it should be configurable. So now you can specify which install options are available, and they can also depend on whether the site is in debug mode or not: $config->moduleInstall = [ // allow install from ProcessWire modules directory? 'directory' => true, // allow install by module file upload? 'upload' => 'debug', // allow install by download from URL? 'download' => 'debug', ]; Above are the current defaults, which can be changed by specifying your own preferred options in /site/config.php. Boolean true means always allowed, boolean false means never allowed, and string “debug” means: allowed if the site is in debug mode. (I’m currently debating on whether the ‘directory’ option should also be ‘debug’ as well.) In addition to these configuration options, the ProcessModule “New” tab now also provides instructions for manual installation of modules. None of us need it I know, but someone new to ProcessWire might see the prior “New” tab and not realize there’s a really simple and safe way to install modules from the file system. So the instructions just seemed to make sense there for consistency. ProCache 4.0 β released Last week I mentioned a new version of ProCache would be coming out this week and version 4.0 of ProCache was released on Wednesday in beta form. It’s available for download now in the ProCache board download thread. This is one of the biggest upgrades for ProCache yet. If you are upgrading from a previous version, read the upgrade instructions in the README.txt file included with it, as it will guide you through some of the new features, and may save you from having to make an update to your .htaccess file. I mentioned much of this in last week's post, but here’s a summary of what’s new in this version of ProCache relative to the previous version: Major refactor of entire module. Now native to ProcessWire 3.x (ProcessWire 3.0.148+ recommended). New .htaccess rules for static cache. New ability to select .htaccess version (v1 or v2, Tweaks tab). New option to specify how trailing slashes are handled (Tweaks tab). Upgrade SCSS compiler from version 0.7.8 to 1.1.1. Add Wikimedia LESS as additional option to Leafo LESS (select which you want on Tweaks tab). Improved per-template lifespan settings. Improved default and per-template behavior settings. New cache clear behavior: Family (parent, siblings, children). New cache clear behavior: References (page that reference saved page). New cache clear override: No-self (skip clearing page that was saved). Per-template behaviors now supports clearing specific pages by ID or selector. Numerous minor fixes and optimizations throughout. Removed direct .htaccess writing ability, replaced with live example file. Lots of new hookable methods for special cases. New “Tests” tab with the following built-in tests: cache function and performance; cache clear behaviors tests; test http response headers; test for GZIP, LZW, zlib/deflate or Brotli compression; Test for keep-alive connection. Thanks for reading and have a great weekend!12 points
-
Needed a really simple solution to embed audio files within page content and couldn't find a module for that, so here we go. Textformatter Audio Embed works a bit like Textformatter Video Embed, converting this: <p>https://www.domain.tld/path/to/file.mp3</p> Into this: <audio controls class="TextformatterAudioEmbed"> <source src="https://www.domain.tld/path/to/file.mp3" type="audio/mpeg"> </audio> The audio element has pretty good browser support, so quite often this should be enough to get things rolling ? GitHub repository: https://github.com/teppokoivula/TextformatterAudioEmbed Modules directory: https://modules.processwire.com/modules/textformatter-audio-embed/5 points
-
2021 update These things change and get tweaked over time, as stuff always does. We can make the size declaration a little more relevant. Declare a selection of sizes for your svg to "encourage" chromium-based browsers to use it instead of the larger bitmapped 192x192 and other assorted icons. We also need to thread the needle to ensure app start icons don't get confused and end up with an icon that is smaller than desired. Declare it last, after your png fallbacks - last one that is suitable size *must* get the nod. Counter-intuitively, declare that your svg is 150x150. (adjusted for 2021) ? "sizes": "48x48 72x72 96x96 128x128 150x150 256x256 512x512 1024x1024", { "name": "Alfresco bar + bistro, Eumundi", "short_name": "Alfresco", "description": "Relaxed outdoor dining on our spacious deck in Eumundi", "start_url": "/", "display": "standalone", "background_color": "#acbd72", "theme_color": "#acbd72", "icons": [ { "src": "https://www.alfresco-bar-bistro.com.au/site/assets/files/favicons/favicon-48x48.png", "sizes": "48x48", "type": "image/png", "purpose": "any" }, { "src": "https://www.alfresco-bar-bistro.com.au/site/assets/files/favicons/favicon-192x192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { "src": "https://www.alfresco-bar-bistro.com.au/site/assets/files/favicons/favicon-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { "src": "https://www.alfresco-bar-bistro.com.au/site/assets/files/favicons/maskable-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }, { "src": "https://www.alfresco-bar-bistro.com.au/site/assets/files/favicons/favicon.svg", "sizes": "48x48 72x72 96x96 128x128 150x150 256x256 512x512 1024x1024", "type": "image/svg+xml", "purpose": "any" } ] } Cross-Origin-Resource-Policy (CORP) manifest/icon bug A slightly-related testing note, if you are incorporating Cross-Origin-Resource-Policy (CORP) to enhance site security. Please note there is a chromium bug which affects chromium dev tools ability to properly display your manifest icons: Chromium DevTools bug: Cross-Origin-Resource-Policy prevents manifest icons from loading This long-term bug doesn't seem to affect the way the manifest itself works or the icons, just their display in Dev tools > Application > Manifest in Chrome, Edge, Brave, Opera... sheesh. Firefox Dev tools continues to happily display them without a problem. ------------------------------------------------------------------------------------ Quick tip for 2020 web manifests, which I couldn't find anywhere else. Wanted web manifest on Chrome/Edge to download the tiny favicon svg, instead of the larger 192x192 png it wanted to by default. Not a big deal, but mildly annoying: 15kb (non-scalable) png vs 1.5kb (infinitely scalable) svg. After a little head-scratching and fiddling around, came up with a solution that seems to do the trick. Declare it last, after your png fallbacks - last one that is suitable size *must* get the nod. Counter-intuitively, declare that your svg is 150x150. (adjusted for 2021) Mine isn't. Never has been, it's a scalable vector graphic. Simply declaring it is 150x150 in the manifest is enough to get the job done. Suspect the Chrome/Edge engine declares that any non-specified svgs are given that 150x150 size and anything else returns as a "fail". Without declaring a size on it, or declaring it as anything other than 150x150, it just wouldn't load. Weird, but works, so I'm happy ? { "name": "Longer App/Business Name", "short_name": "Short Name", "description": "Description of App/Business", "start_url": "/", "display": "standalone", "background_color": "#880000", "theme_color": "#880000", "icons": [ { "src": "favicon-192x192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { "src": "favicon-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { "src": "maskable-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }, { "src": "favicon.svg", "sizes": "150x150", "type": "image/svg+xml", "purpose": "any" } ] }3 points
-
You might need to disable the new(ish) function api. These are global functions and as the error shows you may not redefine those.2 points
-
In CSV linebreaks are used to separate individual rows, so a linebreak inside a cell may cause problems. Depending on your CSV parser it could result in an error altogether, it may silently discard the newline, or it may handle it fine. As far as I know fgetcsv can handle newlines as long as the cell is quoted properly. Can you post an example of your CSV? Make sure the fields are properly quoted. Regarding the paragraph tags, that problem is the result of bypassing the interface and importing directly to the database. The textarea field doesn't care if it contains HTML or regular text with linebreaks. The HTML structure is created by the CK Editor during editing, so if you don't want to edit all imported pages manually, you will have to convert your body fields programmatically. This involves just using str_replace or a regular expression to replace newlines with paragraph tags. You can do that before the import (by modifying your CSV), during the import (apparently you can hook ImportPagesCSV::importPageValue) or after the import (by using $pages->find to find all imported pages, iterate through them and change the body field). Here's a quick and dirty code sample: $text = $page->body; $textHTML = '<p>' . preg_replace("/[\n\r]+/", "</p><p>", $text) . '<p>'; $page->body = $textHTML; $page->save('body'); You can get very sophisticated with that, like converting single line breaks into <br> tags instead and multiple linebreaks into <p> tags, but it depends on your requirements and source data.2 points
-
Hello friends! I have another module for you, which will make your daily work as a Processwire developer easier. Introducing: AppApi This module helps you to create api-endpoints, to which an app or an external service can connect to. Features Simple routing definition Authentication - Three different authentication-mechanisms are ready to use. Access-management via UI Multiple different applications with unique access-rights and authentication-mechanisms can be defined The documentation has become quite extensive, so have a look at the Github repository for details: Installation Defining Applications Api-Keys PHP-Session (Recommended for on-site usage) Single JWT (Recommended for external server-calls) Double JWT (Recommended for apps) Creating Endpoints Output Formatting Error Handling Example: Listing Users Example: Universal Twack Api Routes Page Handlers File Handlers A special thanks goes to Thomas Aull , whose module RestApi was the starting point to this project. This module is not meant to replace this module because it does a great job. But if you want to connect and manage multiple apps or need other authentication methods, this module might help you. I am already very curious about your feedback and would be glad if the module helps you a little bit.1 point
-
Hello @ all ! Today I want to share another new inputfield with the community! It is called Fieldtype OpeningHours and it is designed to enter one or multiple times per day (especially for company opening times). I know that there is another great fieldtype in the repository (https://modules.processwire.com/modules/fieldtype-business-hours/), but I wanted to create my own with a different UI than the other one. Here is a screencast of what it looks like in action: OpeningHours.mp4 A lot of things going on behind the scenes and I dont want to write it all down here, because you can find the whole information on my Github account. https://github.com/juergenweb/FieldtypeOpeningHours Requirements: PHP >= 8.0 (because it uses union types, I have also tested it with new PHP 8.2) ProcessWire >=3.0.181 If you may find any bugs, have any ideas to improve this fieldtype please report it in my Github repository. Greetings from Austria and have a nice day! CHANGELOG: 21.7.20 Add new option to show (true) or hide (false) days with no opening hours on various methods (please be aware that setting options has been changed - it is recommended to deinstall old version and install this inputfield again) . Take a look at the READ.ME for further instructions. 1.1 Add multilang support for timeformat and add 2 additional Schema.org markup methods UPDATE: 09-06-2023: The module has been added to the module directory and can be downloaded from there after it has been published.1 point
-
This is a very beta version of the separate functions I use to generate srcset/bgset markups. There's some similar functions floating around in the forum but this one is a bit different because you can set "named image sets" which you can use easily site-wide. It's definitely work-in-progress so use it at your own risk. MarkupSrcSet Generate srcset and bgset markup for lazysizes. Features generate srcset/bgset markup for lazysizes set image sizes in JSON array add required JavaScripts automatically (optional) fallback to smallest image size if JavaScript is not available Usage Image sets JSON (in module settings): { "hero": [ [640, 210], [1080, null], [1920, null] ], "featured-image": [ [360, 240], 1.333, 2.667 ], "gallery-thumb": [ [240, 120], [480, 300], [800, 576] ] } Image methods $image->srcset(): <img <?php echo $page->featured_image->srcset('featured-image'); ?>> $image->bgset(): <div <?php echo $page->images->first()->bgset('hero'); ?>>Lorem ipsum</div> https://github.com/rolandtoth/MarkupSrcSet1 point
-
Hey @Macrura - looks like you've upgraded from a very old version. Please just save the module settings page and it will be gone.1 point
-
I didn't know the configurable module upload functionality was coming and this was an awesome way to end the week. Can't wait to play with it!1 point
-
PayPal is directly supported by Snipcart. You need to have a look into your Snipcart dashboard under „Payment Gateway“.1 point
-
Don't know this feature, but whats about: different PHP versions local and online? (or: different PHP extensions local and online?, that may be invoked by processing a base32 string.)1 point
-
There are related posts in the forums: just search for deepl in g**gle, specifying the PW sites forum. https://www.google.com/search?q=site%3Aprocesswire.com%2Ftalk+deepl BTW: the deepl service has to be paid, AFAIK.1 point
-
You need to go to your Snipcart dashboard: https://app.snipcart.com/dashboard and work through the setup steps there. After this configure SnipWire and it should work. Especially the "Configure your domains" part is important to tell Snipcart where it can find your product pages to allow the Snipcart crawler to verify your products.1 point
-
1 point
-
Greetings from the sunny covid hotspot state of Georgia, where we haven’t left the house since March. And now getting ready for the kids to start a new school year from home with virtual learning. Everyone delivers everything now, so there’s no need to go out to a grocery store anymore (or go anywhere). I live about a mile from the CDC, so our school district has more kids with parents working at the CDC than any other. That gives me some comfort, knowing that I won’t be sending my kids back to school until the experts at the CDC are willing to; when it’s really and truly safe. Though I don’t think it’s going to be safe for a long, long time. The US is a rudderless ship right now, so we just have to ride it out. Thankfully, we’re all staying safe and keeping busy. The kids are building houses in Roblox (an online game addiction they have), we’ve converted our yard to be a summer camp, and converted the basement to be a gym, while we clear more space to start building out a massive N-scale train set—my 3 locomotives still work perfectly, even after 35 years of storage. And I’ve been learning how to manage chlorine and PH in an inflatable kids pool that keeps the family cool in the hot weather. The kids miss school and other activities, my wife misses being at her office and people she works with, and we all miss our friends and family, but it’s the way things are now, and I’m just grateful to have my immediate family home and safe; and in place where we can ride out the storm. I’m also really glad that I can work on the ProcessWire core and modules for pretty much the entire work day, and enjoying coding as much as I ever have; feeling great about where ProcessWire is and where it’s going, thanks to all of you. I’ve been working on the latest ProCache version the entire week, so not many core updates to report today other than some new hooks added to the Pages class (they are hooks that the new ProCache can use as well). I’d hoped to have this version of ProCache finished by now, but I keep finding more stuff to improve, so decided give it another 2 days of work and testing, and if all looks good, it’ll be ready to release, which will be next week. This version is essentially a major refactor, where just about every line of code has been revisited in some form or another. But if you are already a ProCache user, you’ll also find it very familiar. While I don’t have it posted for download today, below is a brief look at what’s new. Completely new .htaccess rules (v2) that take up a lot less space, especially when using multiple hosts, schemes or extensions. Ability to choose .htaccess version (v1 or v2). ProCache now creates an example .htaccess-procache file that you can rename and use or copy/paste from. ProCache now has a built-in URL testing tool where you can compare the non-cached vs. cached render times. New setting to specify how ProCache delivered URLs should respond to trailing vs. non-trailing slashes in URL. Significant refactor that separates all ProCache functions into separate dedicated classes. Improved custom lifespan settings with predefined template lines. Improved behavior settings with predefined template lines and simpler letter (rather than number) based definitions. Ability to specify predefined cache clearing behaviors, specific pages to clear, or page matching selectors, from within the ProCache admin tool. New predefined cache clearing behavior: Reset cache for family of saved page (parents, siblings, children, grandchildren, and all within). New predefined cache clearing behavior: Reset cache for pages that reference saved page (via Page references). New versions of SCSS and LESS compilers. ProCache is completely ProcessWire 3.x native now (previous versions still supported PW 2.x even if 3.x was recommended). Numerous other improvements, fixes and optimizations throughout. I’ve previously mentioned a built-in crawler in ProCache. That part has been moved to a separate module called ProCacheCrawler and will be released a little later in the ProCache board. It was taking a little too much time to develop, so I didn’t want to hold up the rest of ProCache while I developed that. When installed, ProCache communicates with the crawler, identifying and adding URLs to a queue to be crawled and primed for the cache. What it does is pretty cool already, but it needs more time to develop. It’s also something that depends on being run regularly at intervals (like with CRON) so it’s a little bit of a different setup process than the rest of ProCache, which is another reason why I thought I’d develop is as a separate module. I’ll be working more on finishing development of the crawler later in the year, after the next master version of ProcessWire core is released. Next week I'll have the new ProCache version ready for download as well as a new core version on the development branch. It will focus mostly on fixes for issue reports as we continue working towards the next master version. Thanks for reading and have a great weekend!1 point
-
New EMO version 1.2.0 released! https://github.com/BlowbackDesign/EmailObfuscation/releases/tag/1.2.0 In this version only encryption key is stored in the emo object of html document and crypted email strings as data attributes to span elements that are used to replace found email addresses. This makes it possible to obfuscate emails generated within AJAX request. There is also a new option at module config to lock the encryption key so that it does not change on every session like it does by default. This is required if you are caching AJAX output for more than session lifetime. Otherwise this option is good leave disabled. Here is quick example of a simple AJAX request with obfuscated output: <?php namespace ProcessWire; if($config->ajax) { $str = "<p><a href='mailto:foo@bar.com'>Click to mail</a></p>"; // auto obfuscation works only when complete html document is rendered so you // need to do manual obfuscation on AJAX calls even when mode is set to auto echo $sanitizer->emo($str); return $this->halt(); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>EMO ajax example</title> </head> <body> <p>john@doe.com</p> <div id="result"></div> <p><button onclick="sendExample()">Show me some</button></p> <script> var sendExample = function() { var xhttp = new XMLHttpRequest(); xhttp.open("POST", "<?= $page->url ?>", true); xhttp.setRequestHeader("X-Requested-With", "XMLHttpRequest"); xhttp.onreadystatechange = function() { if(this.readyState == 4 && this.status == 200) { document.getElementById("result").innerHTML = xhttp.response; // run emo init when there's something new to digest emo.init(); } }; xhttp.send(); }; </script> </body> </html>1 point
-
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!1 point
-
Very nice writeup. Thank you for sharing! There are so many different approaches to handling responsive images and it is great to see how others do it. Would love to see more approaches from others here, too. To get the concept of responsive images, I read the anatomy of responsive images which explains them very well. Then I used to implement a quite similar approach to @MoritzLost for some time utilizing the MarkupSrcset module and also my own functions. Until I stumbled upon an article about creating responsive images which shows an approach that I now prefer. In this approach you go through all your different images in the finished layout and determine their exact respective size at your breakpoints. Then you build srcset and sizes attributes for those exact values. So there is no more approximation using scaling factors. You get exactly the amount of images at the sizes you need them. I pass an array with the breakpoints and the required sizes for that breakpoint and build srcset and sizes from that array. Here's the code: /** * Builds responsive img tag with srcset and sizes attributes * @param \Processwire\Pageimage $img * @param array $breakpointsizes array of associative arrays that describe media min breakpoints and width/height of images from the respective * breakpoint to the next * Example: * $breakpointSizes = [ * ['bp' => 320, 'sz' => [500, 0]], * ['bp' => 576, 'sz' => [992, 0]], * ['bp' => 992, 'sz' => [690, 0]] * ['bp' => 1200, 'sz' => [835, 0]] * ]; * @return string */ function renderImageResponsive($img, $breakpointSizes) { $imgSrc = $img->url; // use original image as fallback src $alt = ($img->description) ? $img->description : $img->page->title; // if no description use page title // construct sizes attribute $sizes = array(); foreach(array_reverse($breakpointSizes) as $bs) { $sizes[] = "(min-width:{$bs['bp']}px) {$bs['sz'][0]}px"; } $sizes = implode(', ', $sizes) . ', 100vw'; // construct srcset attribute $srcSet = array(); foreach($breakpointSizes as $bs) { $srcSet[] = $img->size($bs['sz'][0], $bs['sz'][1])->url . " {$bs['sz'][0]}w"; } $srcSet = implode(', ', $srcSet); return "<img class='img-fluid' src='{$imgSrc}' srcset='{$srcSet}' sizes='{$sizes}' alt='{$alt}'>"; } Obviously it does not account for Retina which needs to be added. I will also extend my function to add support for other attributes like you showed it. That is a great idea.1 point
-
Just updated the module to version 0.1. Almost everything in it has changed so reading the docs is a must The module does less "magic" as before, eg. doesn't add the "lazyload" class and data-sizes automatically. These should be added manually or you can write a wrapper function if you would like to simplify things (please notify me if you think you wrote one that should be part of the module). Also all the module settings are gone except the "Load scripts" checkbox because all other settings became useless after the rewrite. The new syntax looks like this (here with a helper to add a noscript tag for no-js fallback): <?php $img = $page->featured_image; ?> <img data-srcset="<?php echo $img->srcset('200x300,*2,*3'); ?>" class="lazyload" data-sizes="auto" alt="" /> <noscript> <img src="<?php echo $img->srcsetUrls[0]; ?>" /> </noscript> The main change is that you can easily tell what image sizes (sets) you need ("200x300,*2,*3" in the above example). Of course you can use WxH values for each set or use divisors too. What's more you don't have to start with the lowest image size, eg. "1200x900, /2, /4" is perfectly fine.1 point