Jump to content


Popular Content

Showing content with the highest reputation since 09/13/2019 in all areas

  1. 23 points
    Hi guys, today I want to share a little preview of my newly developed ModulesManager 2 module which is currently in beta. ModulesManager 2 provides an easy to use interface to download, update, install, uninstall and configure modules. It is meant to provide an optimized alternative to the ProcessModule dashboard. Maybe @ryan agrees to merge it to the core at some point when it is finished and polished. Features: Live-Search (aka find as you type) for categories Live-Search (aka find as you type) for module names Modern UIKit design Quick uninstall of a module Caches the module list from modules.processwire.com directory locally. There's a refresh button to get actual data. Preview video (with voiceover) Why a new module manager? Some people including myself think that the actual module installation in ProcessWire could be improved in many places. Make it easy for everybody! Lower the barrier for new users, and make it easier for existing users to find an install modules. That is one thing, that many other frameworks/CMS's have by default. Like ModX, WordPress or PrestaShop. What are the disadvantages of the actual core module interface? Installation of a module is not very user-friendly: You have to be aware where to get new modules, search for a module, copy or remember the module name or URL, go back to your ProcessWire installation, paste the module name , or URL, click on "get module info" and finally install the module It only displays installed modules, not the ones that are available in the modules directory, so it makes discovering modules hard. Uninstalling a module requires you to go to the module detail page, click a checkbox and then submit the change. After that you have to go back to the module overview page. It displays only limited information about the module History Back in 2012 Soma came up with ModuleManager, a module which displays modules as a table and provides functionality to download, update, install and configure modules (same as this module does). Sadly this module isn't maintained anymore (but it is still working) Then in 2019 Adrian came up with the idea of autocompleting the module name and Robin S developed the AutocompleteModuleClassName module which does this. This approachis a nice addition for devs who know which module they are looking for. But for all others we need a browsable experience, which provides more info than just the name. This was the perfect time for me to chime in, as I thought that module management is very cumbersome at its current state. A quick proof of concept / prototype was quickly developed. But then development slowed down, as I had to get more experience with vue.js first. Even as I was more experienced I stumbled into some problems that were to advanced for me to tackle. So I hired someone to help (yes, I even paid money for developing this module) TODO Install multiple modules at once like it is done in [ProcessModuleToolkit](https://github.com/adrianbj/ProcessModuleToolkit) add filter by installed/not installed / updateable / recommended Allow "search for module" to search in the description also, so a module can be found by its purpose and not only by its name Integrate the Readme or changelog of a module as it is done in [ModuleReleaseNotes](https://processwire.com/talk/topic/17767-module-release-notes/) This would have the following benefits: Make discovery of a module's changes prior to an upgrade a trivial task. Make breaking changes very obvious. Make reading of a module's support documentation post-install a trivial task. Make module authors start to think about how they can improve the change discovery process for their modules. Make sure the display of information from the module support files/commit messages doesn't introduce a vulnerability. I need your feedback This module is in development right now, and I am happy to discuss with you and get some feedback. I will publish it on github as soon as I have the time, so you can download it and analyze the code, and even provide pull requests. What do you like? What is missing? What could make the process even easier? Wink at @bernhard @adrian @Pete
  2. 17 points
    No blog post this week because I don’t have anything new or interesting to write about just yet (unless you like to hear about repairing fridges and clothes dryers). But I’ve been focused primarily on completing the FormBuilder updates that I mentioned in last week’s blog post, and it’s looking very good, though I’ve still got a little further to go with it. When it comes to multi-page forms, I’m trying to cover all of the exceptional cases where sessions expire, cookies clear, etc., and want to make sure a form-in-progress continues to work through all these situations. Multi-page forms can be potentially long and people can invest lots of time in them (relative to regular forms), so trying to make it all as resilient as possible. This takes lots of time developing and testing, so that’s what I’ve been doing. I’ll be doing some of that next week too, though also have been planning to dig back into the core issues repo and start working through some of those in preparation for a new master version this Fall. Hope you all have a great weekend!
  3. 16 points
    In the last few weeks... or almost months... I worked on a project for a restaurant. Sad to say that the company behind the restaurant went out of business before the project could be finished - or was paid or the website ever saw the light of day. As there is kind of a lot of work in it... but yet a lot of customization... I decided to create a site profile of it and make it public as ProcessWire Barebone Site Profile. Right now I'm stripping out every client detail and finish some functions and details that were planned a few weeks back so you can build right on top of it, if you like or want. Maybe it's only a playground for someone or an inspiration for how to f**k up data-mangement... but hey... better than a ZIP file on a backup drive. 😎 As the project was and is super tailor-made to that client, it may not work for a lot of restaurants out there, but nonetheless I don't want miss the opportunity to offer a foundation for upcoming restaurant projects you may face. The project had a huge focus on dishes, beer and wine variations so those templates are kind of feature-rich. You might want to rip out some of it as most restaurants don't focus that much on those details. Important details I want to be honest and clear about: this profile does NOT include a highly polished and usable frontend as the design is paid and therefore can't be made public the frontend will be a collection of re-usable snippets to show every possible detail of each page, item and whatever the whole site profile will be without any support - future updates aren't planned right now the site profile will be released in single-language setup only (english) existing translations (as seen in parts of the screenshots) will be changed to english existing data, for example dishes, will be replaced with demo content if you, one of your clients or anyone else wants to use this profile - feel free to do so Pro Modules were already stripped out of it only public modules were used and are included with the profile itself the full module list will soon be published on Github the full README will soon be published on Github the full LICENSE will soon be published on Github So... yes... that's just a short preview of an upcoming site profile. As mentioned before, the site profile will most likely never ever receive any future updates, so it will be available on Github only. It's not planned to publish this as a full featured site profile, yet. More details, screenshots and the site profile itself (of course) will be available soon. Questions? Please feel free to ask. Github Repository: https://github.com/webmanufaktur/pwbrestaurant
  4. 13 points
    Nginx' performance advantages over Apache were built on three factors: modern-day multiprocessing in the server, a lot less overhead due to reduced functionality and memory caching. Over the last five years, Apache has greatly reduced that gap by adapting Nginx' multiprocessing approach (one keyword there is the event MPM module), so Apache isn't spending most of its time spinning up and tearing down whole server instances anymore. File system access has greatly improved with solid state disks, too. Apache still has a lot more functionality, and its distributed config file approach, most prominently the ability to make configuration changes with a .htaccess file inside the web directories, hurts performance. Its dynamic module loading approach and the dozens of pre-installed modules most distributions ship also take up processing time and memory. Nowadays, Apache can be stripped down a lot and compiled to be head to head with Nginx, though few actually care to do that, since it also means removing functionality one might need in the future. A stock Apache is usually still quite a bit slower and reaches its limits faster (about the factor 2). This becomes an issue under heavy load or on slow machines. Where Nginx still shines brightly is load balancing. Apache can do it too, but with Nginx it is straight forward and well documented, having been there for a long time. For those interested in a bit of (highly subjective) history: for a long time (speak eighties and nineties), the classic forking mechanism that was common on *nix OSes was the way to do multiprocessing in network servers, and therefore in Apache too. This meant spawning a full copy of the server process and initializing it, then tearing it down when the request was done. Apache brought a small revolution to that approach by implementing preforking, meaning to keep spare server instances around to fulfill requests with little delay. After a while, there were other approaches too when faster multiprocessing approaches become part of common operating systems, like multi threading, which is supported by Apache's "worker" multiprocessing module (MPM). There were, however, big caveats with using other MPMs. Since file systems used to be slow, sometimes awfully so, in the old days, and since the classic CGI approach of starting an executable from the file system, supplying it with information through environment variables and standard input and capturing its standard output was a security nightmare - even without thinking about shared hosting - nifty programmers included full languages interpreters inside Apache modules. mod_perl and mod_php became the big thing, the latter coming to dominate the web after a few years. These interpreters, though, often had memory leaks and issues with thread isolation, meaning at best that an error in one thread tore down numerous other sessions and at worst that the server had a propensity for information leaks, remote code execution and privilege escalation attacks, the former security nightmare squared. Thus, these tightly integrated interpreters more or less locked their users into the classic prefork approach where every instance is its own, basically independent process. With PHP as the market leader not evolving in that regard, things were frozen for quite some time. This was when Nginx conquered the market, first by serving static HTML and associated resources with lightning speed (CMSes generating static HTML were still a big thing for a while), but soon by taking care of all the static stuff while handling the dynamic things off to Apache and caching parts of its responses in memory. Finally, though, PHP finally got a fresh boost and grew stable enough for its engine to re-use interpreter instances. It was easier to contain things inside an interpreter-only process instead of dealing with all the server peculiarities, so FastCGI daemons finally became stable, known and used, and suddenly the need to have the language interpreter contained in the web server fell away. Apache got leaner and Nginx more flexible. Caching servers like Varnish became popular since it suddenly was relatively easy to build a fast, nice, layered caching solution with a combination of Nginx, Varnish and a full fledged web server like Apache or IIS, able to serve thousands of highly dynamic and media rich pages per minute. About that time, SSL grew in importance too, and hosting providers learned to love Nginx as a means to route domains to changing backends and provide fast and easily configurable SSL endpoint termination. Over the last years, Nginx got other features like generic TCP protocol load balancing that offset it from other servers and make it more into a one-stop solution for modern web applications. It does boost its popularity that Nginx is often the first (or the first major) web server to ship evolving technologies, making the front pages and pulling in early adopters, http/2 being one of the most prominent examples there.
  5. 11 points
    As threatened in Ryan's announcement for 3.0.139, I built a little module for sliding toggles as a replacement for checkboxes. Styling of the input is CSS3 only (with all the usual caveats about older browsers), no JS necessary, and may still be a bit "rough around the edges", so to speak, since I didn't have much time for testing on different devices or brushing things up enough so I'd feel comfortable pushing it to the module directory. But here's the link to the GitHub repo for now: InputfieldSlideToggle Fieldtype and Inputfield that implements smartphone-style toggles as replacement for checkbox inputs. The visualization is CSS-only, no additional JS necessary. Status Still very alpha, use with caution! Features / Field Settings Size You can render the toggles in four different sizes: small, medium, large and extra large. Off Color Currently, "unchecked" toggles can be displayed either in grey (default) or red. On Color "Checked" toggles can be rendered in one of these colors: blue (default), black, green, grey, orange or red. Screenshots Some examples with checkbox label View all Size and Color Combinations Small toggles Medium toggles Big toggles Extra big toggles
  6. 8 points
    I just wanted to mention. I found a new job as a front-end dev at https://backslash.ch. Since 2 months there already and enjoy it. We work with in-house CMS specially targeted to governments. So a lot less PW for me in the future, but I'll use it as my tool for private projects.
  7. 8 points
    Hey folks! I've been a bit quiet here about Wireframe, but the thing is that I've finally got a couple of "real sites" (as in not my own projects) to implement it on, and that has brought up some new requirements and a few issues. I haven't had the time to dig into a whole lot of really new stuff (components and such), but I have made a lot of other updates to the framework. As such, Wireframe 0.6 was released late yesterday. (A release on Friday the 13th, consisting of a total of 13 commits – not being superstitious here.) I've been updating the docs at wireframe-framework.com as well, but some parts remain a bit outdated. A lot of what happened in this version doesn't really affect the use of Wireframe, but some things do – and one change may even break some existing implementations: It used to be possible to set the view file or layout from Controller with $this->view->layout = "some-layout" or $this->view->view = "json". This has been deprecated and removed in favour of a setter/getter API: $this->view->setLayout("some-layout") and $this->view->setView("json"), and getLayout() / getView() for reading the value. In addition to some obvious benefits from type hinting etc. this also makes it harder to accidentally change something when you thought you were just passing a variable to the View 🙂 There are now three ways to perform actions and pass data from a Controller to the View: Controller::init() (triggered as soon as the Controller class is loaded), public methods (which are made accessible in the View as $this->method_name), and a new addition Controller::render() (called right before the page is rendered). In most cases where one might've used the init() method before, render() is a better choice, as there's less chance that code will get executed "unnecessarily". In addition to existing Page::layout() and Page::view() methods there are now specific setters/getters: Page::setLayout("layout-name"), Page::getLayout(), etc. Original "unified setter+getter" methods may actually get removed at some point, as they're often ambiguous and make code arguably less readable. Quite a few other changes as well, some of which improve performance and others that make the API more polished. Some bug fixes too, though those mostly apply to what I'd consider "border cases" 🙂 Here's the full changelog for Wireframe 0.6.0: ### Added - New Page methods Page::getLayout(), Page::setLayout(), Page::getView(), and Page::setView(). - New Controller::render() method, executed right before a page is actually rendered. - New ViewData class for storing (internal) data required by the View class. - New getter/setter methods for ViewData properties for the View class. - New method Wireframe::getConfig() for getting current config settings. - New method ViewPlaceholders::has() for checking if a placeholder has already been populated. ### Changed - Various View-related features moved from Wireframe module and ViewPlaceholders class to the View class. - Removed access to local get* and set* methods via the PHP's magic setter method __set() and getter method __get() in the View class. - Redirect feature no longer fails if provided with a WireArray data type; in these cases the first item is used as the redirect target. - Improvements to PHPDoc comments. ### Fixed - An issue with Config class where the "all directories exist" message was sometimes displayed unintentionally. - An issue where View Placeholder values might've been overwritten because existence of earlier value was checked inproperly. - An issue where empty / null view file would be automatically replaced with value "default". On a related note, for this version I decided to give sonarcloud a try. For those who don't know it, it's a sort of a code quality inspector, and it's free for public projects. It didn't have a whole lot to complain about at this point, but it's good to have some sort of validation in place just in case. I will also be digging deeper into reported "code smells", as some of them definitely make sense to me 🙂
  8. 5 points
    I may have missed some of the points made here (sorry in advance), but regarding links opening in new tab/window: please keep in mind that this is a problem from accessibility point of view. For typical desktop users with decent eye sight etc. this is usually not a problem – and in fact it may be nice in some cases that a link automatically opens in a new tab – but for most screen readers (apps, that is – and thus their users as well) it's a real problem. From what I've heard/read, it can entirely mess up their understanding of the context. WCAG advises that if you're about to open a link in a new window, you should provide a clear signal that this is going to happen. This can, for an example, be a note at the end of the link text: "(opens in a new window)". If you care about accessibility, think twice before you use target="_blank". And for those cases where the client requests this, be sure to inform them that it will likely cause issues for some users. If the client still wants to go with it then by all means do it, but I've found this argument to resonate quite well with most sensible people 🙂
  9. 5 points
    Well... there is enough space in the forums for a DIY section. 😂
  10. 5 points
    Just stumbled across this...
  11. 4 points
    There is another option to use Processwire explode. As example: $selectors = "parent=$parent,template=page_car,dealer={$dealer_id}"; $cars = $pages->findMany($selectors); $manufacturers = $cars->explode("manufacturer"); Also later you can do and other things, $manufacturers->sort("title") etc... And there are and other nice WireArray methods like implode, filter, and many others... Regards.
  12. 4 points
    Thank you all for helping me with this! the culprit has been found. An “<a>” that should have been an “</a>” further up the page (which must have been there for months uncaught). Strange that today revealed symptoms of it for the first time! I feel silly, but I’m glad that’s over! thanks again!
  13. 4 points
    Teppo, I'm writing my first message on this forum (hi!) to thank you for this module. I'm testing it on a small local test site at the moment, and really liking it so far!
  14. 4 points
    @teppo @wbmnfktr Wow this discussion derailed a bit, but here we are ^^ Teppo, thanks for the pointers regarding target blank, I didn't really have that on my radar. Makes perfect sense though, I'll definitely reconsider my current approach! Even more off-topic, but I feel a bit smug now about having isolated that logic into a twig template in my current project, so now I can change it in a central place: {# blocks/link-start.twig #} {#- # Renders a start tag for a link. The link will automatically have target="_blank" # and rel="noopener" if the link leads to an external domain. # # @var string url The target (href) for the URL. # @var bool is_download Should the link be marked as a download (for direct file downloads)? # @var array classes Optional classes for the anchor. -#} <a href="{{ url }}" {%- if is_download is defined and is_download %} download{% endif -%} {%- if classes is defined and classes is not empty %} class="{{ classes|join(' ')|trim }}"{% endif -%} {%- if isExternalUrl(url) %} target="_blank" rel="noopener"{% endif %}> I'll just get rid of the last part 🙂 Thanks to everyone participating in the discussion! I think it's great how different perspectives come together that provide everyone new insights & best practices ...
  15. 4 points
    @teppo's argument here is solid and I thought... cool... just add some details to those links with CSS-magic: a[target=_blank]:after { content: " (opens in a new window)"; } BUT... not all screenreaders support that feature... all I found was kind of a mixed result. https://www.powermapper.com/tests/screen-readers/content/css-generated-content/ Just in case for those who had a similar idea. 😁
  16. 4 points
    One of my client's website is running on Nginx : https://ricardo-vargas.com It works, but I think a better approach in most cases is to run Nginx as a reversed proxy with Apache serving the files, like I did for my other client: https://www.brightline.org. You get the benefits of easier configuration and a better performance (compared to only run Apache).
  17. 4 points
    I've encountered users who don't even know what a tab is, and are confused when they cannot get back to the site they were on before by just hitting the back button! It's no use keeping your site up in the background if your users don't know how to get back to it 🙄 So on principle I agree with @adrian on this, but our clients still keep asking for target="_blank", so that's that.
  18. 3 points
    Hello for all, and for my friend @Leftfield Sorry, but I need to say that I agree with @wbmnfktr in your previous post ("... why do you want/need such a weird URL?")? Here is one solution (as option) with default Processwire (no hooks, custom modules or core hack): 1) template: product, fields: title, details (page table with auto name date format) 2) child template: details, fields: image, color, size, price # With that you will get paths (URL's): # Backend administration: # Page tree: There is always option to develope custom module with different UI (and Ajax processing) where user in one "container" write title (parent page), and in another product details (child page). For users all that can looks just like a single UI form that they don't know that are 2 pages. Right now, I see this as one option (or something in this direction) how to get that what you want, but also in all that respect PW core. But need to think is this all worth it (complexity in backend, and later in frontend..).
  19. 3 points
    *pops head up* I seem to be getting a few more ecommerce enquiries nowadays so happy to test when you're at that stage.
  20. 3 points
    @CrazyEnimal, please insert your code inside a code block in forum posts. You can use SQL in ProcessWire when it suits you. That's what the $database API variable is for. Here is one way you could get a listing of manufacturers with the number of occurrences within a selection of cars. // Get the IDs of the cars $car_ids = $pages->findIDs("parent=$parent, template=page_car, dealer=$dealer_id"); // Get table for manufacturer Page Reference field $table = $fields->get('manufacturer')->getTable(); // Get manufacturers that are selected in the car pages $query = $database->query("SELECT data FROM $table WHERE pages_id IN (" . implode(',', $car_ids) . ")"); $manufacturer_ids = $query->fetchAll(\PDO::FETCH_COLUMN); // Count how many times each manufacturer occurs in the results $manufacturer_occurrences = array_count_values($manufacturer_ids); // Sort the results in order of occurrences, highest to lowest arsort($manufacturer_occurrences); // Get the manufacturer pages $manufacturers = $pages->getById(array_keys($manufacturer_occurrences)); // Output a list of manufacturer titles and the number of occurrences foreach($manufacturers as $manufacturer) { echo "<p>{$manufacturer->title} ({$manufacturer_occurrences[$manufacturers->id]})</p>"; }
  21. 3 points
    While I also mostly use the alternative syntax for the very same reason, it is also worth noting that (at least on the frontend) Tracy Debugger's Validator panel is quite handy. I keep using it all the time.
  22. 3 points
    AdminBar 2.4.0 is out and adds support for the "data-adminbar-adjust" attribute. The idea here is to automatically modify (or adjust) certain CSS properties whenever the height of the Admin Bar is recalculated. Note: AdminBar already automatically adds "padding-top: [Admin Bar height in px]" to the <html> element, so this feature mainly applies to elements with "position: fixed". Assuming that Admin Bar is displayed and is 100px tall at the moment, the following markup... <div data-adminbar-adjust="top max-height"></div> ... would result in this: <div style="top: 100px; max-height: calc(100% - 100px);" data-adminbar-adjust="top max-height"></div> Thanks to @Fokke for the idea 🙂
  23. 3 points
    This worked for me: <?php namespace ProcessWire; include("../index.php"); //bootstrap ProcessWire // var_dump($wire->pages); $http = new WireHttp(); var_dump($http);
  24. 3 points
    I'm pretty sure I've seen something similar happen when nesting an <a></a> inside another <a></a> - looks "fine" in page source but the browser attempts to escape them or something and it displays differently in the inspector. Steps I would go through is to to replace each var value with a blank string, go through each methodically, or maybe remove each <a></a> and add each one back in to identify which one is triggering it. You might find it completely unrelated to the above, but it'll get you closer to the source of the issue! PS - Grammarly browser extension and CKEditor 4 don't mix well. Had to ask a number of clients and colleagues to disable it so they can actually save content.
  25. 3 points
    @Leftfield I think that URL segmetns is the way to go. wire()->addHook("Page(template=product)::path", function($e) { $page = $e->object; $e->return = "/{$page->name}/pr/{$page->hash_field}/"; }); Then you have to enable URL segments for your home template. And in template file if (input()->urlSegment(1)) { $pagename = input()->urlSegment(1); $hash = $input()->urlSegment(3); $match = pages()->findOne("template=product, name={$pagename}, hash_field={$hash}"); if (!$match->id) throw new Wire404Exception(); echo $match->render(); return $this->halt(); }
  26. 3 points
    Maybe take a look at URL segments? https://processwire.com/docs/front-end/how-to-use-url-segments/
  27. 3 points
    Never tried it with the core module but Croppable Image 3 works really good and has some nice additional settings and features. Maybe worth a try.
  28. 3 points
    https://vitalydidenko.com https://skyscrapers.nurgulyashyrov.com/ We use Nginx for all PW websites, but these are the ones I have on my own servers. Please DM me if you find any security issues.
  29. 3 points
    @pwired I'm not sure if you really know what or about what you are talking? But I definetly know that I really dislike such sort of posts. Please revert back to kindly posts only. And, if you want to answer me to this post, please do it via PM to me. (I'm into weekend now and only can answer on monday next week, but will definetly do.)
  30. 3 points
  31. 3 points
    The easiest answer: you upgraded your local environment a few weeks ago. The more complicated answer: well... yes... don't know. Never had this kind of issue with ProcessWire itself.
  32. 3 points
    The easiest I could think of is to export your models into CSVs using this exporter app or by code with this library, and then upload them using ProcessWire's CSV import module. It would require some planning on how you would map your existing data into ProcessWire tree, and its field/template/page paradigm, but it shouldn't be too hard once you've figured that out.
  33. 3 points
    I think it's great! I've already incorporated it into my current project, and it has a sizable performance impact on Chrome without any real work on my part. I always thought removing the src attribute and setting it with JavaScript was an antipattern, as anyone with JavaScript disabled wouldn't see any images at all, so it's good to see native support for lazy loading. It's progressive enhancement, so you don't need to have support from all browsers. Support doesn't look half bad though, even though only Chrome supports it at the moment (see caniuse), that's already a sizable chunk of the population. Also, caniuse currently lists Chrome on Android, which is huge (36 %) as not supporting it, I'm not sure that's correct. According to the Chrome Platform Status page, native lazyload is already supported in Chrome on Android. That would get the feature to >50 % browser support already. On current Android versions, all WebViews are provided by Chrome, so it will work in all in-app browsers on Android as well. Also, you can very easily polyfill the native lazyload with JavaScript (see article above), so everyone will profit from this without much of a downside!
  34. 3 points
    Hi @Hardoman I post you a code example below that works for me since ages and also with recent PW versions. It includes watermarking too! It is called in a custom module and the event is >before "InputfieldFile::fileAdded" <, but you can call it in ready.php too. Hopefully it is of help for you. Otherwise please ask further. :) public function importImage($event) { $inputfield = $event->object; // handle to the image field if(!$inputfield instanceof InputfieldImage) { // we need an images field, not a file field return; // early return } if(version_compare(wire('config')->version, '2.8.0', '<')) { $p = $inputfield->value['page']; // get the page, PW < 2.8 } else { $p = $inputfield->attributes['value']->page; // get the page, PW >= 2.8 | 3.0 (or only from 3.0.17+ ??) } if('images' != $inputfield->name) return; // we assume a field with name: images if('album' != $p->template) return; // don't do it on other pages than archive album $image = $event->argumentsByName('pagefile'); // get the image // prebuild variations // AdminThumb $image->height(260); // AlbumThumbnail $portrait = $image->height > $image->width; $w = 228; if($portrait) { $w1 = intval($w); $h1 = intval(($w1 / 3 * 4) + 38); } else { $w1 = intval($w); $h1 = intval(($w1 / 3 * 2)); } $image->crop("width=$w1, height=$h1"); // Slick-Slideshow $wmPng = $this->pages->get('id=13967')->getUnformatted('watermark')->first()->width(403); // sharpening added, quality from 80 to 90 $master = $image->contain('width=1000, height=700, quality=100, sharpening=none'); $master = $master->pim2Load('full', false)->watermarkLogo($wmPng)->setQuality(100)->setUpscaling(true)->setSharpening('none')->pimSave(); $sizeArray = array(array(448, 336), array(678, 506), array(908, 676)); foreach($sizeArray as $sizes) { $master->size($sizes[0], $sizes[1], array('upscaling'=>false, 'cropping'=>false, 'quality'=>90, 'sharpening'=>'soft')); } // sharpening added, quality from 80 to 90 // prebuild variations // check / import IPTC data $additionalInfo = array(); $info = @getimagesize($image->filename, $additionalInfo); if($info !== false && is_array($additionalInfo) && isset($additionalInfo['APP13'])) { $iptc = iptcparse($additionalInfo["APP13"]); if(is_array($iptc) && isset($iptc["2#025"]) && is_array($iptc["2#025"]) && count($iptc["2#025"])>0) { $tmp = $iptc["2#025"]; $tags = array(); foreach($tmp as $k=>$v) { if(empty($v)) continue; $tags[] = jhpTextConversion(trim(strtolower($v))); } $p->images->trackChange('tags'); // prepare page to keep track for changes $image->tags = implode(', ', $tags); $p->save('images'); // save the page } } // check / import IPTC data }
  35. 3 points
    I know I am getting OT, but I almost never use target="_blank" anymore. Here are a few thoughts: https://css-tricks.com/use-target_blank/ I think the key thing for me is that it's really hard to prevent opening a new tab when it is used, but it's really easy for the user to choose to open in a new tab if they want, so my rule is that unless the user will lose unsaved form data as a result of opening a link in the same tab, I never force a new tab.
  36. 2 points
  37. 2 points
    Assuming the categories are assigned to locations by a Page Reference field named "categories": $locations = $pages->find("template=location"); foreach($locations as $location) { foreach($location->categories as $category) { echo "<marker title='$location->title' category='$category->title'/>"; } }
  38. 2 points
    aaah, the missing 'namespace ProcessWire;' was the problem. thx!
  39. 2 points
    Here's a demo that loosely reproduces your screenshot: https://codepen.io/jacmaes/pen/qBWJrRY
  40. 2 points
    Hi! I have an issue when I upload an image into the field. This is one of the log messages ("matalampi" is the crop setting😞 /../site/assets/files/1328/testiimagenodashes-1.-matalampi.0x48.jpg - Unable to copy /../site/assets/files/1328/testiimagenodashes-1.-matalampi.jpg => /../site/assets/cache/WireTempDir/.PFM0.06231100T1568804893RIbnRkmpEmZApFan/0/testiimagenodashes-1.-matalampi.0x48.jpg Same error comes from all variations. Apparently the field tries to create file variations from the image immediately after upload, but for some reason it doesn't work and a corrupted file is created. This becomes a problem when I try to check if there exists a cropped version of the image: It thinks that there is one, but since it's corrupted, image doesn't show in site. Is there a way to prevent variations being created before user actually crops the image? var_dump of image, if it helps anything ("matalampi", "korkeampi" and "normaali" are crop settings😞 object(ProcessWire\Pageimage)#370 (13) { ["url"]=> string(71) "/../site/assets/files/1448/img.-normaali.jpg" ["filename"]=> string(100) "/../site/assets/files/1448/img.-normaali.jpg" ["filesize"]=> bool(false) ["description"]=> string(0) "" ["tags"]=> string(0) "" ["created"]=> string(18) "17.9.2019 14:09:57" ["modified"]=> string(18) "17.9.2019 14:09:57" ["filemtime"]=> string(17) "1.1.1970 02:00:00" ["width"]=> int(0) ["height"]=> int(0) ["suffix"]=> string(0) "" ["original"]=> string(34) "img.jpg" ["variations"]=> array(5) { [0]=> string(40) "img.0x260.jpg" [1]=> string(50) "img.-matalampi.0x48.jpg" [2]=> string(50) "img.-korkeampi.0x48.jpg" [3]=> string(50) "img.-normaali.670x0.jpg" [4]=> string(49) "img.-normaali.0x48.jpg" } } "filemtime" looks kinda fishy: Does it affect anything? Edit: Aaand as soon as I decided to ask this, it seems that the issue was that my crop setting names had capital letters! So heads up for that to others as well 😄
  41. 2 points
    OK. Here it is. ProcessWire 3.0.123 PHP 7.2.18 1. In "product" template turned on "Allow URL segments". 2. Made text field: "hash_field" and added it to product.php template. Set visibility to: Open when populated + closed when blanked + Locked (not editable) In site/ready.php <?php namespace ProcessWire; //* conf segments of URL of template product *// wire()->addHook("Page(template=product)::path", function($e) { $page = $e->object; $e->return = "/{$page->name}/pr/{$page->hash_field}/"; }); Made a module "AddHashField" with simple hash CRC32 to populate "hash_field" so the URL will be as short as possible. <?php namespace ProcessWire; class AddHashField extends WireData implements Module { public static function getModuleInfo() { return array( 'title' => 'Add Hash Field', 'summary' => 'Populate hash field of product template', 'href' => '', 'version' => 001, 'permanent' => false, 'autoload' => true, 'singular' => true, ); } public function init() { $this->pages->addHookBefore('saveReady', $this, 'AddHashField'); } public function AddHashField($event) { $page = $event->arguments[0]; if($page->template != 'product') return; if(!$page->hash_field){ date_default_timezone_set('Europe/Podgorica'); $new_date = new \DateTime(date('Y/m/d 00:00')); $formated_date = $new_date->format(YmdHi); $hash = hash('CRC32', $formated_date); $page->set('hash_field', $hash); $this->message("Hesh is set " . $page->hash_field); } } } Now, I am using 'views/' folder for templating so, instead of using home.php I am placing this on the top of the _main.php file. <?php namespace ProcessWire; if ($input->urlSegment1 && $input->urlSegment2 == 'pr' && $input->urlSegment3) : $pagename = $input->urlSegment1; $hash = $input->urlSegment3; $match = $page->findOne("template=product, name={$pagename}, hash_field={$hash}"); if (!$match->id || !$match->hash_field) throw new Wire404Exception(); include("./head.inc"); include('./nav.php'); include('views/' . $match->template->name . '.php'); include("./foot.inc"); // echo $match->render(); // problem with looping page endlessly + fatal error return $this->halt(); endif; NOTE: echo $match->render is causing massive looping until Fatal Error. I don't have idea what is causing that. Tried to turn off xdebug etc. Special THANKS to @Zeka
  42. 2 points
    Ok my feedback is mainly just UX/UI 1. I love the cards layout and how much info is presented. It's informative without being crowded. I think a list view is also needed too and a user should be able to flick between two. Particularly when viewing modules with an update, I want to quickly scan down a compact list and see what might require an update. 2. On the filters for 'Show only installed', 'Show only uninstalled', 'Show only updateable, 'Show most recommended', 'Picked', I wonder if these could be a little more compact and less wordy. IE a simple dropdown with a title of Display and then 4 shorter options of Installed Uninstalled Updatable Most recommended Picked Happy to do a quick mockup/wireframe if that's any use to you. 3. Will 'Show only uninstalled' display modules I have installed and subsequently uninstalled or display everything that's not installed? Might be a better label for this?
  43. 2 points
    Since this thread was started a hasPage property has been added to Inputfield instances in PW. So you can do this: $wire->addHookBefore('Inputfield::render', function(HookEvent $event) { /* @var Inputfield $inputfield */ $inputfield = $event->object; $page = $inputfield->hasPage; if($page) { // Use $page bd($page->name, 'page name'); } });
  44. 2 points
    It's like someone is putting wire between some processes. Oh... wait...
  45. 2 points
    https://www.litespeedtech.com/products/cache-plugins Where is the ProcessWire LSCache module 😊?
  46. 2 points
    So true. It's a nice idea, but CSS content is indeed problematic. Sometimes you want to add otherwise meaningless (visual) content (like icons) with CSS content and it turns out that some screen readers read them out loud, which can be quite confusing. There's no aria-hidden for CSS content, so reliably hiding said content from screen readers can be a real hassle. On the other hand there are times when you actually want to provide content for screen readers with this technique – yet it turns out that some of them will happily disregard it. In my experience it's almost never a good idea to add content with CSS 😅
  47. 2 points
  48. 2 points
    Not "easier", but fun to dive into: if you have MySQL >= 5.7.8, you could convert the page data into JSON and store that string in the database in an InnoDB table with a JSON column when you save the page. Then use MySQL's JSON_SEARCH function to get the hits and fields (paths). This only works with exact or LIKE queries, though, and you will have a little additional work to convert the returned paths to field names. Of course, you will probably still have to write the nested loops to get the data into the database. Not sure how much FieldtypeRepeater->exportValue() can help you avoid that. Table create statement example: CREATE TABLE jsonsearch ( pages_id INT NOT NULL, pagedata JSON, PRIMARY KEY(pages_id) ) Engine=InnoDB; Hypothetical page data for equally hypothetical page 1013 as stored in JSON: { "name": "vehicles", "title": "Our Vehicles", "data": [ { "type": "motorbike", "color": "blue" }, { "type": "car", "specs": { "colors": [ "red", "green" ], "subtype": "motorbike" } }, { "type": "bike", "color": "black" } ] } Query example: /* Get page id and fields where any field's value equals "motorbike" */ select id, json_search(jsondata, 'all', 'motorbike') as paths from testjson where json_search(jsondata, 'all', 'motorbike') is not null; /* Output: id paths 1013 ["$.data[0].type", "$.data[1].specs.subtype"] */ /* Get page id and fields where field's value contains "bike" */ select id, json_search(jsondata, 'all', '%bike%') as paths from testjson where json_search(jsondata, 'all', '%bike%') is not null; /* Output: id paths 1013 ["$.data[0].type", "$.data[1].specs.subtype", "$.data[2].type"] */ It also allows searching by a field name and by full and partial paths. /** * Search all "type" fields in the page's "data" array (repeater) for the string "bike". * This would find results with bike contained in a field data.type but not in * data.relatedvehicles.type */ select id, json_search(jsondata, 'all', '%bike%', NULL, "$.data[*].type") as paths from testjson where json_search(jsondata, 'all', '%bike%', NULL, "$.data[*].type") is not null; /* Output: id paths 1013 ["$.data[0].type", "$.data[2].type"] */ /** * Search all "type" fields at any level for the string "bike". * This would find results with bike contained both in data.type AND in * data.relatedvehicles.type */ select id, json_search(jsondata, 'all', '%bike%', NULL, "$**.type") as paths from testjson where json_search(jsondata, 'all', '%bike%', NULL, "$**.type") is not null; /* Output: id paths 1013 ["$.data[0].type", "$.data[2].type"] */ Converting the JSON path syntax back to something that makes sense for PW would just mean to json_decode() the paths value, cut off the first two chars and remove the digits within curly braces (though the numeric value are of course the index number for calling eq() on a Repeater/PageArray to get the individual item).
  49. 2 points
    Thanks all. I'll be working with a long time PW community member on this. Case closed.
  50. 2 points
    Hey folks – a quick update: Wireframe 0.5.0 was released couple of days ago. Compared to 0.4.x this version mainly fixes bugs and improves performance – nothing particularly major, but if you're using Wireframe, it's a recommended update. On a loosely related note, Tracy Debugger has been really helpful in figuring things out, identifying bottlenecks, etc. Brilliant module 🙂
  • Create New...