-
Posts
816 -
Joined
-
Last visited
-
Days Won
41
Everything posted by Jonathan Lahijani
-
@bernhard Within the Tailwind ecosystem, my goal was to find the "framework" that had the best JavaScript components (the typical things like accordions, tabs, etc.). I started with Tailwind UI but that was geared for headless, however behind the scenes in their demos they had a hidden Alpine.js based solution. That was hacky but I used it for a little bit. Then Alpine.js itself had their premium components and since the two pair well, I went with that for a while but it didn't feel right in the same way that UIkit does. Then I found Preline and played with that for a while, but a couple years ago it was good but not as good as Flowbite. So I stuck with Flowbite for the last 2 years. A couple months ago I re-visited Preline and they've made some incredible progress, so much so that I feel it's "ahead" of Flowbite. Then a month or two ago, the folks at Tailwind finally released non-headless / vanilla JS components officially for Tailwind UI, which I haven't experimented with yet but I'll probably switch to that if it makes sense (which I'm sure it will): https://tailwindcss.com/blog/vanilla-js-support-for-tailwind-plus Also, I've been thinking about eventually switching back to vanilla CSS at some point because of how much progress it's made in the last 15 years. I stopped writing vanilla CSS when Bootstrap 2 came out and every since then I've gone from one framework to another (Bootstrap 2 -> Foundation -> Bootstrap 3 -> Uikit2 -> Uikit3 -> Tailwind). I love UIkit but I feel it's antiquated now and not taking advantage of all the new cool features of CSS. I also came to not like being softly "jailed" in their way of doing things. Also I like the idea of not using a build-step so vanilla CSS is probably what I'll settle on when I'm ready.
-
I really wish I knew about this issue 2 years ago. Like... really really wish.
-
@ryan, can you pretty please look at this issue that has to do with a sub-selector bug that occurs when there are about 1500+ pages? https://github.com/processwire/processwire-issues/issues/2084 It would be nice to have that fixed before the next master version.
-
Just wanted to say this module is working really well under a lot of load. 👍
-
RockShell - a ProcessWire Commandline Companion ⌨️
Jonathan Lahijani replied to bernhard's topic in Modules/Plugins
Thank you very much. This adjustment also fixes admin emails being sent when errors occur. Awesome! -
Ack! You're right. I did this API integration a year ago and I forgot about the 'auth' setting in my routes file. Thanks for mentioning this.
-
@Sebi Is there a reason there is not a simple "API-key based authentication" auth-type, meaning to communicate with the API, you just need an API key (without having to deal with sessions or JWTs)? Would you allow a pull request to add this?
-
Callbacks were introduced in this update (ProcessWire 2.0.235) but I feel like this was an important update that was not spoken about. Analogous to Rails callbacks.
-
RockShell - a ProcessWire Commandline Companion ⌨️
Jonathan Lahijani replied to bernhard's topic in Modules/Plugins
That worked... I replaced the try/catch block with just $app->run(); The error appeared in the console and logged to 'errors' log. I wonder if it can be refactored a bit? Ideally I'd like to run try/catch in my own rockshell command, but with the current approach rockshell's try/catch will override it, right? -
RockShell - a ProcessWire Commandline Companion ⌨️
Jonathan Lahijani replied to bernhard's topic in Modules/Plugins
Basically, I'm running a rockshell command and I get this error due to a logic bug in my own code: Call to a member function getProduction() on null However because the error isn't really specific, I'm not sure where in my codebase that's exactly occurring (like which file and which line). It doesn't log it to ProcessWire's 'errors' log. If I run the same code that's inside my rockshell command "outside" of rockshell (using php on the command line), it will also error, but details about it like this: PHP Fatal error: Uncaught Error: Call to a member function getProduction() on null in /path/to/pw/site/classes/BlahPage.php:210 Stack trace: ... Is it possible to log errors to the error log? -
RockShell - a ProcessWire Commandline Companion ⌨️
Jonathan Lahijani replied to bernhard's topic in Modules/Plugins
@bernhard Somewhere in my codebase, an exception is occurring. It's not rockshell specific, but I'm using rockshell to run a background job and at some point this exception occurs. However, the exception details are very minimal in rockshell's output (just a simple line) which doesn't give me enough information me to track it down. I would think the exception would get placed in ProcessWire's own logs, but it doesn't. Any thoughts on how I can get the exceptions to get logged to help me debug? -
I need to set up monitoring on my servers to detect this. I didn't realize DigitalOcean has a metrics tool. Going to set that up now. I will look into MariaDB using too much memory in general though. Thanks for the tip.
-
Just a heads up for anyone using DigitalOcean and sending out emails using SMTP with port 587, DigitalOcean just recently started blocking this port on "new" droplets. I put "new" in quotes because that's not true: I have a droplet from months ago, before their supposed announced change, and it still got blocked. I didn't realize this until one of my clients brought it up. Good job DO! /s I use WireMailSmtp and power it with Mailgun's SMTP credentials with port 587. I've been doing it this way for a long time, although using Mailgun's direct API approach (of which WireMailgun uses) is more preferred and would avoid this issue. I will start taking that approach soon with new and existing sites. Using SMTP is convenient however. Anyway, I'm not the only one that's complaining: https://www.digitalocean.com/community/questions/smtp-587-ports-is-closed An easy fix, for now at least, is to use port 2525 which is not blocked and which Mailgun also supports: https://www.mailgun.com/blog/email/which-smtp-port-understanding-ports-25-465-587/
-
- 5
-
-
They are droplets. One droplet is Ubuntu 22.04 and the other is 24.04. I don't think the droplets themselves have the same hardware specs. There wasn't a similarity between the sites that would possibly explain it, at least not an obvious one.
-
(I'm putting this in the Dev Talk forum since I don't think this is ProcessWire specific.) I have a ProcessWire site on a DigitalOcean server using Ubuntu 24.04 with MariaDB and PHP. This site doesn't receive much traffic. On 4/1/2025 MariaDB crashed. Here's what systemctl said: > systemctl status mariadb.service × mariadb.service - MariaDB 10.11.11 database server Loaded: loaded (/usr/lib/systemd/system/mariadb.service; enabled; preset: enabled) Active: failed (Result: exit-code) since Wed 2025-04-02 06:31:12 UTC; 9s ago Duration: 1month 5d 13h 27min 44.368s Docs: man:mariadbd(8) https://mariadb.com/kb/en/library/systemd/ Process: 752113 ExecStartPre=/usr/bin/install -m 755 -o mysql -g root -d /var/run/mysqld (code=exited, status=0/SUCCESS) Process: 752115 ExecStartPre=/bin/sh -c systemctl unset-environment _WSREP_START_POSITION (code=exited, status=0/SUCCESS) Process: 752118 ExecStartPre=/bin/sh -c [ ! -e /usr/bin/galera_recovery ] && VAR= || VAR=`/usr/bin/galera_recovery`; [ $? -eq 0 ] && systemctl set-> Process: 752178 ExecStart=/usr/sbin/mariadbd $MYSQLD_OPTS $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION (code=exited, status=1/FAILURE) Main PID: 752178 (code=exited, status=1/FAILURE) Status: "MariaDB server is down" CPU: 178ms Apr 02 06:31:12 myserver1 mariadbd[752178]: 2025-04-02 6:31:12 0 [ERROR] InnoDB: Failed to read log at 73992704: I/O error Apr 02 06:31:12 myserver1 mariadbd[752178]: 2025-04-02 6:31:12 0 [ERROR] InnoDB: Plugin initialization aborted with error Generic error Apr 02 06:31:12 myserver1 mariadbd[752178]: 2025-04-02 6:31:12 0 [Note] InnoDB: Starting shutdown... Apr 02 06:31:12 myserver1 mariadbd[752178]: 2025-04-02 6:31:12 0 [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed. Apr 02 06:31:12 myserver1 mariadbd[752178]: 2025-04-02 6:31:12 0 [Note] Plugin 'FEEDBACK' is disabled. Apr 02 06:31:12 myserver1 mariadbd[752178]: 2025-04-02 6:31:12 0 [ERROR] Unknown/unsupported storage engine: InnoDB Apr 02 06:31:12 myserver1 mariadbd[752178]: 2025-04-02 6:31:12 0 [ERROR] Aborting Apr 02 06:31:12 myserver1 systemd[1]: mariadb.service: Main process exited, code=exited, status=1/FAILURE Apr 02 06:31:12 myserver1 systemd[1]: mariadb.service: Failed with result 'exit-code'. Apr 02 06:31:12 myserver1 systemd[1]: Failed to start mariadb.service - MariaDB 10.11.11 database server. Not sure why that happened and this is a recently built ProcessWire site (no legacy cruft). I thought simply restarting MariaDB would fix it but it didn't. Not even restarting the server fixed it. After Googling and ChatGPTing, this post (and ChatGPT) recommended removing /var/lib/mysql/ib_logfile0 (and iblogfile1), then restarting. The replies to that post suggest that while it works, it's dangerous for reasons related to potentially losing data. Given that this site is not mission critical, that wasn't a concern and the approach suggested worked. Ok. --- Now today, on a totally different site and server (still DigitalOcean) with the same Ubuntu version and LAMP stack, MariaDB crashed in the same way. I ran the same command "systemctl status mariadb.service" on that server and the output was basically identical. Restarting MariaDB worked in this case so it was much easier to fix. Again, this site doesn't receive much traffic. --- I'm wondering what's going on here? While these sites do get the typical hack-bots trying to exploit WordPress or insecure env files and things like that, I don't think that's the story here, even though they do tend to hammer the server. It has to be one of the following reasons logically speaking: a bug with MariaDB and/or Ubuntu? a bug with ProcessWire? an issue with DigitalOcean underlying hardware? Unlikely. overloading of server resources from hackbots or even AI crawlers? I looked at my logs and that didn't seem immediately plausible something code related on my end? Possible because they are both running my home-grown ecommerce module. Anyone else have issues with MySQL or MariaDB crashing for odd reasons?
-
Good question. It has to do with an order and production management system I'm developing. Imagine you ordered a blanket with a personalized design, like a family photo of your choosing, and your order is for 3 of them. That's a print-on-demand product that has to be ultimately manufactured by a fulfiller. A person in the fulfiller manufacturing company will get this order and see it as 3 separate "production jobs" (not one production job with a quantity of 3 -- it's done this way because each manufactured product has to have specific status tracking and other data associated with it), but since it's the exact same product and design, the artwork that needs to be printed (a jpg or png) is actually the same across all 3, and they all point to the same file (won't get into the details of how it's all structured, it's very complex). However I also designed the system so that an employee can understand a production job just by looking at the filenames of the artwork files they downloaded from the manufacturing system, which is required because after they download that file, it has to go through special ripping and printing software. The filename format would be like this: [manufacturer-location]___[provider]___[ship-by-date]___[product]___[production-job-id-x].png That "x" at the end is represents the number based on the quantity of that exact personalized product. A realistic example would be like this based on the quantity of 3 blankets that were originally ordered: losangeles___somebigecommercesite___2025-03-28___blanket-60x80___123456-1.png losangeles___somebigecommercesite___2025-03-28___blanket-60x80___123456-2.png losangeles___somebigecommercesite___2025-03-28___blanket-60x80___123456-3.png So going back to the download attribute and custom filenames, that original file may have been originally called 123.png (where '123' is the id of repeater the artwork file belongs to since that repeater has a single-file field), but when it is downloaded through the interface by a person in the manufacturing facility, it will download as losangeles___somebigecommercesite___2025-03-28___blanket-60x80___123456-1.png, -2, thanks to the download attribute. Those downloaded files can then easily be loaded into a ripping software and the worker won't need to "think" about which ones had multiple quantities since that is fully expressed as individual files.
-
Some time ago, I learned you can add the "download" attribute to a link to force a browser to download the file when its clicked, like this: <a download href="my-awesome-file.jpg">Download</a> What I didn't realize until today is that you can actually specify a filename as a value for the download attribute like this which will automatically use that filename instead!: <a download="my___awesome___file.jpg" href="my-awesome-file.jpg">Download</a> This is incredibly convenient because it means if I want a user to download the same exact file 3 times but with specific filenames for each, I can just do this: <a download="my___awesome___file-1.jpg" href="my-awesome-file.jpg">Download 1</a> <a download="my___awesome___file-2.jpg" href="my-awesome-file.jpg">Download 2</a> <a download="my___awesome___file-3.jpg" href="my-awesome-file.jpg">Download 3</a> Furthermore, as you can see, I am using a triple underscore which ProcessWire cleans up and changes to a single underscore, as well as other changes (such as lowercasing everything) when uploading to a pagefile field. I want my filenames to be exactly as I upload and I know there's ways to prevent ProcessWire from doing that, but using the download attribute in the way I described works perfectly for my use case.
-
I resonate very deeply with this, especially in the last 2 years where I'm using ProcessWire as a web application framework. Maybe it's my impatience of having to write migration files or the fact that I'm usually a team of one, but modeling an app in this way and getting an admin interface "for free" with everything interconnected is peak productivity. I look at ProcessWire very differently as of 2 years ago. In 2006/7, not long after I decided to get into website development, I gravitated towards Ruby On Rails (which has a special place in my heart even though I haven't used it in over a decade). However given my lack of experience with programming in general at that time (I was more of a "hacker") and the fact that a web application framework lends itself to complex applications, OOP, software engineering, etc., it was too early for me to pursue that line of work, so I went down the CMS path and eventually found PW in ~2012 after searching for an alternative for WordPress for a few years. Two years ago, I had the opportunity to re-write an internal order and production system (a true web application... no frontend, purely admin) and I had to make a decision... should I write this in a web application framework like Rails/Laravel or can I actually do this in ProcessWire in the "ProcessWire Way"? This forced me to look at ProcessWire in completely differently and to make a long story short, I've proven it to myself, on a deep level, that ProcessWire is a very capable web application framework as well. Realizing and proving this to myself with this system I've developed is liberating because for me, I can use one system to do two very different types of projects.
-
Turn Your Boring ProcessWire Emails into Beautiful Masterpieces! 🎨
Jonathan Lahijani replied to bernhard's topic in Tutorials
Nice. Just wanted to mention Cerberus as an alternative to MJML if you want to use email-friendly HTML templates directly.- 14 replies
-
- 12
-
-
-
@bernhard How do RM's config migrations work when it comes to creating pages, since it doesn't support page creation? So let's say I wanted to create /colors/ with child pages red, green and blue. I would have: /site/RockMigrations/templates/colors.php /site/RockMigrations/templates/color.php /site/RockMigrations/fields/color.php To make the 'color' single-select page field use /colors/ as a parent page and 'color' as a template, the /colors/ page needs to be created using the 'colors' template and red/green/blue need to be created with the 'color' template. Where would the code go for creating those pages if now the code for fields and templates are done in the new "config migrations" way?
-
Best VPS hosting provider for 2025?
Jonathan Lahijani replied to modifiedcontent's topic in General Support
@FireWire Have you used managed databases with Vultr? I see that they don't have MariaDB, but I suppose MySQL should be fine.- 16 replies
-
- hosting services
- vps
-
(and 1 more)
Tagged with:
-
Digital Ocean Load Balanced Setup with ProCache
Jonathan Lahijani replied to markus_blue_tomato's topic in General Support
@markus_blue_tomato Is there a reason you didn't use Session Handler Database? https://processwire.com/docs/security/sessions/ -
I was thinking about this as well recently. Here's what I came up with: function convertRepeaterPageToPage($repeaterPage, $newParent, $newTemplate, $newStatus) { // store for cleanup $forPage = $repeaterPage->getForPage(); $forField = $repeaterPage->getForField(); // convert $repeaterPage->set('parent_id', $newParent->id); $repeaterPage->set('templates_id', $newTemplate->id); $repeaterPage->set('status', $newStatus); $repeaterPage->set('published', time()); // make this adjustable as well? $repeaterPage->save(['noHooks'=>true]); // cleanup $forPage->save($forField, ['noHooks'=>true]); return $repeaterPage; } Note: It should be improved to make sure what's provided in the arguments is valid. Also maybe have the ability to set the 'name' field of the page as well instead of preserving the auto-generated one that a repeater item gets assigned. Also maybe use types for the arguments. --- Example: Let's say you have a repeater field called 'books'. Then you decide one day that it would be better that they existed as regular pages instead of repeater pages. Therefore, you would create a new template called 'book' making sure it has the same fields as the repeater fields, then do this: foreach($pages->get('/path/to/page/')->books as $book) { convertRepeaterPageToPage($book, wire('pages')->get('/foo/'), wire('templates')->get('book'), 1); }
-
I've literally thinking about that these past few weeks. What a timely post!