Jump to content


  • Posts

  • Joined

  • Last visited

  • Days Won


Everything posted by millipedia

  1. We look after a lot of non-profit sites including half a dozen schools and they're mostly hosted on Digital Ocean instances managed by Cloudways. Their cheapest instance is currently $11 a month and that's what we normally start and which provides good performance for most cases (there are some additional per Gb backup charges but they're normally very minimal). You can scale up instances if you need to. One thing we find particularly useful is being able to 'clone' sites so we keep a skeleton PW site up to date and can just replicate that to kick off a new project (no, all our sites do NOT look the same...). You can also clone a site to a staging site and then push changes from that to the live site. We generally stick sites behind a free Cloudfare account as well (because honestly why wouldn't you), and Cloudways also have a deal with Cloudflare where you get the enterprise level for $5 a month which seems great if you need it (we rarely do). We also use https://krystal.uk/ for clients who need emails - that's more of a traditional CPanel setup but works well for us.
  2. Hmm - it works for me on several sites at different page depths - which webserver are you using? But you're right that I don't really think we need that leading period, and taking it out seems to be fine, so I've made those changes as suggested. Thanks for letting me know.
  3. Slightly embarrassed to mention it given the code is currently 90% todo statements, but I did expand this module for a project I did a few months back to include Vimeo. You can grab the code from https://github.com/millipedia/TextformatterLiteVideoEmbed and you can see a test page here https://millipedia.com/video-test/ It definitely needs a lot of tidying up, but I have it running on a couple of live sites at the moment and it hasn't destroyed them yet (ymmv). I'll try and find some time to polish up the code as soon as I can.
  4. That's such a sensible idea. I only wish I'd thought of it 3 hours ago... I'd still kind of like to know what the default page add is doing, but this absolutely works. Thanks for your help.
  5. I'm building an import tool for sucking in data from a Google spreadsheet. I'm creating pages from each row and that's all working fine. When I'm running the import I want to check we haven't already created a page with this title, so I'm taking the title field of the data, converting it into what I expect the name of the new page to be and then seeing it we already have a page with that name. This works fine most of the time, but when we have a long title the values I get from sanitizing to PageName are not the actual length of the page name created. For example: $title='What to do with ‘white working-class’ underachievement? Framing ‘white working-class’ underachievement in post-Brexit Referendum England'; echo '<br>' . $title; $nice_name = $this->sanitizer->pageName($title); echo '<br> Pagename : ' . $nice_name ; $nice_name = $this->sanitizer->pageName($title, true); echo '<br> Pagename with beautify: ' . $nice_name ; $nice_name = $this->sanitizer->pageNameTranslate($title); echo '<br> PagenameTranslate : ' . $nice_name; gives the result What to do with ‘white working-class’ underachievement? Framing ‘white working-class’ underachievement in post-Brexit Referendum England Pagename : what-to-do-with-white-working-class-underachievement--framing-white-working-class-underachievement-in-post-brexit-re Pagename with beautify: what-to-do-with-white-working-class-underachievement-framing-white-working-class-underachievement-in-post-brexit-referendum PagenameTranslate : what-to-do-with-white-working-class-underachievement-framing-white-working-class-underachievement-in-post-brexit-referendum but the page name that is actually created by that title when I do $pages->add is: what-to-do-with-white-working-class-underachievement-framing-white-working-class-underachievement-in-post even stripping out quote marks etc I can't reliably get it to be the same length so can't test against it. Any clues as to what I should be using?
  6. Like @wbmnfktr we only really do bespoke designs. If a client asks us to do a 'bog standard' website then honestly we're going to point them either at a site builder ( Wix / Weebly / Squarespace ) or even (shudder) WordPress. That's because there are so many off the shelf templates for those platforms that will probably do just what they much cheaper than we can. A hosted solution also means we don't have to be involved in keeping systems up to date and applying the inevitable WP security patches. There's also the fact that building those kind of sites honestly isn't that satisfying. Website builders are just getting better and more convenient for user so I think your business will really need to offer something above what they offer in order to be a success, and this is exactly where ProcessWire can help. Nearly all of the sites we build have some bespoke functionality, whether it's for a campaign or gathering and presenting data in an interesting way. Using ProcessWire means we can rapidly develop that extra functionality they need to add value to their site. If you're not a designer then it would definitely be worth partnering with a someone who has design experience. For purely functional sites you can use Bootstrap / UIKit etc but creating a bespoke site is definitely something a professional designer needs to be involved with. A good design is valuble to your client so something they should be paying for; probably the hard part is going to be convincing them of the value you're providing over and above just using an off the shelf design.
  7. OK - this kind of works: In my template I've included the Cloudflare script (and added their end point to the content security policy). Then I'm injecting a placeholder into the form: $comments_form= $page->comments->renderForm(array( 'requireHoneypotField' => 'email2' )); // add a div with class="cf-turnstil" to the form - this gets replaced with a token (after a successful challenge) $cft_tag='<div class="cf-turnstile" data-sitekey="yourturnstilesitekey"></div>'; $comments_form=str_replace("CommentForm' method='post'>","CommentForm' method='post'>$cft_tag", $comments_form); echo $comments_form; Cloudflare replaces that with a token if they think you're not a bot. Then in init.php (not _init.php) I'm hooking into ProcessPageVIew $this->addHookBefore('ProcessPageView::execute', function(HookEvent $event) { if(wire('input')->post('CommentForm_submit')){ // get the Cloudflare token. $cf_token=wire('input')->post('cf-turnstile-response'); // and send it to siteverify $postdata = http_build_query( array( 'secret' => 'yoursupersecretcloudflaresecretkey', 'response' => $cf_token ) ); $opts = array('http' => array( 'method' => 'POST', 'header' => 'Content-Type: application/x-www-form-urlencoded', 'content' => $postdata ) ); $context = stream_context_create($opts); $api_json_response = file_get_contents('https://challenges.cloudflare.com/turnstile/v0/siteverify', false, $context); if($api_json_response ){ // check result and redirect if failed. $result=json_decode($api_json_response,TRUE); if(!($result['success'])){ // die or redirect or whaterver you fancy. // print_r($result); // die("Failed verification."); wire('session')->redirect('/some-help-page-or-something/'); } }else{ // die or redirect or whaterver you fancy. die("No response from verification server."); } } }); If we have a comment that's been submitted then I check the token with Cloudflare. If it fails we can redirect or die or something - it'd be nice to fail a bit more gracefully. No idea how well this will deal with spam and I think I'll need to do some user testing but I think it might be useful.
  8. Thanks @elabx(although slightly discouraging that even you gave up...). I think I'm going to switch tack and hook in ProcessPageView::execute to check the token and then just redirect if it fails. I'll let you know how I get on....
  9. We're building a site that's going to be using the Comments Module and I'm looking at ways to mitigate the amount of spam I'm expecting. Alongside the usual honeypot / Akismet solutions I was thinking of using Cloudflare's Turnstile captcha doodah. I was thinking of either just not rendering the form until the Turnstile challenge was completed or hooking into the Comments module to validate the Turnstile token, or hell, maybe both. Has anyone done something like this already? It's not obvious to me how to hook into a Comment submission - so if anyone can point me in the right direction that would be a good start.
  10. Could you first do a simple test to see if your lat and lng are between the highest and lowest values in your polygon (so treat it like a square on first pass). Then once you've got a (hopefully much smaller) list of pages that are in that rough area you can loop through the page array and see if they are actually within the polygon (either using Leaflet or there's bound to be some php code out there). It might not help in your particular case but it might be a good strategy to try first. And of course if you live near London (hello) or anywhere else at zero longitude then you'll have to deal with that...
  11. It's not exactly a magazine but I did write up a move we did for a site from WP to PW: It has lots of tagging and authors and and so on. And that write up has some handy before and after metrics in which might help you convince them...
  12. So ... we're ditching Google Analytics for one of our sites (hooray) and switching it over to Fathom. Because we wanted to be able to make a comparison between the old stats and the new ones we've had both installed on the site for a couple of months. On top of that the site is behind Cloudflare so we also have stats directly from them. I've just pulled together the stats for the last 30 days: So thats: 37K visitors according to Google, 42K for Fathom 80K for Cloudflare My guess is that a Google is both fussiest about tying requests to real people (cos hey that's what they're after), but also most likely to be blocked by ad blockers and privacy tools. Fathom is probably less likely to be blocked by privacy tools; we use a bespoke domain which hopefully isn't blocked that often. Cloudways isn't is going to be blocked by anything (it's gathering stats at sever level) but maybe is more lenient about what constitures a 'visitor'. I'm not really sure about that but it is a big jump from Google. Anyway - I thought it was an interesting comparison and handy for when you need to explain to clients that analytics are useful for trends but not much cop if you're after real numbers.
  13. When we've needed to import images from an old CMS (looking at you WordPress) to a new PW installation then I've just used the API to add an image to a field by passing the URL of the image. I normally just run a PHP script that looks something like this <?php namespace ProcessWire; /** * * Import data from a CSV * */ // Boostrap PW. // this example lives in an 'import' folder include("../index.php"); echo 'in import <br><br>'; // read in data from your CSV file in the same folder while ($row = $files->getCSV('your_export.csv')) { // in this case we already had pages created in PW which we mapped to the old system // using a field 'article_key' so we're using that here to find the right page. // You can do something similar or you could create a new page here. $article_key=$row['article_key']; $p = $pages->get("template=article,article_key={$article_key}"); if ($p->id) { // check we've got a page echo '<br> Got page ID: ' . $p->id; // Check if this CSV row has a field containing our image path if ($row['article_pic'] !== '') { // in this case it was the path to a url on the old site (which obvs // still needs to be accessible online). $post_image = 'https://our-old-site.co.uk/uploads/' . $row['article_pic']; $p->setOutputFormatting(false); $p->featured_image->removeAll(); // remove any old version of the image if you want $p->featured_image->add($post_image); // add our new image $p->save(); echo ' Added: ' . $row['article_pic'] . PHP_EOL; } else { echo ' No image'; } } } This code is just cobbled together from things I've done in the past so you'll need to fix it your needs. If you have 4,500 images then you'll want to run the import in batches rather than trying to do them all at once.
  14. If you want to keep it super simple, you can also just use 'now' or 'today' to compare against your date field: $future_events=$page->children("event_when>'now'");
  15. Here's another alternative to add to the list that looks pretty interesting: https://withcabin.com/ Not being charged for pageviews might work for some of our larger clients and the dashboard looks like a happy medium between Fathom and Mamoto both of which we currently use. We've managed to shift a few clients away from GA recently. In part because the shift to GA4 meant there was going to be some disruption anyway so they might as well switch to a more privacy friendly alternative. Also interesting is that I found out about Cabin on Mastodon instead of Twitter .... that's probably a different thread tho.
  16. Grouping your posts by year is a different use case than pagination which is just showing a fixed number of posts per page. For example you might have a year with dozens of posts that still need to be paginated. When we've needed to do this in the past we've used URL segments to pull out a year and then build a selector from that. We can then use the results from that query for pagination if need be. The code below uses a URL like /blog/by-year/2020 ; you could easily build a subnav that has the years in you need to filter by. Obviously you'd need to update the code to match your fields but hopefully it will point you in the right direction. <?php namespace ProcessWire; $filter_title=''; // Do we have url parameters? if ($input->urlSegment1 ) { // in this example we only filter by year if($input->urlSegment1 =='by-year'){ $year=(int)$input->urlSegment2; $year_end=$year . '-12-31'; $year_start=$year . '-01-01'; if($year > 2000 && $year < 2050){ // not really santizing but I just don't want to encourage any Widdecombe of the Week behaviour. $filter_title=$year; } $results = $pages->find("template=news_item, publish_from<=".$year_end.",publish_from>=".$year_start.",limit=12, sort=publish_from"); }else{ $filter_title="Sorry - unknown filter."; } }else{ $results = $pages->find("template=news_item, limit=12, sort=-publish_from"); } if($filter_title){ echo '<h2>' . $filter_title .'</h2>'; } if($results && $results->count){ $pagination = $results->renderPager(); echo '<div class="news-list">'; foreach($results as $result) { echo '<div class="news-item">'; echo '<div class="news-title"><a href="'.$result->url . '">' . $result->title .'</a></div>'; echo '<div class="news-date">' . $result->publish_from .'</div>'; echo '<div class="news-summary">' . $result->summary .'</div>'; echo '</div>'; } echo "</div>"; echo '<div class="text-center">' . $pagination .'</div>'; }else{ echo '<div class="news-list">No results found.</div>'; }
  17. If you're using Markup Regions as your template strategy then make sure you're acually outputing your code into a valid region otherwsise it's going to be discarded (that's fooled me before).
  18. https://50thbirthday.londonfriend.org.uk/ This is a site we created to celebrate the 50th anniversary of London Friend which is the UK's oldest LGBTQ+ charity. It has a timeline that covers significant events of the charity's history together with a showcase of 50 inspirational Londoners who have made a difference to life in the capital. The technical side of things is pretty much as you imagine. One choice we made was not to use ajax for loading the timeline events but instead loading all of the html and then leaning hard into caching and lazy loading of images. We did use @markus_blue_tomato 's imageBlurHash module to produce placeholders for our lazily loaded images - although honestly you rarely get to see them. For some of the pages the client wanted to be able to add footnotes so we created a text formatter than moves any text in a content block surrounded in square brackets into footnotes section and creates an anchor link. I'll tidy that up and pop it on GitHub when I get some time but feel free to nag me if you think it might be useful to you. Other modules of note were ProCache and (of course) TracyDebugger. We also have some galleries on the site that use PhotoSwipe which is still our g to script for phot galleries. We got great marks in Lighthouse, Observatory and Wave (even the timeline itself which is a complicated page still does very well). It was great to be part of the celebrations (just a shame that I'm on holiday when the launch party happens... dammit)
  19. If you're just after making your image a wonky shape then you can probably just use some fancy css border radius values. Here's a handy looking site for generating the code that looks promising: https://9elements.github.io/fancy-border-radius/
  20. I do use Project Manager already and it's definitely a must have extension. Cool. Whilst we're sharing tips though - on a Mac you can set up a Quick Action in Automater to open a folder in VS Code: the command I've got in that Quick Action is open -n -b "com.microsoft.VSCode" --args "$*" On my Linux box I'm using Dolphin on KDE and get offered the option to 'Open with VSCode' when I right click on a folder ... I think thats just happens by default; I don't remember ever setting that up.
  21. Because I have dozens of PW projects on the go (I really must tidy up my code folder) I didn't want to include the core files for each projects. The best solution I managed was to have a single copy of the core files in a folder which I then add to the intelephense environment path settings: "intelephense.environment.includePaths": [ "/Users/stephen/code/stubs/", "/home/stephen/code/stubs/" ], It would be great to have PW stubs in the core Intelephense list but I have no idea how to even start going about that.
  22. My first thought was that URL hooks might help: https://processwire.com/blog/posts/pw-3.0.173/ my second thought is that if your aim is to improve search rankings then I'm not convinced that this is really going to improve things (and indeed might even make thing worse). Having a concise URL is good for humans but I bet Google doesn't care and may even prefer longer URLs. Looking at the site I bet your time would be better spent optimising performance; if you can bump up that Lighthouse score for page speed I bet that would make much more of a difference than tweaking URLs. Great looking pictures though.
  23. In Test A your PHP Social Stream code is loading it's own copy of jQuery. At about line 974 you have: <!-- PHP Social Stream By Axent Media --> <link href="https://www.ncoinc.org/site/templates/social-stream/public/css/colorbox.css" rel="stylesheet" type="text/css" /> <link href="https://www.ncoinc.org/site/templates/social-stream/public/css/styles.css" rel="stylesheet" type="text/css" /> <script type="text/javascript" src="https://www.ncoinc.org/site/templates/social-stream/public/js/jquery.min.js"></script> that's as well as the version of jQuery you're loading at the bottom of the page. Obviously it's not good to be loading two versions of jQuery. In Test B the Hannah code isn't loading its own version of jQuery. I'm guessing the plugin code is looking for the presence of jQuery in the markup and finds it in the second but not the first - perhaps due to the way the page is being built. The first thing I'd try would be to move loading jQuery into the HEAD section of the site rather than leaving it until last. Techincally that's going to slow down your rendering but means jQuery will be loaded early if you want to add scripts inline. Of course the best solution is not to use jQuery or include social media widgets ?
  24. I like a little bit of chaos personally. Can I stick a vote in for NTS Radio - they have a fantastic range of music.
  25. After having logged this on GitHub and having wiser people than me investigate, we managed to figure out that this only happens when using Varnish cache (in particular using Varnish on a Cloudways server). Disabling Varnish was enough to fix the problem. I'll leave it here though because hey, it's a nice bug, and maybe it'll help someone else.
  • Create New...