Search the Community
Showing results for 'video'.
-
Hey all, My projects involves more and more video. For a couple of years I've been self hosting mp4's. It works okay but it's always a hassle with determining the bitrate and getting the clients to understand that kind of thing. I don't want to go back to the bad old days of embedding. Mux.com looks good, but then all video material is locked up in their servers and it's hard to move. I don't love the idea to be tied to them forever. My dream would be to have something similar to image->width(500), such as video->bitrate(3000) for 3mb per second, for example. So that the editors can upload whatever video file. I think this would be not that hard to achieve with ffmpeg? I've been toying with the idea to do something like this myself. Anyone else? J
-
There’s not a huge amount to tell (currently), we’re using it in a very simple way. So if a client needs a lot of video on their site we’ll provision a library for their site in our Bunny account rather than using Yotube embeds (or similar). We then embed videos to the site using a ‘video block’ in repeater matrix with the video loaded by its Bunny ID. Specifically we use Bunny’s stream platform which provides encoding to multiple formats/resolutions/sizes for different devices, analytics, geolocation options, customisable player etc and then optimised delivery to the site visitor. At the moment video upload is manual via their dashboard, however they have a full upload API hence the plan to explore doing this via a module soon. They do provide things like edge caching, DNS and a standard CDN too (I believe Stream is built on top of their CDN) which I why I think it could be used with ProCache. I’m just not sure if you go down this route how you simultaneously achieve the benefits of the stream player etc - I still need to test this to determine if a separate module is needed or not.
-
Hi all, I've been doing some work to extend this great module created by @nbcommunication to meet a specific need we have. This is very much as-yet untested in production, but I wanted to share it here now anyway in case anyone wants to point out any flaws in the logic, try out the changes or suggest improvements before we push it to any live sites or make a PR to NB. The challenge: We manage a number of sites where we would like to get forms behind Turnstile. But in many cases these forms are connected to or hosted by third-party CRMs/Email marketing platforms etc which makes integrating Turnstile tricky and time consuming. The proposed solution: Rather than modifying a plethora of different third-party form integrations 1-by-1 and verifying submissions with Cloudflare on submit, we'll remove the forms from the DOM and only render them once Turnstile verifies the site visitor. As mentioned this is as-yet untested so I've no idea how robust this will be against bots and scrapers, but it seems worth trying IMO! In case people are wondering why we wouldn't use Cloudflare's other bot mitigation tools - we don't control all site's DNS and not all sites run behind Cloudflare. Turnstile works regardless of whether the site uses Cloudflare nameservers. The approach: I've modified the Turnstile module with a few new methods to do the following: Utilises @ryan's awesome URL hooks to create some endpoints to handle Turnstile token verification and: Dynamic loading of HTML render files into a page only once the token is verified. Use of data-attributes in page elements to specify the target location and file for rendering. Forked branch available here: https://github.com/warp-design/MarkupCloudflareTurnstile/tree/feature-protect-content-turnstile All probably best illustrated visually: Example page structure using a mix of the existing module methods and the new ones: <body> <?php //Load module in page $captcha = $modules->get('MarkupCloudflareTurnstile'); ?> <!-- Existing module widget render call. Set inside #turnstile-container to auto hide turnstile after verification --> <div id="turnstile-container"> <?= $captcha->render(); ?> </div> <!-- Elements you wish to protect with Turnstile. Set data attribute with render file path, relative to templates folder. There divs will populate with the specified render files when Turnstile verifies. --> <div class="turnstile_content" data-render-path="inc/exampleForm"></div> <div class="turnstile_content" data-render-path="inc/exampleContent"></div> <!-- New method to load the scripts if you want to protect dom elements behind Turnstile --> <?= $captcha->renderProtectedContent(); ?> <!--Existing module script call--> <?= $captcha->getScript(); ?> </body> And how this page looks live. Note: .turnstile_content elements only populate when Turnstile is verified: Test - 11 December 2024 - Watch Video Let me know what you think - is this a workable approach, or am I overlooking pitfalls? Likely issues/TODOs you may encounter: Things may need more sanitization and better error handling to come out during testing. Not sure how this will behave when loading in external JS. I've tried to ensure it will initialise any scripts in the render files when they are loaded. In theory things shouldn't be too different to loading content with frameworks or something like HTMX. Would be nice to eventually add support for passings $vars and $options params to the render files like any other PW template call. Someone may have already implemented the above in another module and I hadn't noticed! 😅
-
File upload field cutting file size and file content
Vladislav Zharkov replied to a-ok's topic in General Support
I know I'm bumping an ancient thread, but I just recently ran into this exact issue, and it wasn't limited to just videos, but pretty much all file types, so I thought I'd share my solution. I was building a personal portfolio for a client and their website is hosted on DreamHost. Each time I uploaded a background video (8MB MP4 file) it would hang at 100% for a while, finish the upload and then show its size reduced to 2.5MB. Weirdly enough the video did play, but only a portion of it. This issue caught me off guard because I'm also running ProcessWire on a different hosting, and I've never encountered this problem before. I skimmed through phpinfo thinking that the upload limit was set too low, but it far exceeded the file size. I decided to upload a big image file (5MB JPG) somewhere in the gallery to see if it'll work, and the same issue happened. It got cut precisely at 2.5MB, thus getting corrupted, yet still displaying. So I decided to finally contact the support. Turns out it was ModSecurity acting aggressively, throwing the "Request body no files data length is larger than the configured limit" error. Support increased the limit and the issue was gone. So yeah, if anybody else ever encounters this problem, try increasing the ModSecurity file size upload limit. -
Season's Greetings ProcessWirers! I hope you enjoy the gift of this module, but use with care... TLDR: This module captures changes made in the development environment so that they can be easily migrated to the live environment without needing to specify the changes or write any code. The demo below gives a brief overview. Want to read? Read on. One of the (few) problems with ProcessWire, in my opinion, is the lack of any native way of handling migrations. Given that PW is such a powerful tool capable of sophisticated and complex web-based applications, this is less than ideal. There is a solution, however, in RockMigrations which accomplishes a lot in a controllable way, provided you are happy to specify your database set-up in code rather than via the UI (albeit that the latest versions allow you to grab much of the required code from the UI). If that suits your need, great. Around the same time as the first versions of RockMigrations, I started developing my own UI-based migrations module, which I have been using with reasonable success for some time. I halted development of the module for a while as RockMigrations developed and I considered switching to that route. However, I decided that my module suited me better and that a real improvement could be made if it was effectively automated so that I no longer needed to specify a migration. So that is exactly what it does: after configuring the module, you add a new migration page with ‘log changes’ enabled (which includes determining what types of objects are relevant for the migration) and work on your development system. Once you have made the desired changes (and tested them!) in the development environment, you go back to the migration page where it has magically captured the objects which have changed and listed them in dependency order. You then ‘export’ the changes, which creates json files to be uploaded to the live environment (via Git or FTP etc.), where they are then ‘installed’ to re-create the changes in the live system. The demo below illustrates this briefly. This first demo shows the creation of a migration. The installation demo will be in the next post, because of size constraints. See post 4 for HD video. Video-source small.mp4 There is a very extensive manual which covers all the features of the module, not just this ‘automatic’ method. Available on github at https://github.com/MetaTunes/ProcessDbMigrate and in the modules library here. PLEASE NOTE that this is still in 'alpha'. Do not use in production without fully testing and backing up at every stage. It is quite complex so, although I have tried hard to eliminate bugs, there will inevitably be some left!
- 43 replies
-
- 22
-
Hello ProcessWire Community! I'm thrilled to announce that RockCommerce has finally arrived! Some years ago, after building a custom shop solution, I swore I would never create another ecommerce system again. 😅 Yet here we are! After months of hard work and completely rethinking my approach, I'm confident RockCommerce will be a game-changer for ProcessWire ecommerce. I can't wait to see what you'll create with it! 🚀 This video guides you through the Quickstart Tutorial, which was written by @Sanyaissues (THANK YOU SO MUCH!!!) He rose his hand when I asked for beta-testers 💪😎 He had never done E-Commerce before and wanted to understand how it works - so I sent him a copy of RockCommerce and let him play and this is what he came up with!!! Absolutely remarkable! Hat off to him! Docs & Download: https://www.baumrock.com/rockcommerce P.S.: To celebrate the RockCommerce release, I've applied discounts to all module licenses in my shop! If you've had a successful year, this is a great opportunity to invest in yourself and potentially reduce your taxes 😉
- 12 replies
-
- 13
-
Looking for some advice. I need to create a page, insert some basic info, create a repeater and save either a video or image. This is where I'm at so far. I'm finding that the repeater doesn't save. So when I access the main page after the process, there is no repeater inside. The logging gets stuck on video uploading to repeater This is an example url - https://video-lhr6-2.xx.fbcdn.net/v/t42.1790-2/359544611_1299622917599542_1081641833156909957_n.mp4?_nc_cat=104&ccb=1-7&_nc_sid=c53f8f&_nc_ohc=v3DnXymDRrMQ7kNvgGY2g0m&_nc_ht=video-lhr6-2.xx&_nc_gid=Avf0ALrpn8YGUKRdqZmpwGi&oh=00_AYCqXW4qteSLwgQKol8SbVAgvR-O3-9R1QVqmDjjZwiUjw&oe=66ECD5B4 Any advise would be welcomed. // Continue handling the data if everything is present $mediaUrl = $data['media_url']; $mediaType = $data['media_type']; $description = isset($data['description']) ? $data['description'] : ''; $title = 'Test Ad Title'; try { // 1. Create the main page $page = new Page(); $page->template = 'asset'; // Use the correct template $page->parent_id = 1029; // Adjust this ID for the correct parent page $page->title = $title; $page->content_type = $mediaType; // Assuming 'content_type' is a valid field $page->save(); // Log the creation of the page $log->save('save-ad', 'Created new page with title: ' . $title); // 2. Create a repeater item for the "content" repeater field $log->save('save-ad', 'Creating a new repeater item.'); $contentRepeater = $page->content->getNew(); // Create a new repeater item if (!$contentRepeater) { $log->save('save-ad', 'Error: Failed to create new repeater item.'); echo json_encode(['status' => 'error', 'message' => 'Failed to create repeater item.']); exit; } // 3. Handle media file and add it to the correct field in the repeater $filename = basename($mediaUrl); // Extract the filename from the URL // Prepare the file to be added if ($mediaType == 'video') { $log->save('save-ad', 'Adding video file to repeater.'); // Add the file directly to the file field 'video_1' $contentRepeater->video_1->add($mediaUrl); // Assuming 'video_1' is the field $log->save('save-ad', 'Video file added successfully.'); } elseif ($mediaType == 'image') { $log->save('save-ad', 'Adding image file to repeater.'); // Add the image directly to the image field 'image_1' $contentRepeater->image_1->add($mediaUrl); // Assuming 'image_1' is the field $log->save('save-ad', 'Image file added successfully.'); } else { $log->save('save-ad', 'Error: Unsupported media type: ' . $mediaType); echo json_encode(['status' => 'error', 'message' => 'Unsupported media type: ' . $mediaType]); exit; } // 4. Save the repeater item $log->save('save-ad', 'Saving repeater item.'); $contentRepeater->save(); // 5. Add the repeater item to the main page $page->content->add($contentRepeater); // Add the repeater item to the page // 6. Save the main page with the repeater $page->save(); $log->save('save-ad', 'Repeater item added to page and page saved successfully.'); // Log success $log->save('save-ad', 'Media saved successfully to page.'); echo json_encode(['status' => 'success', 'message' => 'Ad saved successfully!']);
-
Hey all! I've been creating new block/widget buttons for the current project I'm working on and wanted to share them in case they may be useful to others. They're SVGs intended to complement the style of buttons that come with RockPageBuilder. This post has been updated with a link to a Github repository containing all of the buttons currently available. New buttons have been added. Existing buttons have have been tweaked for quality and consistency. Download from or fork the Builder Buttons Github repository Rather than keep this post up to date with every button that is added, visit the Github repo to see the most current example and download/clone buttons for use in your projects. Buttons include: Accordion Announcement Articles Audio Bios Call To Action Card Over Image Code/Embed Events Image Image Carousel Image Mosaic Image Roll List Lists Products Reviews Video Weather Preview (not in order of list) If you find them useful, let me know what you think!
- 8 replies
-
- 13
-
I guess this article could be helpful: https://screencasting.com/cheap-video-hosting More details about the Cloudflare R2 pricing: https://developers.cloudflare.com/r2/pricing/
-
This module is improved and extended successor to Version Control For Text Fields. It handles everything it's predecessor did -- providing basic version control features for page content -- and quite a bit more. Download or clone from GitHub: https://github.com/teppokoivula/VersionControl. This module requires ProcessWire 2.4.1 or later, mostly because of the file features, which require certain Pagefile and Pageimage methods to be hookable. There's no sensible way around this limitation; for those stuck with < 2.4.1, Version Control For Text Fields will remain a viable option. What does it do? While editing pages, fields with old revisions available show up with a new icon in their header bars. By hovering that icon you get a list of available revisions and by clicking any one of those the value of that particular field is reverted to that revision. No changes are made to page until you choose a revision and save the page, which means that you can keep switching between revisions to get an idea what's really changed without inadvertently causing any content to change. The module also adds a History tab to page edit. This tab opens a view to the history of current page in the form of "revisions" -- each of which is a group of changes to page fields processed during one page save (similar to revisions in various source control applications). There are three actions you can perform on these revisions: adding comments, live previewing what the page might've looked in that revision and restoring the page to specific revision. One specific feature that has been a big thing for me personally is support for file (and image) fields, as the original version control module felt rather incomplete without it. I'm hoping to take this a lot further performance, stability and feature wise, but as it stands right now, it's already included here and should be fully functional. Watch the video preview here I prepared a little screencast outlining most of this: http://youtu.be/AkEt3W7meic. Considering that it was my first screencast ever, I'd like to think that it wasn't that bad.. but I might give it another shot at some point, this time planning a bit before hitting "record" Upgrading from Version Control For Text Fields For those already using Version Control For Text Fields, I've added something extra. If you upgrade that module to it's latest version, you should see a new checkbox in it's settings screen saying "Don't drop tables during uninstall". If you check this, uninstall the module and then remove it's files (this is required in order to install Version Control), your old data should be automagically imported to Version Control. Import has only been tested with limited amounts of demo data. Proper tests are yet to come, so please be careful with this feature! Update, 21.6.2015: as of today, this module is no longer in beta. While all the regular warnings still apply (making changes, including installing any new modules, on a production site should always be considered risky) Version Control has gone through pretty extensive testing, and should be as stable as any other module out there.
- 164 replies
-
- 37
-
One strategy would be to store all the original video files on the website as file uploads, and then use mux for conversion and delivery. At least then there a pinch less of a lock-in. Then, maybe at some point there might be a self-hosted mux alternative (or I make one based on ffmpeg). That could be a way to do things?
-
Hey @herr rilke thx for your interest. Unfortunately nobody else showed interest so it looks like a video would not be interesting to a lot. I did that as well, but what I don't like with that approach is that if you move a block you have to adjust all classes of all blocks which is tedious. My approach works similar but different 😛 I use a dedicated block type solely for the color. This block sets a custom runtime flag (like $site->bgColor) and all blocks then add a custom class based on that bgColor flag's value. So once I have a color block with setting "muted" all following blocks might get the class "bg-muted", for example. Then when the next color block comes up it might set the bgColor flag to "primary" which would tell all following blocks to add the class "bg-primary". Now the challenge is to make that work with spacings and with frontend editing. On frontend editing the problem to solve is with sorting blocks, because classes come from php and sorting is done on the client with JS. So you need to add a little js to adjust classes once sorting happens. In my project this is done like this: // section color updates on sort document.addEventListener("rocksortable-added", (event) => { let sortable = event.detail; sortable.option("onMove", (e) => { setTimeout(() => { let el = e.dragged; let container = el.closest("[sortable]"); let children = container.children; let colorClass = null; // loop all items and update color classes for (let i = 0; i < children.length; i++) { let child = children[i]; // don't change class of dragged item if (child.classList.contains("sortable-drag")) continue; // update colorClass on every rpb-color block if (child.classList.contains("rpb-color")) { colorClass = child.getAttribute("data-col"); continue; } // update colorclass of element if (!colorClass) colorClass = "white"; child.classList.remove("has-bg-white"); child.classList.remove("has-bg-muted"); child.classList.remove("has-bg-primary"); child.classList.remove("has-bg-secondary"); child.classList.add(`has-bg-${colorClass}`); } }, 0); }); }); For block spacings I've switched my approach from PHP based calculations to CSS based calculations. The trick is to only use margin-top for blocks, because then you can define different margins for blocks based on the previous block like this: .bg-white + .bg-muted { margin-top: 20px; } This way you can achieve a lot of flexibility in your design with very little complexity in code. Hope that helps!
-
Uncheck Active without 404? Or other way to deal with languages?
virtualgadjo replied to joe_g's topic in General Support
@joe_g actually, the page souldn't be linked from anywhere on the website as, using pw native link module, language by language it should not appear in the page select, this active thing is useful for the menus as well as the language sitch, too bad my video is in French i explain and demonstrate all this (being french most the websites i make use at least two languages when not, like yours, more and i've had to dig a lot in this languages things 🙂) have a nice day -
Uncheck Active without 404? Or other way to deal with languages?
virtualgadjo replied to joe_g's topic in General Support
Hi, as i assume the 404 issue is only for the front side of your web site, i think the problem is only when on a page you change the language.... what you can easyly solve when building your lang switcher foreach($languages as $language) { if( ! $page->viewable($language) ) continue; // here is the trick $url = $page->localUrl($language); $iso = $language->name == 'default' ? 'fr' : $language->name; // fr here but of course chang it for your default language iso $local_title = $language->getLanguageValue($language->name, 'title'); // here comes your output with flags/iso code or whatever you want :) } with this a page that doesn't exist in a language will not have the language button in the switcher and shouldn't appear in the menu for this language when you are on another page i've made a video and have a github repo with language menu tricks and exmples but, too bad, in french as there are very few reesources for the french guys using or discovering pw 🙂 sorry if i've understood your question in a wrong way have a nice day -
Video embed for YouTube and Vimeo ProcessWire Textformatter module that enables translation of YouTube or Vimeo URLs to full embed codes, resulting in a viewable video in textarea fields you apply it to. How to install Download or clone from GitHub: https://github.com/r...atterVideoEmbed Copy the TextformatterVideoEmbed.module file to your /site/modules/ directory (or place it in /site/modules/TextformatterVideoEmbed/). Click check for new modules in ProcessWire Admin Modules screen. Click install for the module labeled: "Video embed for YouTube/Vimeo". How to use Edit your body field in Setup > Fields (or whatever field(s) you will be placing videos in). On the details tab, find the Text Formatters field and select "Video embed for YouTube/Vimeo". Save. Edit a page using the field you edited and paste in YouTube and/or Vimeo video URLs each on their own paragraph. Example How it might look in your editor (like TinyMCE): Here are two videos about ProcessWire https://www.youtube.com/watch?v=Wl4XiYadV_k https://www.youtube.com/watch?v=XKnG7sikE-U And here is a great video I watched earlier this week: http://vimeo.com/18280328 How it works This module uses YouTube and Vimeo oEmbed services to generate the embed codes populated in your content. After these services are queried the first time, the embed code is cached so that it doesn't need to be pulled again. The advantage of using the oEmbed services is that you get a video formatted at the proper width, height and proportion. You can also set a max width and max height (in the module config) and expect a proportional video. Configuration/Customization You may want to update the max width and max height settings on the module's configuration screen. You should make these consistent with what is supported by your site design. If you change these max width / max height settings you may also want to check the box to clear cache, so that YouTube/Vimeo oembed services will generate new embed codes for you. Using with Markdown, Textile or other LML I mostly assume you are using this with TinyMCE. But there's no reason why you can't also use this with something like Markdown or Textile. This text formatter is looking for a YouTube or Vimeo video URL surrounded by paragraph tags. As a result, if you are using Markdown or Textile (or something else like it) you want that text formatter to run before this one. That ensures that the expected paragraph tags will be present when TextformatterVideoEmbed runs. You can control the order that text formatters are run in by drag/drop sorting in the field editor. Thanks to Pete for tuning me into these oEmbed services provided by YouTube and Vimeo a long time ago in another thread.
-
I'm thrilled to announce the official release of RockPageBuilder, the game-changing tool that's set to transform your web development journey (at least ChatGPT says so ??)! With its brand new drag-and-drop interface RockPageBuilder is your secret weapon for creating outstanding ProcessWire-based websites. Get a taste of the future of web development by visiting the demo page at https://pagebuilder.baumrock.com/ and exploring the RockPageBuilder documentation at https://www.baumrock.com/en/processwire/modules/rockpagebuilder/docs/ Don't miss out on this exciting opportunity to elevate your web development game. Get ready to rock with RockPageBuilder! ??️ Early Bird Sales until 30.11. https://www.baumrock.com/processwire/module/rockpagebuilder/ Here is a quick demo video: And here is how to install it from scratch in under 5 minutes: Some showcase projects are already in the pipeline ? So be sure to subscribe to https://processwire.rocks/ or to https://www.baumrock.com/rock-monthly/ Have a great weekend!
- 71 replies
-
- 20
-
Hey ProcessWire community! I'm excited to introduce RockCalendar, a new calendar module that brings powerful event management capabilities to your ProcessWire sites. Key features include: Full calendar view in the ProcessWire admin Easy event creation and management Drag-and-drop event scheduling Recurring events (SSE powered, needs RockGrid) Seamless integration with ProcessWire pages and fields I've created a detailed video walkthrough showcasing RockCalendar's features and installation process: Check it out and let me know what you think! I'm here to answer any questions you might have about RockCalendar. Download & Docs: https://www.baumrock.com/en/processwire/modules/rockcalendar/ Happy coding!
-
Hi there, In almost every project I find myself wanting to use the images field for being able to drag many images at once to one field. It's super convenient. However then I loose the ability to be able to mix other filetypes in the same collection. Usually, after a while clients come back and want to have a video in there somewhere or perhaps even an embed. Then I have to rethink how that works and by using repeater matrix the interface becomes clunkier (new 'image' -> upload -> new 'video' -> upload, etc). I'm dreaming now but it would be amazing with drag and drop support for repeater matrix, repeater items are automatically created from the file-type (jpg and mp4 mostly). Not sure where I'm going with this, mostly I wanted if I missed something or if this idea could work. I know there are some media library extensions around but I could never befriend them 100%. best J
-
I'll share my youtube videos in this thread and if you have questions this is the place to ask. You can also subscribe to this thread to get notified when I post new videos ? Here is the link to my channel: baumrock.com/youtube --- Hey! I've just published my very first ProcessWire video. It's about RockFrontend: https://processwire.com/talk/topic/27417-rockfrontend-??-take-your-processwire-frontend-development-to-the-next-level/#comment-225666 Here is the video: What do you think? Do you understand what I'm trying to explain (despite the many ääähms und öööhms...)? ? What about the length?? I really didn't plan do get to 40mins... Did anybody even watch it till the end? ? Would it be easier to follow when having a small thumbnail in the bottom corner when working on the code? Or better without? Is it worth the effort of creating a video or would a readme be just as good? ? Any tips for better sound/lighting? I'm not really knowing what I do, so any ideas for improvements are very welcome ? Better with or without background music? So many questions... So much to learn... ? But it's fun and I'm a bit proud ?
- 48 replies
-
- 23
-
I've recently hit 10.000 views on my youtube channel (over all videos since the beginning 2+ years ago) 🥳 I'm counting 218 subscribers and all have watched 800 hours of (almost only) ProcessWire content. The best performing videos so far are: Recent videos have been by far less popular. I don't know why. Maybe it's the thumbnail. Maybe the video length (the RockFrontend video is over 40min long!). Maybe it's the algorithm. Who knows. But Ive learnt a lot and recently I found a way to be more efficient with creating those videos - unfortunately I didn't get any feedback in that regard, but feedback is rare on my channel in general. Maybe that's part of the youtube game 🙂 So I'm asking here: What is your opinion about these videos? Are the videos helpful? Could anything be improved (easily 😅 )? What would you like to see as a next video? RockMigrations Deployment RockMigrations in general More about RockFrontend More about the commercial modules Less about the commercial modules Anything else? Thanks for "watching" and have a great weekend 🙂
-
I didn't know where the right place to share this was on the forums, so I'll post it here since it may be helpful to those getting started with ProcessWire hooks, or some experienced ProcessWire developers who might find them useful. Either way, dear reader, If someone already wrote it, why write it again? If you're someone with experience, feedback is welcome! If there are better or more efficient ways to do something, I would love being a student. Some of these may either address challenges that others have experienced as well or were inspired by the awesome community sharing their solutions. Kudos to all of the people out there helping all of us. If someone sees something that was solved elsewhere please share it in the comments to give credit. I have to make a disclaimer- these have worked for me and while most of them are ready to copy/paste, a few of them are going to need customization or tweaking to make them work for your use case. I don't currently have the resources (time) to provide a lot of support. Some of these were slightly rewritten or adapted for the examples. If you run into issues, the best thing to do is research the solution so that you know exactly what is happening in your application. If you adapt something, fix a bug, or address an edge case, it would be great if you can come back and share that. Be smart, if you're going to run hooks that modify or create data, run a DB backup first. This is the part where I say "I'm not responsible if your site blows up". I don't think that's possible, but do the right thing. There are dozens of hooks in the project I am sharing these from, and to manage that I created a file structure to handle this because there were far too many to put in one file and keeping the init.php and ready.php files clean really makes a huge difference in maintainability. Being able to jump between files by filename is a supremely efficient way to work as well. The filenames don't matter, they're there to identify the files and make it easy to locate/switch between them. Here's my approach to directory organization: /site - hooks -- HookUtils -- init -- lazy_cron -- ready init.php ready.php The ready.php file contents: <?php namespace ProcessWire; if(!defined("PROCESSWIRE")) die(); /** @var ProcessWire $wire */ // Import all ready hooks foreach (glob(__DIR__ . '/hooks/ready/*.php') as $hook) { require_once $hook; } The init.php file contents: <?php namespace ProcessWire; if(!defined("PROCESSWIRE")) die(); /** @var ProcessWire $wire */ // Import all init hooks foreach (glob(__DIR__ . '/hooks/init/*.php') as $hook) { require_once $hook; } // Import all LazyCron hooks, import after init hooks as there may be dependencies foreach (glob(__DIR__ . '/hooks/lazy_cron/*.php') as $hook) { require_once $hook; } Operational Hooks Here are some favorites. Sort items in a Repeater matrix when a page is saved This one helped sort RM items by a date subfield to help the user experience when editing pages. This implementation is configured to only fire on a specific template but can be modified to fire everywhere if modified to check that a field exists on the page being saved first. This was adapted from an answer here in the PW forum but can't find the original post, so I'm going to include it. If you're having issues getting items to sort the way you want, check out this post about natural sorting, which also works elsewhere in ProcessWire. Github Gist Automatically add a new child page under a page having a specific template when created This automatically creates a new child page and saves it when a page having a specific template is created. This also has the ability to show a message to the user in the admin when new page(s) have been created by this hook. It is also error safe by catching any potential exceptions which will show an informative error to the admin user and log the exception message. The messaging/logging operation is abstracted to a separate object to allow reuse if creating multiple pages. Github Gist Conditionally show the user a message while editing a page This one shows a message on a page with a specific template under specific conditions. May be page status, field value, type of user, etc. Visual feedback when editing complex pages can be very helpful, especially when an operation may or may not take place depending on factors like the values of multiple fields. This can reduce the amount of explanations needed on each field or training required for users to use a ProcessWire application. In my case, a message is shown if the state of a page indicates that another operation that is triggered by other hooks will or will not run, which is something that the user doesn't directly trigger or may not be aware of. Github Gist Show the user a message when viewing the page tree This is intended to display a message, warning, or error when the page tree is viewed, such as on login, but in this case executes any time the main page tree is viewed to provide consistent communication and awareness. In my case it displays if there is an activity page located under an "Uncategorized" page for an event. This is something that may be buried in the page hierarchy and not noticeable, but if an activity isn't categorized, then is isn't visible on the website, and if it's not visible on the website, people aren't seeing it or buying tickets. So having a persistent message can bring visibility to important but otherwise potentially unnoticed issues. Or you can just say hi and something nice. Github Gist Hook Enhancement - Fast user switching Hooks can run on triggers that vary widely. Some can and should be identified as those that are triggered by the current user, others may be more autonomous like executing via cron. There may be other hooks that are executed by a user that isn't logged in. Depending on the type of action and your need to identify or track it, switching from the current user to another user created specifically to handle certain tasks can be very helpful. ProcessWire tracks a number of things that are attributed to users- log entries note the user, the user that creates pages is stored, the user that last updated the page is stored, etc. You may want to know who did what when, or only take action if the last user that touched something was X and not Y. I created a separate user that has been provided only the specific permissions it needs to complete jobs that are triggered by hooks or crons. Creating a user with less permissions may also help prevent accidental behaviors, or at least help you be very intentional in determining what actions are delegated. Creating custom permissions is also useful. With a dedicated user I can see explicitly that the last update on some pages were made by an autonomous script that syncs information between the ProcessWire application and a third party platform. Github Gist - Fast user switcher Github Gist - Example of switching users in a hook Fast, powerful, and very (very) easy custom admin buttons I needed a way to add custom interactive buttons that had some specific requirements. Needs to be a button that can be clicked by the user and does something Can be conditionally shown to the user with an alternate message if that action is not available Needs to do something on the server and interact with ProcessWire Here's what that looked like for my application. The green "Refresh Activity" button in the top right. That's a custom button and you don't have to author an Inputfield module to get it. When a user clicks that button, it sends a request to the server with GET variables that are recognized in a hook, actions are taken, then a nice message indicating success or failure is shown to the user. To do this you'll need to install FieldtypeRuntimeOnly and create a new field. Following the documentation for that field, create a button with a URL to the current page with GET variables appended. Then create a hook that watches for the specific GET variable that executes if it's present. Shoutout to @Robin S for helping make short work of a potentially complex task. Note that the field code contains JS that handles the URL on page load. Since the hook is looking for a GET variable in the URL, using the back button or refreshing the page will cause the action to run twice. The JS in that example removes the entry from the browser history and also removes the GET parameter after the page loads if it's present. Github Gist - An example gist for the hook that handles the action Github Gist - An example of the FieldtypeRuntimeOnly code that is displayed and interacted with by the user. Automatically convert logged object or array data to JSON If you're using the outstanding Logs JSON Viewer (yet another great one by @Robin S module, then this hook makes for a thoroughly enjoyable logging experience. Using array or stdClass data when logging your values helps store additional information in an organized way Github Gist <?php $log->save('log_name_here', 'Regular string message'); // Remains a string $log->save('log_name_here', ['gets' => 'converted', 'to' => 'json']); $log->save('log_name_here', (object) ['is' => 'stdClass', 'object' => 'friendly']); Use a separate field to store address data for a FieldtypeMapMarker field This one is really simple, more just sharing an implementation and idea, but proved valuable for reducing data redundancy. I have a FieldtypeMapMarker field but the way that I needed to store address data was much better suited to using multiple fields for things like street, city, state, and zip code. I wanted those fields to be the "controlling" fields for the map marker field to prevent needing to edit 2 fields to keep updated, or accidental content diversion between them. On page save the value from the address fields are pulled and converted into a single string that is added to the FieldtypeMapMarker field's "address" property. I used a Custom Field (ProFields) for my address fields but this can be modified to suit your use case very easily. Github Gist You might also consider hiding the address input on the FieldtypeMapMarker field itself to reduce confusion since the values will be updated automatically anyway. You'll need to have this in a file that is appended to the Admin styles /* You can find the appropriate class for the template you are applying this to in the <body> element when editing a page You can omit that if you want to apply this everywhere */ .ProcessPageEdit-template-your_template_name .InputfieldMapMarker.Inputfield_activity_location .InputfieldMapMarkerAddress, .ProcessPageEdit-template-your_template_name .InputfieldMapMarker.Inputfield_activity_location .InputfieldMapMarkerToggle, .ProcessPageEdit-template-your_template_name .InputfieldMapMarker.Inputfield_activity_location .InputfieldMapMarkerLat, .ProcessPageEdit-template-your_template_name .InputfieldMapMarker.Inputfield_activity_location .InputfieldMapMarkerLng { display: none !important; } <?php // Add this to your ready.php file or ready-firing hook to insert the file containing that CSS to your admin. $config->styles->add("/path/to/your/custom/admin/css/file.css"); Not-A-Hook Bonus - Here's code for an interactive Google Map Renders a Google Map using a FieldtypeMapMarker field, a separate address field, Alpine.js, and Tailwind. You'll need a Google Maps API key, a styled map ID from your Google Developer account, and the aforementioned fields. I wrote it using the latest Google Maps API. Saved you some time. You'll probably need to tweak it. I adapted this so if you find a bug please let me know and I'll update the gist. Note- this makes use of the AlpineJS Intersect plugin to improve performance by only loading/initializing the map when a user scrolls close enough to it. If you don't want that, remove the x-intersect directive. If you want to see it in action, you can check it out here. Github Gist Hook Support Class - A static method class to translate a field into all languages automatically If you use the Fluency translation module, this is a class that will help out with translating a field into all languages programmatically. Sharing this here because the next hook uses this as a dependency. I keep this in the HookUtils directory noted in the file structure above. Usage is demonstrated in the next hook. Github Gist Translate all translatable fields using Fluency on page save whether from UI or API. This is useful for instances where you want a page translated automatically and especially helpful when you are creating pages programmatically. This requires the above hook support class, as well as Fluency connected to an API account. Here are things that must be kept in mind. Please read them, the code for the hook, and the code for the support class to ensure that it works to your needs. You should modify Fluency before using this, really. Change the value of CACHE_EXPIRY on line 19 in the TranslationCache file to WireCache::expireNever. Do this to prevent chewing through your API usage from month to month on repeat translations. This will become standard in the next release of Fluency. This is an expensive operation in terms of API usage, which is why you very much should modify the caching behavior. This hook does not make an effort to determine which fields have changed before translating because it doesn't really matter if the translation is already cached. First time translations of pages with a significant amount of fields/content may be slow, like noticeably slower first time page save because this operation is only as fast as the speed of the request/response loop between ProcessWire and the translation API. Later page saves will be much faster thanks to cached translations. This will not attempt to translate empty fields, so those won't cause any delays. This works with multi-language text/textarea/TinyMCE/CKEditor fields, RepeaterMatrix fields, and the newer Custom Fields (ProFields). Other fields haven't been tested, but it's definitely possible to adapt this to those needs. I prefer to target specific templates with hooks, you can add multiple but be mindful of your use case. Consider adding excluded fields to the array in the hook if it makes sense Consider adding a field to enable/disable translations from the UI, a checkbox field or something This hook is probably one of the uglier ones, sorry. If you run out of API usage on your account, you're going to see a big ugly exception error on screen. This is due to Fluency not handling an account overage error properly because the return type was not as expected. Will be fixed in the next version of the module This is one that may be tailored to my PW application, I think it's general enough to use as-is for your project, but testing is definitely required. Read all the code please. Github Gist ProcessWire Object Method & Property Hooks The following are custom methods that add functionality to native ProcessWire objects. Add a getMatrixChildren() method to RepeaterMatrixPage objects RepeaterMatrix fields represent nesting depth as an integer on each RepeaterMatrixPage item. So top level is 0, first nested level is 1, second 2, etc. When looping through RM items, determining nesting requires working with that integer. It works, but adding adding some functionality helps out. This is infinitely nestable, so accessing children, children of children, children of children of children, and so on works. Fun for the whole family. This was inspired by a forum post, another one I can't find... Github Gist <?php // Access nested RepeaterMatrix items as child PageArray objects $page->repeater_matrix_field->first()->getMatrixChildren(); // => PageArray ?> <!-- Assists with rendering nested RM items in templates Sponsors are nested under sponsorship levels in the RM field --> <div> <?php foreach ($page->sponsors as $sponsorshipLevel): ?> <h2><?=$sponsorshipLevel->title?></h2> <?php if ($sponsorshipLevel->getMatrixChildren()->count()): ?> <ul> <?php foreach ($sponsorshipLevel->getMatrixChildren() as $sponsor): ?> <li> <img src="<?=$sponsor->image->url?>" alt="<?=$sponsor->image->description?>"> <?=$sponsor->title?> </li> <?php endforeach ?> </ul> <?php endif ?> <?php endforeach ?> </div> Add a resizeAspectRatio() method to PageImage objects Adds a simple way to quickly resize an image to a specific aspect ratio. Use cases include sizing images for Google Structured Data and formatting images for consistency in image carousels. Could be improved by accepting second argument to specify an image width, but didn't fit my use case. Github Gist <?php $page->image_field->resizeAspectRatio('square')->url; // Alias for 1:1 $page->image_field->resizeAspectRatio('video')->url; // Alias for 16:9 $page->image_field->resizeAspectRatio('17:10')->url; // Arbitrary values accepted Add a responsiveAttributes() method to PageImage objects Adds a very helpful method to generate image variations and accompanying 'srcset' and 'sizes' attributes for any image. Designed to be very flexible and is Tailwind ready. Responsive sizing can be as simple or complex as your needs require. Includes an optional 'mobile' Tailwind breakpoint that matches a custom tailwind.config.js value: screens: { 'mobile': '320px'}. I added this breakpoint largely to further optimize images for small screens. The array of Tailwind breakpoints and size definitions can be edited to suit your specific setup if there are customizations When sizing for Tailwind, the last media query generated will automatically be switched to "min-width" rather than "max-width" to prevent problems arising from restricting widths. Example, you can specify values only for 'sm' and 'md' and the 'md' size will have the media query correctly adjusted so that it applies to all breakpoints above it. Github Gist <-- The responsiveAttributes() returns a renderable attribute string: srcset="{generated values}" sizes="{generated values}" --> <-- Create responsive images with arbitrary width and height at breakpoints --> <img src="<?=$page->image->url?>" <?=$page->image->responsiveAttributes([ [240, 125, '(max-width: 300px)'], [225, 125, '(max-width: 600px)'], [280, 165, '(max-width: 900px)'], [210, 125, '(max-width: 1200px)'], [260, 155, '(min-width: 1500px)'], ])?> width="240" height="125" alt="<?=$page->image->description?>" > <-- Heights can be selectively ommitted by setting the height value to null --> <img src="<?=$page->image->url?>" <?=$page->image->responsiveAttributes([ [240, 125, '(max-width: 300px)'], [225, null, '(max-width: 600px)'], [280, 165, '(max-width: 900px)'], [210, null, '(max-width: 1200px)'], [260, null, '(min-width: 1500px)'], ])?> width="240" height="125" alt="<?=$page->image->description?>" > <-- Create responsive images with only widths at breakpoints --> <img src="<?=$page->image->url?>" <?=page->image->responsiveAttributes([ [240, '(max-width: 300px)'], [225, '(max-width: 600px)'], [280, '(max-width: 900px)'], [210, '(max-width: 1200px)'], [260, '(min-width: 1500px)'], ])?> width="240" height="125" alt="<?=$page->image->description?>" > <-- Create custom sizes matched to Tailwind breakpoints --> <img src="<?=$page->image->url?>" <?=$page->image->responsiveAttributes([ 'mobile' => [240, 125], // Custom tailwind directive 'sm' => [225, 125], 'md' => [280, 165], 'lg' => [210, 125], 'xl' => [260, 155], ])?> width="240" height="125" alt="<?=$page->image->description?>" > <!-- Resizes width of image to fit Tailwind breakpoints, useful for full width images such as hero images, doesn't change height. Also accepts 'tw' as an alias for 'tailwind' --> <img src="<?=$page->image->url?>" <?=$page->image->responsiveAttributes('tailwind')?> width="240" height="125" alt="<?=$page->image->description?>" > Add PHP higher-order function methods to WireArray and WireArray derived objects WireArray objects are incredibly powerful and have tons of utility, but there are situations where I find myself needing to work with plain PHP arrays. I'm a very big fan of PHP's array functions that are efficient and make for clean readable code. I found myself often reaching for $wireArrayThing->getArray() to work with data then using functions like array_map, array_filter, and array_reduce. These return arrays, but could easily be modified to return WireArray objects if that is more helpful. Github Gist <?php // The EventPage page class has a method that determines sold out status from more than one source of data/page fields // which means that it isn't queryable using a ProcessWire selector. This returns a single integer calculated from ticket availability // of all events from non-queryable data. $totalEventsAvailable = $eventPages->reduce( fn ($total, $eventPage) => $count = $eventPage->isActive() ? $total++ : $total, 0 ); // Requires using a page class to determine status reliant on multiple data points not queryable via a selector. Knowing what the event // page is for an activity can't be determined using a selector for activity pages. $displayableActivities = $matches->filterToArray( fn ($activityPage) => $activityPage->eventPage()->isPublic() && $activityPage->isActive() ); // Iterating over each Page in a PageArray and processing data for sorting/ordering before rendering on a search results page // Executed within a page class $results = $pages->mapToArray(function($page) { return (object) [ 'page' => $page, 'summary' => $this->createResultSummary(page: $page, maxLength: 750), 'keywordMatchCount' => $this->getQueryMatchCount(page: $page), ]; }); Add an image orientation method/property to PageImage objects Get the portrait or landscape orientation of a PageImage. Github Gist <?php $page->image->orientation; $page->image->orientation(); Add the ability to get all related pages for Page objects at once Gets all of the related pages to a page at once by both page reference fields and links in fields. Transparently passes native arguments to Page methods for native behavior Github Gist <?php $page->allPageReferences(); $page->allPageReferences(true); // Optionally include all pages regardless of status $page->allPageReferences('your_selector=here', 'field_name'); // Use with native Page::references() and Page::links() arguments Add a saveWithoutHooks() convenience method to Page objects The number of hooks in my most recent project was... a lot. There were many that hooked into the page save event and a lot of operations that happen in the background where pages needed to be modified and saved quietly to prevent clearing ProCache files or excessive DB operations through chained hooks. Being able to use a method to do this rather than passing options felt more deliberate and clear when working across hundreds of files and in critical areas of very expensive operations. This method also accepts page save options, but in a way that hooks will always be disabled even if an option is accidentally passed enabling them. Furthermore, it also accepts a string as the first argument that, if debug mode is enabled, will dump a message to the bar via Tracy. Github Gist <?php // Adding a message can be very helpful during testing, especially when saving a page with/without hooks is conditionally based // where the result of another operation determines how a page is saved $page->saveWithoutHooks('updated event sync data hash, saved without hooks'); $page->saveWithoutHooks(['resetTrackChanges' => true]); $page->saveWithoutHooks('message and options', ['resetTrackChanges' => true]); These are a few that I've used to show some diversity in application. Hooking to ProcessWire events makes it possible to build beyond simple websites and implement truly custom behavior. Hope these may be useful to others. If you have any favorite hooks of your own, have corrections of those I've shared, or improvements, sharing them in the comments would be stellar. Cheers!
-
Hey @wbmnfktr thank you very much for your honest feedback! I had to think about what you said, because I had good reasons (at least I thought they where good) for using my advanced techniques. I didn't think that showing a very basic, old-school installation via unzipping the module files would be interesting at all, so I wanted to pack more into the video. Not that old-school installations are bad in any way, I just didn't think that this is interesting or helpful for anybody. Also I thought I made it clear that this is nothing anyone has to use, but I think that got lost during editing. So I think you have a very good point and I'll try to not mix different topics in one single video. These alias commands would have been more helpful in a dedicated RockShell video, I guess, where I also show how these can be setup. Thank you!
-
Is my 100th post I wanted to do something special. I edited a video, hope you like it Just for fun Edited: Now with subtitles
- 41 replies
-
- 59
-
Hey @olivetree thank you for your question. I think in terms of use cases you can do quite the same with both modules. (See note at the end) The main difference between both modules is how/where data is handled. ListerPro handles all data on the server side, which means there is basically no limit in terms of scale. Pagination etc. is all done by the backend (php) and only small chunks of the data are sent to the client. With RockGrid, on the other hand, all the data is sent to the client at once and that data is then handled by the client. That has the drawback that you might hit limits earlier than with ListerPro, but it has the benefit that sorting and filtering is done on the client and produces instant results. There is no need for any ajax requests, no waiting for receiving the data, etc.; Another benefit of using RockGrid is that you have unlimited possibilities in HOW you present your data. The downside is that you need to define all that with a mix of PHP (the data selection, basically just a PW selector) and JS (the visual part). With ListerPro you can build your data listings via GUI with just a few clicks. That means that you are very limited in terms of visually presenting data. In terms of scalability RockGrid should be fine quite far, though. Users reported good results with grids having 75 columns and up to 150.000 rows! That's a lot. It always depends on the device though, but I've never had tables with 75 columns and less columns means more rows possible. If you have a look at the demo image of RockGrid: How would you build that with ListerPro? BUT: ListerPro will show page actions by default and you will not have to do anything. With RockGrid every piece of the representation comes from code, so if you need page actions you need to add code to do so (there are helper functions there, but it's more work than with ListerPro). Oh, I almost forgot 🙂 You can use RockGrid as an Inputfield as well! For the RockCommerce module I'm using RockGrid to select the variations of a product. You can filter by variation name, then select all options of the variation by clicking the variation column, then deselect single items that you don't need by clicking on the option column: So RockGrid can not only be used for similar use cases like ListerPro but also for use cases that you have used page reference fields in the past. 😎 I'll have to write better docs and make a video about it, but I wanted/needed to release it as it is a dependency for RockCommerce and for that module you don't need to create any grids on your own, you just install the module and use it. That's why for RockCommerce the RockGrid docs are not a necessity. It's a really powerful module and it has gone a long way. 🙂 Does that answer your question? PS: Oh and here is an example how you can use it for more complex input scenarios, like adding prices and doing calculations on the fly: That's also very different to what ListerPro offers 🙂
-
Thanks @bernhard for that video. This puts a name on the concept I have used a bunch of times and I think is the only real way to solve OPs problem. Most ERP systems and also some well-known e-commerce systems out there (I know of Shopfiy and Shopware which do that) don't let you manage an item's quantity with a simple text field or a simple "number". Instead, the quantity is "event sourced" (as in the video) with a log of changes. This even includes the initial value. The video also says it, but as an example, one could come up with the current item's stock quantity of 8 by looking at this "event log": +10 from incoming shipment -2 from order -1 from order +3 from incoming shipment -1 from order -1 from order In ProcessWire, you could create a repeater to achieve this. Just record all your "incoming" lines with a repeater in your product (remember to add a datetime field) and add hooks to orders which automatically add lines when orders are placed/refunded/cancelled. The only remaining thing is to modify the repeater editor to only allow the user to edit new items. But a combination of limiting edit access to the entire repeater and a "update quantity" field like @bernhard and @da² suggested will get you there.