I'm an certified Drupal Triple Expert with {{ get_years_of_experience() }} years of experience, a Drupal core contributor, public speaker, live streamer, and host of the Beyond Blocks podcast.
+
+
+
+
diff --git a/sculpin/source/_includes/banner.html.twig b/sculpin/source/_includes/banner.html.twig
new file mode 100644
index 000000000..8f5f7f47a
--- /dev/null
+++ b/sculpin/source/_includes/banner.html.twig
@@ -0,0 +1,9 @@
+{% if site.banner_text %}
+
diff --git a/sculpin/source/_includes/main-menu.html.twig b/sculpin/source/_includes/main-menu.html.twig
new file mode 100644
index 000000000..3a8518172
--- /dev/null
+++ b/sculpin/source/_includes/main-menu.html.twig
@@ -0,0 +1,7 @@
+
diff --git a/sculpin/source/_includes/message.html.twig b/sculpin/source/_includes/message.html.twig
new file mode 100644
index 000000000..f6924a093
--- /dev/null
+++ b/sculpin/source/_includes/message.html.twig
@@ -0,0 +1,9 @@
+{#
+
+
+
+ Work with me and support the Drupal project. 5% from any Drupal-related product or service is donated to the Drupal Association.
+
+
+
+
#}
diff --git a/sculpin/source/_includes/podcast/other-episodes.html.twig b/sculpin/source/_includes/podcast/other-episodes.html.twig
new file mode 100644
index 000000000..ab609f0ea
--- /dev/null
+++ b/sculpin/source/_includes/podcast/other-episodes.html.twig
@@ -0,0 +1,22 @@
+{% set other_episodes = [] %}
+
+{% for episode in all_episodes %}
+ {% if guest in episode.guests and episode.topic is not same as topic %}
+ {% set other_episodes = other_episodes|merge([episode]) %}
+ {% endif %}
+{% endfor %}
+
+{% if other_episodes is not empty %}
+
+
+
+ {% block abstract %}{% endblock %}
+ {% endif %}
+
+ {% if page.new_events %}
+ {% include 'presentation/events-new' with {
+ events: page.events,
+ } %}
+ {% else %}
+ {% include 'presentation/slides' with {
+ speakerdeck: page.speakerdeck,
+ } %}
+
+ {% include 'presentation/video' with {
+ video: page.video,
+ } %}
+
+ {% include 'presentation/events' with {
+ events: page.events,
+ } %}
+ {% endif %}
+{% endblock %}
diff --git a/sculpin/source/_pages/404.md b/sculpin/source/_pages/404.md
new file mode 100644
index 000000000..84c8f7b0e
--- /dev/null
+++ b/sculpin/source/_pages/404.md
@@ -0,0 +1,21 @@
+---
+layout: page
+title: Page not found
+---
+
+{% block content %}
+Perhaps you were looking for one of these pages:
+
+- [Products and services](/pricing)
+- [Public speaking and workshops](/presentations)
+- [My daily email list](/daily)
+- [The Beyond Blocks podcast](/podcast)
+
+If you were looking for something else, you can [search for it](/search).
+{% endblock %}
+
+{% block content_bottom %}
+ {% include 'daily-email-form.html.twig' with {
+ intro: 'Sign up and get daily emails about Drupal, PHP and software development.'
+ } %}
+{% endblock %}
diff --git a/sculpin/source/_pages/atdc-welcome.md b/sculpin/source/_pages/atdc-welcome.md
new file mode 100644
index 000000000..67a19af84
--- /dev/null
+++ b/sculpin/source/_pages/atdc-welcome.md
@@ -0,0 +1,24 @@
+---
+layout: page
+title: Welcome to the Test-Driven Drupal email course
+meta:
+ title: Welcome to the Test-Driven Drupal email course | %site.name%
+---
+
+{% block head_meta %}
+
+{% endblock %}
+
+{% block content %}
+Hi. Thank you for registering for my Test-Driven Drupal email course.
+
+Every email will be sent from my personal email address, so if you have a question about any email, press reply, send it to me, and I'll personally respond.
+
+I'd also appreciate any feedback as you work through or complete the course.
+
+Remember to check your inbox and confirm your email address to ensure you get the lessons.
+
+In the meantime, why not [go to my daily email archive](/archive) and read some of my previous daily emails?
+
+Found this page by accident? [Consider subscribing for the course](/atdc).
+{% endblock %}
diff --git a/sculpin/source/_pages/atdc.md b/sculpin/source/_pages/atdc.md
new file mode 100644
index 000000000..77aa24660
--- /dev/null
+++ b/sculpin/source/_pages/atdc.md
@@ -0,0 +1,26 @@
+---
+layout: page
+title: Automated Testing in Drupal email course
+---
+
+{% block content %}
+
+Do you want to learn how to write automated tests and do test-driven development in Drupal?
+
+Register below for my free 10-day email course, where you'll learn to write a new Drupal module from scratch with automated tests.
+
+Learn to test things like:
+
+* Whether pages exist.
+* Whether users can view specific pages based on their permissions.
+* Whether certain content is visible on a page given particular circumstances.
+* Refactoring code to test dedicated classes such as Services, Repositories, Builders and Actions.
+
+{% include 'email-course-form.html.twig' %}
+{% endblock %}
+
+{% block content_bottom %}
+
+ {% include 'testimonials' with { tag: 'atdc' } %}
+
+{% endblock %}
diff --git a/sculpin/source/_pages/bcm.md b/sculpin/source/_pages/bcm.md
new file mode 100644
index 000000000..2e09fd13c
--- /dev/null
+++ b/sculpin/source/_pages/bcm.md
@@ -0,0 +1,30 @@
+---
+layout: page
+title: Hi, PHP UserGroup Munich!
+---
+
+{% block head_meta %}
+
+{% endblock %}
+
+{% block content %}
+Thanks for attending my talk on [Build Configs](/build-configs).
+
+[These are the slides](/talks/building-build-configs) I presented and the example video:
+
+{% include 'youtube-video' with { id: 'LkhsdmxReUc'} %}
+
+From nothing to a working Drupal 10 website in less than a minute!
+
+If you have any further questions, let me know.
+
+## Can we work together?
+
+[Get in touch](/contact) if you're interested in using Build Configs-based managed configuration files in your projects (I offer one-off implementations or ongoing updates via a subscription), you need an in-house tool built for your team, or if we can [work together another way](/pricing).
+
+## While you're here
+
+Why not join my [daily email list](/daily) or register for my [free email course](/atdc) on automated testing in Drupal?
+
+Oliver
+{% endblock %}
diff --git a/sculpin/source/_pages/bco.md b/sculpin/source/_pages/bco.md
new file mode 100644
index 000000000..a47b33c70
--- /dev/null
+++ b/sculpin/source/_pages/bco.md
@@ -0,0 +1,32 @@
+---
+layout: page
+title: Hi, PHP Oxford!
+---
+
+{% block head_meta %}
+
+{% endblock %}
+
+{% block content %}
+Thanks for attending my talk on [Build Configs](/build-configs).
+
+[These are the slides](/presentations/building-build-configs) I presented and the example video:
+
+{% include 'youtube-video' with { id: 'LkhsdmxReUc'} %}
+
+From nothing to a working Drupal 10 website in less than a minute!
+
+If you have any further questions, let me know.
+
+## Can we work together?
+
+[Get in touch](/contact) if you're interested in using Build Configs-based managed configuration files in your projects (I offer one-off implementations or ongoing updates via a subscription), you need an in-house tool built for your team, or if we can [work together another way](/pricing).
+
+If you liked the talk, or [any of my others](/presentations), I can also present talks and run workshops for companies.
+
+## While you're here
+
+Why not join my [daily email list](/daily) or register for my [free email course](/atdc) on automated testing in Drupal?
+
+Oliver
+{% endblock %}
diff --git a/sculpin/source/_pages/blog.html.twig b/sculpin/source/_pages/blog.html.twig
new file mode 100644
index 000000000..bcb51dc59
--- /dev/null
+++ b/sculpin/source/_pages/blog.html.twig
@@ -0,0 +1,27 @@
+---
+title: Blog
+generator: pagination
+pagination:
+ max_per_page: 30
+ provider: data.posts
+use: [posts]
+---
+
+{% if site.features.show_blog_post_count %}
+ {{ data.posts|length|number_format }}
+{% endif %}
+
+
+ {% endfor %}
+
+ {% if page.pagination.previous_page or page.pagination.next_page %}
+
+ {% endif %}
+
diff --git a/sculpin/source/_pages/brumphp.md b/sculpin/source/_pages/brumphp.md
new file mode 100644
index 000000000..1736a45d4
--- /dev/null
+++ b/sculpin/source/_pages/brumphp.md
@@ -0,0 +1,38 @@
+---
+layout: page
+title: BrumPHP
+---
+
+{% block head_meta %}
+
+{% endblock %}
+
+{% block content %}
+Thank you for attending my Sculpin talk at BrumPHP.
+
+[The slides and a previous recording](/talks/building-static-websites-sculpin) are on the talk page.
+
+To see an example of a Sculpin project, you can see [the source code for this website](https://github.com/opdavies/oliverdavies.uk) or [my demo for PHP South West](https://github.com/opdavies/phpsw-sculpin-demo).
+
+If you want to try Sculpin, you can use [the Sculpin Skeleton project I created](https://github.com/opdavies/sculpin-skeleton) as a starting point for new projects.
+
+## Can we work together?
+
+As well as building static websites with Sculpin, I do consulting and advisory work using other PHP projects - namely, Drupal and Symfony.
+
+I offer [unlimited monthly Drupal consulting](/consulting), [1-on-1 consultation calls](/call) and [pair programming sessions](/pair) to get people unstuck, and fixed prices (not estimates) for development work.
+
+## Are you stuck on Drupal 7, 8 or 9?
+
+Drupal 7 will be end-of-life and no longer supported as of the 5th of January, 2025.
+
+Drupal 8 and 9 are already unsupported.
+
+If you have an outdated Drupal website and want to know more about upgrading to Drupal 10, [book your upgrade roadmap](/drupal-upgrade) and I'll help you get there.
+
+## Stay in touch
+
+The best way to stay in touch with me is my [email list](/daily) to which I send daily emails about Drupal and software development.
+
+All emails are sent from my personal email address, so you can press reply and contact me directly.
+{% endblock %}
diff --git a/sculpin/source/_pages/build-configs.md b/sculpin/source/_pages/build-configs.md
new file mode 100644
index 000000000..c00691186
--- /dev/null
+++ b/sculpin/source/_pages/build-configs.md
@@ -0,0 +1,78 @@
+---
+layout: page
+title: "Build Configs - configuration files as a service"
+products:
+ -
+ price: 2500
+ link: https://buy.stripe.com/14kbJs98K1fc0p29AC
+ buttonText: Get started
+ -
+ price: 1000
+ link: https://buy.stripe.com/7sIdRAbgS8HEfjWfZ1
+ buttonText: Sign up
+---
+
+**Sprint zero in a box.**
+
+All of my projects contain similar build configuration files, such as Docker and Docker Compose files, PHPUnit and PHPStan configuration, and CI/CD pipeline workflows. I used to maintain these manually which was time-intensive and there was no guarantee that the same features or options were available from project to project.
+
+Build Configs is a tool I've written to maintain a canonical set of templates and generate them for each project based on a per-project configuration file.
+
+This makes it quicker and easier for me to create or onboard new projects to work on and, as all of the base files are centralised, when I fix something or add a new feature, the changes can be pushed to every project that uses it.
+
+I've been using this on personal projects such as my [Drupal](https://github.com/opdavies/docker-example-drupal), [LocalGov Drupal](https://github.com/opdavies/docker-example-drupal-localgov) and [Drupal Commerce Kickstart](https://github.com/opdavies/docker-example-drupal-commerce-kickstart) Docker examples and for various client projects.
+
+## How does it work?
+
+- I create a `build.yaml` file for the project that contains its configuration - what version of PHP it needs, which web server to use, which paths to run automated tests from or static analysis on, etc.
+- I run the `build-configs` tool which generates the required files, such as a Dockerfile, Docker Compose file, PHPStan and PHPUnit configuration files, and a `run` script for automating project commands.
+- The files are committed to version control and pushed to your code repository (GitHub, GitLab, Bitbucket, etc).
+- As I add new features or make changes, I will re-run the process to re-generate the files and push any changes - ensuring you’re always up to date.
+
+## What are some of the recent new features?
+
+* Adding additional databases for working with migrations or multi-site projects.
+* Adding additional `run` tasks.
+* Adding CI pipelines with GitHub Actions to run automated quality checks.
+* Creating and running Git hooks to run automated checks before changes are pushed.
+
+## Example
+
+In this short video, I show how Build Configs works by creating a new Drupal 10 project, generating its configuration files, and opening it in a browser.
+
+From nothing to a ready-to-work-on Drupal website in less than a minute.
+
+{% include 'youtube-video.html.twig' with { id: 'LkhsdmxReUc'} %}
+
+## Options
+
+### Initial project setup - £{{ page.products.0.price|number_format() }}
+
+To get your project started on the right foot, I will create a new Drupal project skeleton for you using `build-configs` that is ready to work on, including all of the generated files. You can even edit them if you like as they will get no ongoing updates.
+
+{% include 'button.html.twig' with {
+ text: page.products.0.buttonText,
+ url: page.products.0.link,
+} %}
+
+### Ongoing updates and support (single site) - £{{ page.products.1.price|number_format }} per month
+
+Once you have a project with `build-configs`, if you want to receive ongoing updates to the generated files, I can automatically refresh your files once changes are available and push them to your code repository.
+
+This also includes unlimited support via a dedicated Slack channel and you can make feature requests for me to consider adding to the roadmap.
+
+If you like, I can add you to a beta users list and you’ll get new experimental features before anyone else.
+
+{% include 'button.html.twig' with {
+ text: page.products.1.buttonText,
+ url: page.products.1.link,
+} %}
+
+
+If you have multiple Drupal sites, [contact me](/contact) and we can work something out.
+
+### Retro-fitting into an existing project
+
+`build-configs` can also be added to an existing project. It can be tricky and will be different from project to project so [contact me](/contact) and we can discuss it further.
+
+Once I know more about your project, I’ll be happy to give you a fixed-price quote to do the work.
diff --git a/sculpin/source/_pages/call.html.twig b/sculpin/source/_pages/call.html.twig
new file mode 100644
index 000000000..9789c35d0
--- /dev/null
+++ b/sculpin/source/_pages/call.html.twig
@@ -0,0 +1,124 @@
+---
+layout: page
+title: Book a 1-on-1 consulting call
+link: https://savvycal.com/opdavies/consulting-call
+price: 350
+faqs:
+ -
+ - What happens after I pay?
+ - Your call is scheduled at the time you pay. So after payment is received, we're all set!
+ -
+ - What if I can't find a suitable time?
+ - If you can't find a suitable time, due to your time zone, or weekly schedule, just send me an email, and we can arrange something. I try to be as flexible as possible!
+ -
+ - Can my colleague or business partner join the call?
+ - No. This is a 1-on-1 call. You are welcome to record the conversation to share it after the fact.
+ -
+ - Do you offer something more hands-on?
+ - Sure! Book a pair programming session instead and we’ll work on your code together.
+ -
+ - I still have questions!
+ - No problem. Send me an email at oliver@oliverdavies.uk.
+---
+
+{% block content %}
+
+{#
Who is this for?
#}
+
+{# Pain #}
+
+{# Dream #}
+
+{# Fix #}
+
+
As a professional Software Developer and Consultant with {{ get_years_of_experience() }} years of Drupal and PHP experience, I have a lot of knowledge that I use to help customers and their projects.
+
+{# 1st call to action #}
+
+{% include 'button' with {
+ position: 'centre',
+ text: 'Book your call now',
+ url: page.link,
+ withArrow: true,
+} %}
+
+
Some things I can help you with
+
+
+
How to approach a new project or task.
+
PHP and Drupal fundamentals and best practices.
+
Adding eCommerce functionality with Drupal Commerce.
+
Upgrading Drupal websites from unsupported versions.
+
Writing your first automated tests with PHPUnit or starting with test-driven development.
+
Introducing static analysis or other code quality tools to your project.
+
Adopting a utility-first CSS approach, e.g. Tailwind CSS, within a new or existing theme.
+
Configuring continuous integration (CI) pipelines with GitHub Actions, GitLab CI or Bitbucket Pipelines.
+
Automating tasks with Docker or Ansible.
+
Help fixing a bug or some broken code.
+
Reviewing your code and providing advice and suggestions.
+
+
+
In this 1-on-1 video call, I can help you answer questions in these areas or any others you may have.
+
+{% include 'button' with {
+ position: 'centre',
+ text: 'Book your call now',
+ url: page.link,
+ withArrow: true,
+} %}
+
+{# Social proof #}
+
+{% include 'testimonials' with { tag: 'call' } %}
+
+{# Overcome objections #}
+
+
100% money-back guarantee!
+
+
If you don't find the call valuable, just let me know, and I'll refund 100% of the cost.
+
+
Frequently asked questions
+
+{% for faq in page.faqs %}
+
+
{{ faq.0 }}
+
{{ faq.1|raw }}
+
+{% endfor %}
+
+{# Uniqueness #}
+
+
Who am I?
+
+
+
I'm an Acquia-certified Drupal expert with {{ get_years_of_experience() }} years of professional development experience.
+
I'm a former Drupal Association employee who was responsible for improving and maintaining Drupal.org.
+
I'm a Drupal core contributor and maintain numerous Drupal projects, including the Override Node Options module, which is used on over 38,000 websites.
+
I'm a multiple-time DrupalCon speaker who regularly presents talks and workshops at conferences and meetups.
+
+
+{# 2nd CTA #}
+
+{% embed 'callout.html.twig' with {
+ title: 'Ready to book your call?',
+} %}
+ {% block callout_content %}
+ {% include 'button.html.twig' with {
+ text: 'Book your call now for £' ~ page.price|number_format ~ '',
+ url: page.link,
+ withArrow: true,
+ } %}
+
+
You can pay and reserve your time slot straight away. There are a limited number of available slots each month. If, after the call, you decide to do a longer engagement with me, the cost of this call will be deducted from that engagement.
I’m only available for a few 1-on-1 calls per month.
+
+
Scheduling is first come, first served, so the sooner you book your time slot, the sooner you will have the answers you need to move your project forward.
+
+{% endblock %}
diff --git a/sculpin/source/_pages/contact.md b/sculpin/source/_pages/contact.md
new file mode 100644
index 000000000..adcb1f45d
--- /dev/null
+++ b/sculpin/source/_pages/contact.md
@@ -0,0 +1,10 @@
+---
+layout: page
+title: Contact Oliver
+---
+
+The best way to get in touch with me is via email. I usually reply within one business day.
+
+I'm also on [LinkedIn][linkedin].
+
+[linkedin]: https://www.linkedin.com/in/opdavies
diff --git a/sculpin/source/_pages/dcg.md b/sculpin/source/_pages/dcg.md
new file mode 100644
index 000000000..74d3f8758
--- /dev/null
+++ b/sculpin/source/_pages/dcg.md
@@ -0,0 +1,36 @@
+---
+layout: page
+title: DrupalCamp Ghent 2024
+---
+
+{% block head_meta %}
+
+{% endblock %}
+
+{% block content %}
+Thank you for attending my session at DrupalCamp Ghent.
+
+You can view the slides and previous recordings of my [Test Driven Drupal][] and [Tailwind CSS][] talks on this website.
+
+If you want to learn more, I released a video of the [Tailwind CSS workshop][workshop] I gave for DrupalCamp Florida, and I've created a [free 10-day email course][course] on automated testing in Drupal.
+
+## Can we work together?
+
+Automated testing, test-driven development and Tailwind CSS are some of my favourite subjects to teach.
+
+If you want me to teach your team to do test-driven development or build UIs with Tailwind CSS, team training is included within my [unlimited monthly Drupal consulting][consulting].
+
+## Stay in touch
+
+The best way to stay in touch with me is my [email list] to which I send daily emails about Drupal and software development.
+
+All emails are sent from my personal email address, so you can press reply and contact me directly.
+
+[consulting]: /consulting
+[course]: /atdc
+[email list]: /daily
+[tailwind css]: /presentations/taking-flight-with-tailwind-css
+[test driven drupal]: /presentations/tdd-test-driven-drupal
+[workshop]: /daily/2024/01/22/tailwind-css-workshop-recording
+
+{% endblock %}
diff --git a/sculpin/source/_pages/drupal-london.md b/sculpin/source/_pages/drupal-london.md
new file mode 100644
index 000000000..2870d5d86
--- /dev/null
+++ b/sculpin/source/_pages/drupal-london.md
@@ -0,0 +1,40 @@
+---
+layout: page
+title: Drupal London
+---
+
+{% block head_meta %}
+
+{% endblock %}
+
+{% block content %}
+Thank you for attending my session for the Drupal London meetup.
+
+You can [view the slides and previous recording][slides] of the talk on this website and [the example repository on GitHub][repo].
+
+If you want to learn more, I released a [free 10-day email course][course] on automated testing in Drupal and [live stream on YouTube][youtube]. Most recently, [I contributed tests to the Content Access by Path module][video].
+
+## Can we work together?
+
+Drupal, automated testing and test-driven development are some of my favourite subjects to teach.
+
+If you want me to teach your team to do test-driven development, team training is included within my [unlimited monthly Drupal consulting][consulting].
+
+## Stay in touch
+
+The best way to stay in touch with me is my [email list] to which I send daily emails about Drupal and software development.
+
+All emails are sent from my personal email address, so you can press reply and contact me directly.
+
+I also host the [Beyond Blocks podcast][podcast] where I discuss Drupal, PHP and other software-related topics with people.
+
+[consulting]: /consulting
+[course]: /atdc
+[email list]: /daily
+[podcast]: /podcast
+[repo]: http://github.com/opdavies/drupal-london-meetup
+[slides]: /presentations/tdd-test-driven-drupal
+[video]: https://www.youtube.com/watch?v=XTpliKd47Lg
+[youtube]: https://www.youtube.com/@opdavies
+
+{% endblock %}
diff --git a/sculpin/source/_pages/drupal-upgrade.md b/sculpin/source/_pages/drupal-upgrade.md
new file mode 100644
index 000000000..47279d3f4
--- /dev/null
+++ b/sculpin/source/_pages/drupal-upgrade.md
@@ -0,0 +1,86 @@
+---
+layout: page
+title: Are you stuck on Drupal 7, 8 or 9?
+button:
+ text: Book your roadmap now
+ url: https://buy.stripe.com/aEU4h0gBc4ro0p27sz
+---
+
+{# Pain #}
+
+Drupal 7 will be unsupported on the **5th of January 2025**.
+
+Drupal 8 has been unsupported since **November 2021**, and Drupal 9 since **November 2023**.
+
+Are you stuck on any of these versions?
+
+* Do you need to upgrade your website before it reaches its end-of-life date and is no longer supported?
+* Are you worried about rebuilding your website and migrating your data to Drupal 10?
+* Is all of your business logic embedded within your Drupal code, making it hard to upgrade?
+* Do you rely on modules or themes that are no longer updated or don't exist for Drupal 10?
+
+{# Dream #}
+
+## What if you had a clear path how to upgrade your website?
+
+* What if you had a recommended approach to upgrade your website to Drupal 10?
+* What if you knew about potential issues ahead of time?
+* What if you had a list of modules to use in Drupal 10, alternatives for any missing modules, and what functionality would need custom code?
+
+{# Fix #}
+
+## Drupal upgrade roadmap
+
+An upgrade roadmap is a personalised audit of your Drupal website and includes details and actionable steps to upgrade it, including identifying potential blockers you might encounter.
+
+{# 1st call to action #}
+
+{% include 'button.html.twig' with {
+ text: page.button.text ~ ' ',
+ url: page.button.url,
+ withArrow: true,
+} %}
+
+## How does it work?
+
+* After receiving payment, we will arrange an initial call to discuss your project.
+* You share your source code and provide access to your website.
+ * I'll need access to the source code for your website. Ideally, this is via GitHub, GitLab, Bitbucket, or whatever online version control you currently use, but I can also audit a zip file of your Git repository.
+ * I'll need a user account to access your website to review its configuration, such as content types, user roles, etc. You can delete this once the engagement is complete.
+ * Alternatively, I can use an export of the database to get a local version of your site up and running. Ideally, this would be sanitised to remove personal information such as usernames, email addresses and passwords.
+* I'll send you the document once it's complete for you to review.
+* We'll arrange a follow-up call to go through it in detail and for you to ask any questions.
+
+{# 2nd CTA #}
+
+{% include 'button.html.twig' with {
+ text: page.button.text,
+ url: page.button.url,
+ withArrow: true,
+} %}
+
+{# Social proof #}
+
+{% include 'testimonials.html.twig' %}
+
+{# Overcome objections #}
+
+{# Uniqueness #}
+
+## Who am I?
+
+* I'm an Acquia-certified Drupal expert with {{ get_years_of_experience() }} years of professional development experience.
+* I'm a former Drupal Association employee who was responsible for improving and maintaining Drupal.org.
+* I'm a Drupal core contributor and maintain numerous Drupal projects, including the Override Node Options module, which is used on over 38,000 websites.
+* I'm a multiple-time DrupalCon speaker who regularly presents talks and workshops at conferences and meetups.
+
+{# Urgency #}
+
+## Availability is limited
+
+There will be no further extensions of Drupal 7 support, and I have limited availability due to the time required, so buy now to secure your place.
+
+{% include 'button.html.twig' with {
+ text: page.button.text ~ ' →',
+ url: page.button.url,
+} %}
diff --git a/sculpin/source/_pages/drupalcamp-ghent.md b/sculpin/source/_pages/drupalcamp-ghent.md
new file mode 100644
index 000000000..907b0518f
--- /dev/null
+++ b/sculpin/source/_pages/drupalcamp-ghent.md
@@ -0,0 +1,28 @@
+---
+layout: page
+title: DrupalCamp Ghent 2024
+---
+
+{% block head_meta %}
+
+{% endblock %}
+
+{% block content %}
+Thank you for attending my session at DrupalCamp Ghent.
+
+You can view the slides and previous recordings of my [Test Driven Drupal](/talks/tdd-test-driven-drupal) and [Tailwind CSS](/talks/taking-flight-with-tailwind-css) talks on this website.
+
+If you want to learn more, I released a video of the [Tailwind CSS workshop](/daily/2024/01/22/tailwind-css-workshop-recording) I gave for DrupalCamp Florida, and I've created a [free 10-day email course](/atdc) on automated testing in Drupal.
+
+## Can we work together?
+
+Automated testing, test-driven development and Tailwind CSS are some of my favourite subjects to teach.
+
+If you want me to teach your team to do test-driven development or build UIs with Tailwind CSS, team training is included in my [unlimited monthly Drupal consulting](/consulting).
+
+## Stay in touch
+
+The best way to stay in touch with me is my [email list](/daily) to which I send daily emails about Drupal and software development.
+
+All emails are sent from my personal email address, so you can press reply and contact me directly.
+{% endblock %}
diff --git a/sculpin/source/_pages/drupalgive.md b/sculpin/source/_pages/drupalgive.md
new file mode 100644
index 000000000..e4a7efe0c
--- /dev/null
+++ b/sculpin/source/_pages/drupalgive.md
@@ -0,0 +1,89 @@
+---
+layout: page
+title: My Drupal Contributions
+modules:
+ -
+ name: Block ARIA Landmark Roles
+ machine_name: block_aria_landmark_roles
+ type: module
+ usage: 1346
+ stars: 16
+ -
+ name: Copyright Block
+ machine_name: copyright_block
+ type: module
+ usage: 2514
+ stars: 10
+ -
+ name: Feature Toggle Twig
+ machine_name: feature_toggle_twig
+ type: module
+ -
+ name: Layout Builder Extra Templates
+ machine_name: override_node_options
+ type: module
+ usage: 325
+ stars: 11
+ -
+ name: Null User
+ machine_name: null_user
+ type: module
+ usage: 5
+ stars: 4
+ -
+ name: System User
+ machine_name: system_user
+ type: module
+ -
+ name: Tailwind CSS Starter Kit
+ machine_name: tailwindcss
+ type: theme
+ usage: 260
+---
+
+[This is my profile page on Drupal.org](https://www.drupal.org/u/opdavies) and these are some of the projects I maintain:
+
+
+ {% for project in page.modules|sort((a, b) => b.usage <=> a.usage) %}
+
+
+## Example Projects on GitHub
+
+- [Docker Example Drupal](https://github.com/opdavies/docker-example-drupal)
+- [Docker Example Drupal Commerce Kickstart](https://github.com/opdavies/docker-example-drupal-commerce-kickstart)
+- [Docker Example Drupal LocalGov](https://github.com/opdavies/docker-example-drupal-localgov)
+
+## Events
+
+- I was a speaker at DrupalCon Lille in October 2023.
+- I was a speaker at DrupalCon Europe 2020 (online).
+- I was a workshop trainer at DrupalCamp NYC 2020 (online).
+- I was a workshop trainer at DrupalCamp London 2020 (online).
+- I was a speaker at DrupalCon Amsterdam in October 2019.
+- I was a contribution day mentor at DrupalCon Vienna 2017.
+- I was a speaker at DrupalCamp Dublin 2017.
+- I was a speaker at DrupalCamp Bristol 2016.
+- I was an organiser of DrupalCamp Bristol (2016, 2017, 2019).
+- I was a contribution day mentor at DrupalCon Los Angeles and Barcelona 2015.
+- I was a speaker at DrupalCamp North 2015.
+- I was a speaker at DrupalCamp Brighton 2015.
+- I was a contribution day mentor at DrupalCon Amsterdam 2014.
+- I was a speaker at DrupalCamp London (2014, 2015, 2016, 2017, 2019).
+- I was a volunteer at DrupalCamp London 2014.
+- I was a contribution day mentor at DrupalCon Prague 2013.
+- I was an organiser of the South Wales Drupal user group (SWDUG) and Drupal Bristol user group.
+
+## Other
+
+- I'm a Board Member of the [Drupal England and Wales Association](https://drupal-england-wales.github.io) (DEW).
diff --git a/sculpin/source/_pages/dto.md b/sculpin/source/_pages/dto.md
new file mode 100644
index 000000000..9eb03feff
--- /dev/null
+++ b/sculpin/source/_pages/dto.md
@@ -0,0 +1,46 @@
+---
+layout: page
+title: Introduction to Automated Testing and Test-Driven Development with Drupal
+drupal_version: 10
+prices:
+ early: 299
+ full: 499
+is_early_bird: true
+next_date: 2024-02-23
+---
+
+Are you a Drupal Developer who wants to learn about automated testing and test-driven development, or do you manage a development team that you'd like to train?
+
+I've delivered large Drupal projects using automated tests and test-driven development for custom functionality, and maintain Drupal modules with thousands of installations whilst using their tests to ensure working code and prevent regressions.
+
+I offer an interactive full-day workshop (previously presented at DrupalCamp London, and remotely for DrupalCamp NYC) that provides an introduction to automated testing in Drupal and how to utilise test-driven development - which I've updated specifically for Drupal {{ page.drupal_version }}.
+
+## Contents
+
+- What is automated testing, and why write tests?
+- What types of tests are available in Drupal?
+- Outside-in vs. inside-out testing.
+- Configuring Drupal and PHPUnit to run tests locally.
+- Exercise: writing tests for existing Drupal core functionality.
+- Exercise: adding tests to an existing custom module.
+- What is test-driven development?
+- Exercise: writing a new Drupal module from scratch with test-driven development.
+- Q&A
+
+
+
+## Dates and prices
+
+The workshop is currently only available remotely, and the next available date is {{ page.next_date|date('F jS, Y') }}.
+
+Seats are available at {{ page.is_early_bird ? 'an early bird price of £' ~ page.prices.early : 'a price of £' ~ page.prices.full }}, with a 10% discount for bulk orders of 5 or more seats.
+
+{% include 'button.html.twig' with {
+ text: 'Book your seat',
+ url: 'https://buy.stripe.com/aEU9Bk2KmaPM3Be8wJ',
+ withArrow: true,
+} %}
+
+
+
+{% include 'testimonials' with { tag: 'testing' } %}
diff --git a/sculpin/source/_pages/glossary.html.twig b/sculpin/source/_pages/glossary.html.twig
new file mode 100644
index 000000000..4481d0370
--- /dev/null
+++ b/sculpin/source/_pages/glossary.html.twig
@@ -0,0 +1,35 @@
+---
+layout: page
+title: Glossary
+terms:
+ -
+ - Automated Testing
+ -
+ - CI pipeline
+ -
+ - Continuous integration
+ - |
+ The process of continuously integrating your code with other people's code, i.e. at least once a day.
+ This is done by regularly pulling and merging everyone else's code with yours and pushing yours so it's available for others to do the same. This can also refer to CI pipelines and tools like GitHub Actions and GitLab CI.
+ -
+ - Continuous Delivery and Deployment
+ -
+ - Git
+ -
+ - PHPStan
+ -
+ - Static analysis
+ -
+ - Test-Driven Development
+ -
+ - Trunk-based Development
+ -
+ - Version control
+---
+
+
+ {% for term in page.terms if term.1 %}
+
{{ term.0 }}
+
{{ term.1 }}
+ {% endfor %}
+
diff --git a/sculpin/source/_pages/homelab.md b/sculpin/source/_pages/homelab.md
new file mode 100644
index 000000000..21e6baaf4
--- /dev/null
+++ b/sculpin/source/_pages/homelab.md
@@ -0,0 +1,24 @@
+---
+title: Homelab
+---
+
+My homelab/home server is running on a TUXEDO InfinityBook Pro Gen7 laptop and uses [NixOS as the operating system][0].
+
+It hosts this website and a number of other static websites.
+
+I also use it to self-host these services:
+
+- [Audio Bookshelf](https://www.audiobookshelf.org)
+- [Forgejo](https://forgejo.org)
+- [Home Assistant](https://www.home-assistant.io)
+- [Homepage](https://gethomepage.dev)
+- [Immich](https://immich.app)
+- [Jellyfin](https://jellyfin.org)
+- [Mastodon](https://joinmastodon.org) (work in progress)
+- [Paperless-ngx](https://docs.paperless-ngx.com)
+- [Peertube](https://peertube.tv)
+- [Tube Archivist](https://www.tubearchivist.com)
+- [Uptime Kuma](https://uptime.kuma.pet)
+- [Vaultwarden](https://github.com/dani-garcia/vaultwarden)
+
+[0]: {{site.code.url}}/{{site.code.username}}/nix-config
diff --git a/sculpin/source/_pages/index.html.twig b/sculpin/source/_pages/index.html.twig
new file mode 100644
index 000000000..df78f0170
--- /dev/null
+++ b/sculpin/source/_pages/index.html.twig
@@ -0,0 +1,101 @@
+---
+layout: page
+permalink: /
+title: Oliver Davies - Drupal Developer, Consultant and Speaker
+meta:
+ title: Drupal Development and Consulting by Oliver Davies
+urls:
+ exploratory_call: https://savvycal.com/opdavies/drupal-consulting-exploratory-call
+---
+
+{% block content %}
+
+{# Pain #}
+
+
+
Are bugs and errors on your Drupal website losing you customers?
+
Are you stuck on an outdated or unsupported version of Drupal?
+
Are you unable to efficiently change your website and spend your time searching for workarounds?
+
Does it take too long to release new features and bug fixes?
+
Are you considering switching to Drupal from your current CMS and wondering if it's the right choice?
+
+
+
+
+
+
+{# Solution #}
+
+
What can I do for you?
+
+{# TODO: add more information about each of these. #}
+
+
+
Analysis & Audit. I can help you identify expensive bottlenecks, hidden issues, and potential problems with your site. I'll put together a detailed report with actionable next-steps on how to fix any issues.
+
Roadmap & Planning. We'll identify your goals and challenges, and I'll put together a custom roadmap to help you get there.
+
Implementation and Maintenance. I can build your project for you, or work with your engineering team to accelerate your progress.
+
Team Training. Give your team the skills they need to get more done. Grow, retain, and attract talented developers.
+
Ongoing Advisory. Throughout the duration of your project, I'll be available to review progress, answer questions, recommend tools and processes, share emerging best practices, and keep your project on the right track.
+
+
+
+
+
You're already running my code in production
+
+
I've contributed code to Drupal core and written popular contributed Drupal modules and themes, PHP and JavaScript libraries, and Tailwind CSS plugins.
+
+
For example, the Override Node Options module is used on around 40,000 active Drupal websites.
+
+
+
+
Here's some of my recent work
+
+
+
I developed a Drupal module to integrate with a third-party translation provider, enabling automated translations of English-only content from XML feeds into different languages.
+
I created an example eCommerce application using Drupal Commerce for an asset finance company for their presentation to a multinational eCommerce company.
+
I migrated a membership portal for a UK health association from Drupal 7 to Drupal 10, migrating their content and users and rebuilding the required custom functionality.
+
I presented workshops on automated testing and test-driven development at Drupal conferences.
+
I assisted a UK consultancy in hiring their in-house Senior Drupal Developer by providing technical input for interviews and candidate feedback.
+
I developed a Tailwind CSS-based theme for a multi-tenant Drupal application, delivering the theme scaffolding, build system, and initial components. I also trained and supported the in-house development team.
+
I developed a Drupal Commerce application for an annual international photography competition that allows photographers to pay and submit their entries, including custom scoring functionality for jurors.
+
+
+
+
+
Get in touch
+
+
Unlike working with large agencies, you'll only work directly with me. You won't be handed off sub-contractors, offshore teams or Junior Developers who are learning on the job.
+
+
I only give fixed prices so you'll know upfront how much my work will cost and all of my work is covered by a bug-free guarantee.
+
+
+{% include 'button.html.twig' with {
+ full_width: true,
+ text: 'Click here to email Oliver',
+ url: 'mailto:' ~ site.email,
+} %}
+
+
+
+{% include 'testimonials' with {
+ limit: 5,
+ tag: 'front',
+ title: 'Kind words from clients and customers',
+} %}
+
+
+
+
Get in touch
+
+
There’s no reason to wait. Drop me a line and I'll get back to you ASAP.
+
+{% include 'button.html.twig' with {
+ full_width: true,
+ text: 'Click here to email Oliver',
+ url: 'mailto:' ~ site.email,
+} %}
+{% endblock %}
diff --git a/sculpin/source/_pages/mob.md b/sculpin/source/_pages/mob.md
new file mode 100644
index 000000000..b21f34369
--- /dev/null
+++ b/sculpin/source/_pages/mob.md
@@ -0,0 +1,16 @@
+---
+layout: page
+title: An Introduction to Mob Programming
+---
+
+*
+*
+*
+*
+*
+
+
+ {% include 'youtube-video' with { id: '28S4CVkYhWA' } %}
+ {% include 'youtube-video' with { id: 'ri-35oHWIU8' } %}
+ {% include 'youtube-video' with { id: 'jPLEbZLE3g8' } %}
+
diff --git a/sculpin/source/_pages/pair.html.twig b/sculpin/source/_pages/pair.html.twig
new file mode 100644
index 000000000..71d865329
--- /dev/null
+++ b/sculpin/source/_pages/pair.html.twig
@@ -0,0 +1,81 @@
+---
+layout: page
+title: Pair program with me
+price: 499
+link: https://savvycal.com/opdavies/pair
+---
+
+{% block content %}
+
+{# Pain #}
+
+
Are you stuck adding a new feature or fixing a bug?
+
+
Do you need help starting a new Drupal module or theme?
+
+
Would you like another pair of eyes on your code, providing real-time suggestions and feedback rather than waiting for a code review?
+
+{# Dream #}
+
+{# Fix #}
+
+
Book a pair programming session
+
+
What if you could have a pair programming session with a Lead Developer and Drupal Expert with {numberOfYears} years of professional experience?
+
+
Book a 2-hour pair programming call, and we can work on your code together - adding new functionality, fixing bugs, writing tests for existing code, or something else.
+
+
Price: £{{ page.price|number_format }}
+
+{# 1st call to action #}
+
+{% include 'button.html.twig' with {
+ text: 'Book your session now',
+ url: page.link,
+} %}
+
+{# Social proof #}
+
+{% include 'testimonials.html.twig' %}
+
+{# Overcome objections #}
+
+
100% money-back guarantee!
+
+
If you don't find the session valuable, just let me know, and I'll refund 100% of the cost.
+
+{# Uniqueness #}
+
+
Who am I?
+
+
+
I'm an Acquia-certified Drupal expert with {{ get_years_of_experience() }} years of professional development experience.
+
I'm a former Drupal Association employee who was responsible for improving and maintaining Drupal.org.
+
I'm a Drupal core contributor and maintain numerous Drupal projects, including the Override Node Options module, which is used on over 38,000 websites.
+
I'm a multiple-time DrupalCon speaker who regularly presents talks and workshops at conferences and meetups.
+
+
+{# 2nd CTA #}
+
+{% embed 'callout.html.twig' with {
+ title: 'Ready to book your session?',
+} %}
+ {% block callout_content %}
+ {% include 'button.html.twig' with {
+ text: 'Book your session now for £' ~ page.price|number_format ~ ' ',
+ url: page.link,
+ withArrow: true,
+ } %}
+
+
You can pay and reserve your time slot straight away. There are a limited number of available slots each month. If, after the call, you decide to do a longer engagement with me, the cost of this call will be deducted from that engagement.
I’m only available for a few pair programming sessions per month.
+
Scheduling is first come, first served, so the sooner you book your time slot, the sooner you will have the answers you need to move your project forward.
+
+{% endblock %}
diff --git a/sculpin/source/_pages/phpberks.md b/sculpin/source/_pages/phpberks.md
new file mode 100644
index 000000000..3c7ae78b2
--- /dev/null
+++ b/sculpin/source/_pages/phpberks.md
@@ -0,0 +1,38 @@
+---
+layout: page
+title: PHP Berkshire
+---
+
+{% block head_meta %}
+
+{% endblock %}
+
+{% block content %}
+Thank you for attending my Sculpin talk at PHP Berkshire.
+
+[The slides and a previous recording](/presentations/building-static-websites-sculpin) are on the talk page.
+
+To see an example of a Sculpin project, you can see [the source code for this website](https://github.com/opdavies/oliverdavies.uk), [my Zettlekasten](https://github.com/opdavies/zet.oliverdavies.uk) or [my demo for PHP South West](https://github.com/opdavies/phpsw-sculpin-demo).
+
+If you want to try Sculpin, you can use [the Sculpin Skeleton project I created](https://github.com/opdavies/sculpin-skeleton) as a starting point for new projects.
+
+## Can we work together?
+
+As well as building static websites with Sculpin, I do consulting and advisory work using other PHP projects - namely, Drupal and Symfony.
+
+I offer [unlimited monthly Drupal consulting](/consulting), [1-on-1 consultation calls](/call) and [pair programming sessions](/pair) to get people unstuck, and fixed prices (not estimates) for development work.
+
+## Are you stuck on Drupal 7, 8 or 9?
+
+Drupal 7 will be end-of-life and no longer supported as of the 5th of January, 2025.
+
+Drupal 8 and 9 are already unsupported.
+
+If you have an outdated Drupal website and want to know more about upgrading to Drupal 10, [book your upgrade roadmap](/drupal-upgrade) and I'll help you get there.
+
+## Stay in touch
+
+The best way to stay in touch with me is my [email list](/daily) to which I send daily emails about Drupal and software development.
+
+All emails are sent from my personal email address, so you can press reply and contact me directly.
+{% endblock %}
diff --git a/sculpin/source/_pages/podcast.md b/sculpin/source/_pages/podcast.md
new file mode 100644
index 000000000..766f46eaa
--- /dev/null
+++ b/sculpin/source/_pages/podcast.md
@@ -0,0 +1,32 @@
+---
+layout: page
+title: The Beyond Blocks podcast
+meta:
+ description: A podcast about Drupal, PHP open-source and software development.
+use:
+ - podcast_episodes
+---
+
+{% block content_bottom %}{% endblock %}
+
+{% block content %}
+A podcast about Drupal, PHP, open-source, and related software development topics.
+Guests include people like [Matt Glaman](/podcast/1-retrofit), [Eirik Morland](/podcast/8-eirik-morland-violinist), [Tim Lehnen](/podcast/9-tim-lehnen), [Ryan Szrama](/podcast/13-ryan-szrama-centarro), [Sam Mortenson](/podcast/19-sam-mortenson) and [Jess Archer](/podcast/25-jess-archer-drush-laravel-prompts).
+
+## Episodes {.sr-only}
+
+{% for episode in data.podcast_episodes|reverse %}
+ {% set episodeNumber = episode.url|trim('/')|split('/')|last|split('-')|first %}
+
+
Episode {{ episodeNumber }}: {{ episode.topic }} with {{ episode.guests|join(' and ') }}
Since September 2012, I have given {{ get_presentation_count(data.presentations) }} public talks and workshops at various conferences and meetups, in-person and remotely, on topics including PHP, Drupal, automated testing, Git, CSS, and systems administration.
+
+{% for presentation in data.presentations|sort((a, b) => a.events|last.date|date('U') > b.events|last.date|date('U') ? -1 : 1) %}
+
+
+ {% endif %}
+
+{% endfor %}
diff --git a/sculpin/source/_pages/press.md b/sculpin/source/_pages/press.md
new file mode 100644
index 000000000..b0a2f3a6a
--- /dev/null
+++ b/sculpin/source/_pages/press.md
@@ -0,0 +1,43 @@
+---
+layout: page
+title: Press Info
+---
+
+The following information is provided as a cut-and-paste resource for conference organisers, media professionals, podcast hosts, and other interested parties.
+
+Please feel free to use anything here as-is without checking with me first. If you have additional questions, you can email me directly.
+
+## Short Bio
+
+Oliver is a Software Developer and Drupal expert with {{ get_years_of_experience() }} years experience. He specialises in code quality, automated testing and test-driven development.
+
+## Sample Topics
+
+* Getting started in software development, Drupal, or open-source software.
+* Building your first Drupal website.
+* Drupal module and theme development.
+* Automated testing and test-driven development (TDD).
+* Static analysis in PHP applications.
+* Utility-first styling and Tailwind CSS.
+* Git and different ways to use it (continuous integration, trunk-based development).
+* Contributing to open-source software.
+
+## Social Media and Elsewhere
+
+* [Drupal.org][drupal]
+* [GitHub][]
+* [LinkedIn][]
+* [Twitter][]
+
+## Physical Location
+
+[Caerleon, Wales](https://www.google.co.uk/maps/place/Caerleon,+Newport)
+
+## Photo
+
+
+
+[drupal]: https://www.drupal.org/u/opdavies
+[github]: https://github.com/opdavies
+[linkedin]: https://www.linkedin.com/in/opdavies
+[twitter]: https://twitter.com/opdavies
diff --git a/sculpin/source/_pages/pricing.md b/sculpin/source/_pages/pricing.md
new file mode 100644
index 000000000..6496cbe0f
--- /dev/null
+++ b/sculpin/source/_pages/pricing.md
@@ -0,0 +1,114 @@
+---
+layout: page
+title: Products and Services
+products:
+ -
+ title: Drupal development subscription
+ description: |
+ In less time than it takes to post on a job board, and for a fraction of the cost, get unlimited access to a certified Drupal development expert, core contributor and multiple-time DrupalCon speaker for a fixed monthly fee. No surprises. Cancel anytime.
+ perMonth: true
+ price: 5000
+ isFrom: true
+ link:
+ text: Register now
+ url: /subscription
+ -
+ title: Drupal upgrade roadmap
+ description: |
+ Are you stuck on an already or soon-to-be unsupported version of Drupal? Get a personalised roadmap of your Drupal website, including details and actionable steps to upgrade it.
+ price: 5000
+ link:
+ text: Book your roadmap now
+ url: /drupal-upgrade
+ -
+ title: Diagnosis
+ description: |
+ An in-depth investigation into a single issue where I'll provide a report with my findings and advice on the next steps. Once you've purchased, you can book a Zoom call with me to discuss what you want me to investigate.
+ price: 2500
+ link:
+ text: Book now
+ url: https://buy.stripe.com/00gbJs84G2jg8Vy9AJ
+ -
+ title: Private talk or workshop
+ description: |
+ If you found one of my public speaking presentations or workshops useful, I'm available for private speaking engagements on a variety of topics to help your team succeed.
+ price: 2000
+ isFrom: true
+ link:
+ text: Schedule a talk
+ url: https://buy.stripe.com/eVa4h0bgSaPM6NqcMU
+ -
+ title: 1-on-1 consulting call
+ description: |
+ Book a 1-on-1 video call, and I can help you by answering questions about software development, architecture and automation, helping you write your first automated test, or reviewing some of your code and giving advice and suggestions.
+ price: 350
+ link:
+ text: Book your call now
+ url: /call
+ -
+ title: Pair program with me
+ description: |
+ Would you like another pair of eyes on your code, providing real-time suggestions and feedback rather than waiting for a code review? Book a 2-hour pair programming call and we can work on your code together.
+ price: 499
+ link:
+ text: Book your session now
+ url: /pair
+ -
+ title: Introduction to Automated Testing in Drupal - 10-day email course
+ description: |
+ Register for my free email course on automated testing in Drupal.
+ link:
+ text: Register now
+ url: /atdc
+ -
+ title: Drupal module template
+ description: |
+ If you're creating a new Drupal module, try starting with my free module template for Drupal 9 and 10.
+ link:
+ text: Download
+ url: https://github.com/opdavies/drupal-module-template
+---
+
+{% block content %}
+
+{% for product in page.products %}
+
+
+
+
+
diff --git a/sculpin/source/_pages/sfs.md b/sculpin/source/_pages/sfs.md
new file mode 100644
index 000000000..b01d87456
--- /dev/null
+++ b/sculpin/source/_pages/sfs.md
@@ -0,0 +1,10 @@
+---
+layout: page
+title: Sculpin from Scratch
+---
+
+Coming soon: a daily email course on building static websites with Sculpin.
+
+If you're interested, why not [watch my Sculpin talk](/presentations/building-static-websites-sculpin) in the meantime or check out [the source code for this website](https://github.com/opdavies/oliverdavies.uk)?
+
+If you want me to let you know once the course is ready, send me an email and I'll add you to the list.
diff --git a/sculpin/source/_pages/sitemap.xml.twig b/sculpin/source/_pages/sitemap.xml.twig
new file mode 100644
index 000000000..7d493cd54
--- /dev/null
+++ b/sculpin/source/_pages/sitemap.xml.twig
@@ -0,0 +1,22 @@
+---
+permalink: /sitemap.xml
+use:
+ - pages
+ - podcast_episodes
+ - posts
+ - presentations
+default_priorities:
+ posts: '0.2'
+---
+
+
+
+ {% for content_type, content in data %}
+ {% for item in content %}
+
+ {{ site.url }}{{ item.url|trim('/', 'right') }}
+ {{ page.default_priorities[content_type]|default('0.5') }}
+
+ {% endfor %}
+ {% endfor %}
+
diff --git a/sculpin/source/_pages/speaker.md b/sculpin/source/_pages/speaker.md
new file mode 100644
index 000000000..309ac4b3a
--- /dev/null
+++ b/sculpin/source/_pages/speaker.md
@@ -0,0 +1,32 @@
+---
+layout: page
+title: Speaker Information
+---
+
+## Bio
+
+Oliver is a Software Developer and Drupal Expert with {{ get_years_of_experience() }} years of experience. As well as consulting on large Drupal projects, Oliver helps Drupal Developers learn automated testing and test-driven development via a free email course and paid coaching and workshops. He regularly contributes to open-source software projects, including Drupal core.
+
+## Photos
+
+-
+
+## Some events that I've spoken at
+
+- BlueConf 2019 (Cardiff, UK)
+- DrupalCamp Brighton 2015
+- DrupalCamp Bristol 2016
+- DrupalCamp Dublin 2017
+- DrupalCamp London (2014, 2015, 2016, 2017, 2019, 2020)
+- DrupalCamp North 2015 (Sunderland, UK)
+- DrupalCon Amsterdam 2019
+- DrupalCon Europe 2020 (Online)
+- DrupalCon Lille 2023
+- Nomad PHP
+- Norfolk Developers' Conference (nor(DEV):con) 2023
+- PHP North West 2017 (Manchester, UK - 10 year anniversary)
+- PHP South Coast 2016 (Portsmouth, UK)
+- PHP UK Conference 2018 (London, UK)
+- WordCamp Bristol 2019
+
+I also [gave a number of talks remotely](/blog/speaking-remotely-during-lockdown) for various user groups and conferences during COVID-19.
diff --git a/sculpin/source/_pages/sponsor.md b/sculpin/source/_pages/sponsor.md
new file mode 100644
index 000000000..58f5d2b8d
--- /dev/null
+++ b/sculpin/source/_pages/sponsor.md
@@ -0,0 +1,31 @@
+---
+layout: page
+title: Sponsor me to work on Open Source Software
+---
+
+I enjoy working with and on open source software.
+
+I've been using PHP since 2007 and Drupal since 2008 and am an enthusiastic open source contributor.
+
+Drupal core, Drupal.org, LocalGov Drupal and Drupal Commerce are some of the projects I've contributed to.
+
+I maintain the popular Override Node Options module which is used on around 40,000 active Drupal websites and created a starterkit Drupal theme for Tailwind CSS.
+
+There are lots of other examples on my [Drupal.org][0] and [GitHub][1] profiles.
+
+I've found that companies want to contribute more, but are too busy.
+
+Most projects have a custom module or library that was going to be contributed back, but it just hasn't happened yet.
+
+## Why not sponsor a contributor?
+
+If you don't have time to contribute, why not sponsor me to do it for you?
+
+Whether it's contributing that module or library, adding a missing feature or fixing a bug in something you use, or you just want to sponsor me to work on open source, I'd love to hear from you.
+
+We can discuss how much time you want to sponsor and what you want me to focus on and I'll get to work.
+
+You'll be mentioned on any relevant blog posts, live streams, issues and pull or merge requests, so everyone will know that you sponsored me and you'll get the kudos.
+
+[0]: {{site.drupalorg.url}}
+[1]: {{site.github.url}}
diff --git a/sculpin/source/_pages/testimonials.md b/sculpin/source/_pages/testimonials.md
new file mode 100644
index 000000000..32a72c8b6
--- /dev/null
+++ b/sculpin/source/_pages/testimonials.md
@@ -0,0 +1,9 @@
+---
+layout: page
+title: Testimonials
+---
+
+{% include 'testimonials' with {
+ limit: 0,
+ title: 'All Testimonials',
+} %}
diff --git a/sculpin/source/_pages/things-about-php.md b/sculpin/source/_pages/things-about-php.md
new file mode 100644
index 000000000..6f3cb0a90
--- /dev/null
+++ b/sculpin/source/_pages/things-about-php.md
@@ -0,0 +1,64 @@
+---
+layout: page
+title: Things you should know about PHP
+---
+
+Thanks for attending my [Things you should know about PHP](/talks/things-you-should-know-about-php) talk.
+
+I hope that you learned some things about PHP, its ecosystem, and its communities, and if you haven't tried using PHP yet, I'd encourage you to do so.
+
+Here are links to the resources that I mention in the talk, plus a couple of extras.
+
+## Resources
+
+- [The PHP Foundation](https://thephp.foundation) - non-profit to support, advance, and develop the PHP language
+- [PHP-FIG](https://www.php-fig.org) - PHP Framework Interop Group
+- [Composer](https://getcomposer.org) - dependency manager
+- [Drupal](https://www.drupal.org) - content management system
+- [Jigsaw](https://jigsaw.tighten.co) - static site generator
+- [Laravel](https://laravel.com) - framework
+- [Nomad PHP](https://nomadphp.com) - online user group
+- [PHP official images on Docker Hub](https://hub.docker.com/_/php)
+- [PHPStan](https://phpstan.org) - static analysis tool
+- [PHPUnit](https://phpunit.de) - testing framework
+- [Pest](https://pestphp.com) - testing framework
+- [Psalm](https://psalm.dev) - static analysis tool
+- [Sculpin](khttps://sculpin.io) - static site generator
+- [WordPress](https://wordpress.org) - content management system
+- [php.net](https://www.php.net) - online documentation
+- [php[architect]](https://www.phparch.com) - online magazine
+
+## Books
+
+- [Laravel: Up & Running](https://www.oreilly.com/library/view/laravel-up/9781492041207)
+- [Symfony: The Fast Track](https://symfony.com/book)
+
+## Videos
+
+- [Codecourse](https://codecourse.com)
+- [How to Code Well](https://www.howtocodewell.net)
+- [Laracasts](https://laracasts.com)
+- [SymfonyCasts](https://symfonycasts.com)
+
+## Podcasts
+
+- [How to Code Well podcast](https://howtocodewell.fm)
+- [PHPUgly](https://www.phpugly.com)
+- [Talking Drupal](https://talkingdrupal.com)
+- [The Laravel Podcast](https://laravelpodcast.com)
+- [The PHP Roundtable](https://phproundtable.com)
+- [Voices of the elePHPant](https://voicesoftheelephpant.com)
+
+## Can I help?
+
+Do you want to introduce PHP to your company or team, or add one of these tools to your existing PHP application?
+
+I offer consulting calls and services to reduce your onboarding time and get you up and running quicker and easier.
+
+
+ {% include 'button.html.twig' with {
+ text: 'Book your call ',
+ url: '/call',
+ withArrow: true,
+ } %}
+
diff --git a/sculpin/source/_pages/welcome.md b/sculpin/source/_pages/welcome.md
new file mode 100644
index 000000000..6a4ece758
--- /dev/null
+++ b/sculpin/source/_pages/welcome.md
@@ -0,0 +1,26 @@
+---
+layout: page
+title: Welcome!
+meta:
+ title: Welcome to my Daily Email list | %site.name%
+---
+
+{% block head_meta %}
+
+{% endblock %}
+
+{% block content %}
+Hi. Thank you for subscribing to my Daily Email list!
+
+You'll soon start to get emails from me directly in your inbox. Every day.
+
+The first might be later today!
+
+Every email is sent from my personal email address, so if you have a question about any email, press reply, send it to me, and I'll personally respond.
+
+Also, remember to check your inbox and confirm your email address to ensure you get my emails.
+
+In the meantime, why not [go to the archive](/archive) and read some of my previous dailies?
+
+Found this page by accident? [Consider joining the list](/daily).
+{% endblock %}
diff --git a/sculpin/source/_podcast_episodes/1-retrofit.md b/sculpin/source/_podcast_episodes/1-retrofit.md
new file mode 100644
index 000000000..4f949f69e
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/1-retrofit.md
@@ -0,0 +1,37 @@
+---
+date: 2023-11-10
+topic: Retrofit
+guests:
+ - Matt Glaman
+links:
+ - - Retrofit
+ - https://retrofit-drupal.com
+ - - Retrofit on GitHub
+ - https://github.com/retrofit-drupal/retrofit
+ - - Running legacy Drupal 7 code on your Drupal 10 site
+ - https://mglaman.dev/blog/retrofit-running-legacy-drupal-7-code-your-drupal-10-site
+ - - PHPStan
+ - https://phpstan.org
+ - - phpstan-drupal
+ - https://github.com/mglaman/phpstan-drupal
+ - - Centarro
+ - https://www.centarro.io
+ - - Drupal Commerce
+ - https://drupalcommerce.org
+ - - Matt on GitHub
+ - https://github.com/mglaman
+ - - Matt on Drupal.org
+ - https://www.drupal.org/u/mglaman
+ - - Matt on YouTube
+ - https://www.youtube.com/@nmdmatt
+ - - Matt on Twitch
+ - https://www.twitch.tv/mglaman
+transistor:
+ id: 7d728873
+episode_filename: 1-retrofit.mp3
+file_size: 20523767
+duration: 0:43:52
+use: [podcast_episodes]
+---
+
+In this episode, Oliver is joined by Matt Glaman to discuss Retrofit. A tool that makes it easier to upgrade Drupal websites by allowing legacy Drupal code to run on any version of Drupal.
diff --git a/sculpin/source/_podcast_episodes/10-ryan-weaver-symfonycasts.md b/sculpin/source/_podcast_episodes/10-ryan-weaver-symfonycasts.md
new file mode 100644
index 000000000..7b790cbbe
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/10-ryan-weaver-symfonycasts.md
@@ -0,0 +1,37 @@
+---
+date: 2024-02-10
+topic: Twig, Symfony and SymfonyCasts
+guests:
+ - Ryan Weaver
+transistor:
+ id: f276e486
+links:
+ - - Symfony
+ - https://symfony.com
+ - - SymfonyCasts
+ - https://symfonycasts.com
+ - - Symfony UX
+ - https://ux.symfony.com
+ - - Twig
+ - https://twig.symfony.com
+ - - Twig Components
+ - https://ux.symfony.com/twig-component
+ - - Composer
+ - https://getcomposer.org
+ - - 'Symfony: The Fast Track'
+ - https://symfony.com/book
+ - - SymfonyCasts Blog
+ - https://symfonycasts.com/blog
+ - - Ryan on Twitter
+ - https://twitter.com/weaverryan
+ - - Ryan on GitHub
+ - https://github.com/weaverryan
+ - - Ryan on YouTube
+ - https://www.youtube.com/@weaverryan
+episode_filename: 10-symfonycasts.mp3
+file_size: 26892969
+duration: 0:53:59
+use: [podcast_episodes]
+---
+
+This week, Oliver is joined by Ryan Weaver - Symfony Developer, Symfony core team member and Writer for SymfonyCasts to discuss the recent developments in Twig templates, SymfonyCasts, release cycles, and similarities between the Drupal and Symfony projects and communities.
diff --git a/sculpin/source/_podcast_episodes/11-mark-conroy.md b/sculpin/source/_podcast_episodes/11-mark-conroy.md
new file mode 100644
index 000000000..4cae1823d
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/11-mark-conroy.md
@@ -0,0 +1,35 @@
+---
+date: 2024-02-16
+topic: Build something useful in one day
+guests:
+ - Mark Conroy
+transistor:
+ id: c2c3f7b3
+links:
+ - - mark.ie
+ - https://mark.ie
+ - - Annertech
+ - https://www.annertech.com
+ - - Running Plan Generator
+ - https://runningplangenerator.com
+ - - Web Component Design System
+ - https://web-components-design-system.mark.ie
+talking_points:
+ - Building better websites faster, including Drupal distributions, such as LocalGov.
+ - What Drupal gives you out of the box and using the right tool for the job.
+ - Drupal migrations and migrating Drupal configuration from a spreadsheet.
+ - Saving time and improving efficiency by standardising base builds with Docksal and Composer.
+ - How to build projects, like the Running Plan Generator, in a day and training for 10Ks and marathons.
+ - Prioritising tasks.
+ - Annertech's development workflow and standardisation by developing on remote servers.
+ - Why you may not need preprocessors and complicated front-end build tools, and reducing complexity using vanilla CSS, JavaScript and web components.
+ - Lowering the barrier to entry to contribute to open-source projects.
+ - Building a design system/component library with web components.
+ - The Content Access by Path module that was developed for Essex County Council.
+episode_filename: 11-something-useful.mp3
+file_size: 30369780
+duration: 1:02:28
+use: [podcast_episodes]
+---
+
+This week, Oliver is joined by Mark Conroy - Director of Development at Annertech - to discuss all things front-end and how to build something useful in one day.
diff --git a/sculpin/source/_podcast_episodes/12-nick-janetakis-docker.md b/sculpin/source/_podcast_episodes/12-nick-janetakis-docker.md
new file mode 100644
index 000000000..f2361913e
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/12-nick-janetakis-docker.md
@@ -0,0 +1,22 @@
+---
+date: 2024-02-26
+topic: Docker
+guests:
+ - Nick Janetakis
+transistor:
+ id: a0498296
+links:
+ - - Nick's website and blog
+ - https://nickjanetakis.com
+ - - Nick on YouTube
+ - https://www.youtube.com/@NickJanetakis
+ - - Nick's Docker example repositories on GitHub
+ - https://github.com/nickjj?tab=repositories&q=-example
+talking_points: []
+episode_filename: 12-docker.mp3
+file_size: 28021353
+duration: 0:51:48
+use: [podcast_episodes]
+---
+
+This week, Oliver is joined by Nick Janetakis - a Software Developer, Docker Captain and Teacher who focuses on building and deploying web apps - to discuss Docker, content creation and more.
diff --git a/sculpin/source/_podcast_episodes/13-ryan-szrama-centarro.md b/sculpin/source/_podcast_episodes/13-ryan-szrama-centarro.md
new file mode 100644
index 000000000..b8f8f2f92
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/13-ryan-szrama-centarro.md
@@ -0,0 +1,63 @@
+---
+date: 2024-03-04
+topic: Centarro and Drupal Commerce
+guests:
+ - Ryan Szrama
+transistor:
+ id: ab539bef
+links:
+ - - Centarro
+ - https://www.centarro.io
+ - - Ryan on Twitter
+ - https://twitter.com/ryanszrama
+ - - Ryan on Drupal Answers
+ - https://drupal.stackexchange.com/users/866/ryan-szrama
+talking_points:
+ - eCommerce, Ubercart and Drupal Commerce.
+ - How Commerce Guys started.
+ - How Ryan started in the eCommerce space.
+ - The origins of Drupal Commerce and Commerce Guys.
+ - Commerce Kickstart.
+quotes:
+ - The best code isn't the most novel or most compact, it's the most easily readable and easily extendable (RS).
+ - My only resource for learning PHP was php.net (RS)
+ - I'm a self-taught Developer or community-taught, I suppose (OD).
+ - Porting osCommerce into Drupal, which became Ubercart (RS).
+ - Do we do this in Ubercart and Drupal 6 that we know works, or this new thing that was Drupal Commerce in Drupal 7 (OD).
+ - It was built in Ubercart and was in production until quite recently, considering we're in 2024 (OD).
+ - Once you get thoroughly entrenched in a platform, the cost to re-platform outweighs the cost of maintaining it in-house (RS).
+ - If there's 10,000 of anybody, they're going to want to pay for support (RS).
+ - How do we convince them to invest in an upgrade that doesn't see them jettison Drupal entirely? (RS)
+ - One thing I like about Drupal Commerce is its flexibility. (OD)
+ - It doesn't need to be a t-shirt or book shop. You can do some outside the box things with it. (OD)
+ - Being a native extension of Drupal has a lot more advantages and pros than cons. (RS)
+ - Ubercart was more "batteries included" and "this is what it does", whereas Drupal Commerce is more flexible and although it requires a bit more setup to begin with, you can plug it together the way you want to. (OD)
+ - Our vision was to go the next step of getting off the Drupal island. (RS)
+ - The Commerce Addressing library is now over 17,000,000 downloads. (OD)
+ - Composer allowed us to separate our projects and separate our concerns. (RS)
+ - Do you know when you should not use Views and when you decouple that component and use JavaScript and the REST API? (RS)
+ - If we can reduce the number of times introduce those kinds of problems, that's how I would certify somebody. (RS)
+chapters:
+ - 00:00 Introduction and DrupalCon Lille.
+ - 6:00 How did you get started in software development?
+ - 17:36 Getting into eCommerce.
+ - 19:14 The origins of Commerce Guys and Drupal Commerce.
+ - 20:26 How Oliver started with eCommerce projects.
+ - 22:68 Ubercart for Drupal 7?
+ - 30:04 Commerce Kickstart.
+ - 34:08 Commerce flexibility.
+ - 35:52 More commerce Kickstart.
+ - 38:62 Ubercart and Commerce differences.
+ - 40:34 The Commerce Kickstart project template.
+ - 42:50 Building Drupal Commerce 2.
+ - 46:38 Releasing new things.
+ - 51:58 Certified Centarro partners?
+ - 57:38 Centarro roadmap.
+ - 62:06 Wrapping up.
+episode_filename: 13-drupal-commerce.mp3
+file_size: 30353061
+duration: 1:03:36
+use: [podcast_episodes]
+---
+
+This week, Oliver is joined by Ryan Szrama, CEO of Centarro, to discuss Drupal Commerce, Commerce Kickstart, Mario Kart and the dreaded `cache_form` table.
diff --git a/sculpin/source/_podcast_episodes/14-yuri-gerasymov-diffy.md b/sculpin/source/_podcast_episodes/14-yuri-gerasymov-diffy.md
new file mode 100644
index 000000000..1ab9c3551
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/14-yuri-gerasymov-diffy.md
@@ -0,0 +1,45 @@
+---
+date: 2024-03-11
+topic: Diffy and Visual Regression Testing
+guests:
+ - Yuri Gerasymov
+transistor:
+ id: 5940d69f
+links:
+ - - About Yuri
+ - https://ygerasimov.com/about-me
+ - - Diffy
+ - https://diffy.website
+ - - Yuri on Drupal.org
+ - https://www.drupal.org/u/ygerasimov
+talking_points:
+ - What is visual regression testing?
+ - How do you deal with false positives?
+ - Different use cases for visual regression testing.
+ - Automatic updates.
+ - Scheduling content.
+ - Visual regression testing in CI.
+ - Diffy in WordPress.
+ - What's a baseline?
+ - Initial setup and onboarding feedback.
+ - Testing dark mode?
+ - Component testing with Storybook and Fractal?
+ - Testing local environments.
+ - Testing as authenticated users.
+ - The roadmap for Diffy.
+quotes:
+ - We help development teams to have less visual bugs in their website. We take screenshots of the pages and compare them so you can see what changed and how. (YG)
+ - We built tools for you to mock the content. You provide selectors for the elements with the content of the article and we'll replace it with lorem ipsum text so it will be exactly the same across multiple environments. (YG)
+ - I can still write an assertion to check the text is on the page or not, but it won't confirm it's in the correct place. (OD)
+ - Having a tool checking for changes on a regular basis instead of only after a deployment would be very useful. (OD)
+ - So, you could have a tool like Violinst automatically creating pull requests and Diffy checking those PRs, so the two could work together? (OD)
+ - With visual testing, it's very easy to get started. (YG)
+ - Visual testing is great for showing your client your work. (YG)
+chapters: []
+episode_filename: 14-diffy.mp3
+file_size: 22892297
+duration: 0:46:25
+use: [podcast_episodes]
+---
+
+This week, Oliver discusses visual regression testing and Diffy with Yuri Gerasymov.
diff --git a/sculpin/source/_podcast_episodes/15-rob-allen-domain-driven-design.md b/sculpin/source/_podcast_episodes/15-rob-allen-domain-driven-design.md
new file mode 100644
index 000000000..45c4c7a19
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/15-rob-allen-domain-driven-design.md
@@ -0,0 +1,51 @@
+---
+date: 2024-03-20
+topic: Domain-Driven Design
+guests:
+ - Rob Allen
+transistor:
+ id: 0de0b405
+links:
+ - - Rob's blog
+ - https://akrabat.com
+ - - Nineteen Feet
+ - https://19ft.com
+talking_points:
+ - Rebuild vs. rewrite.
+ - Writing good commit messages.
+ - Are code comments useful?
+ - Technical Design Documents and ADRs.
+ - Ubiquitous language and Domain-Driven Design.
+ - PHP UK, PHP South West, conferences and user groups.
+ - DDD in Drupal?
+ - DRY and YAGNI.
+ - When to refactor?
+quotes:
+ - I quite like legacy projects because i think they've already proved their worth in the marketplace. (RA)
+ - I general, I think that rewriting the wrong approach nearly every single time. (RA)
+ - Things are so impermanent. The only things you can trust are in the source code and what's in the revision history of that source code.
+ - We have the "what" but we don't have the "why". (OD)
+ - As you do this for longer, I think you start picking up on what you wish you'd written in the past. (RA)
+ - I think nearly everything related to software development that really matters is invariably about communication. (RA)
+ - Nearly everything that results in good quality software is because good communication works. (RA)
+ - Ubiquitous language is using the same language the specialists are using. (RA)
+ - An awful lot about DDD is trying to get the communication right. (RA)
+ - If you pretend it's not happening, it doesn't mean it's not happening - just that you're ignoring the problem. (RA)
+ - It's cheaper to fix things earlier in the process. (RA)
+ - User groups are such a good community resource. We get to try thing (talks) out. (RA)
+ - A conference gives you a focused block of time to learn something. (RA)
+ - I think the tenets of DDD can are important regardless (of the size of the project). (RA)
+ - We can refactor our way out, but now the overall time is longer. (RA)
+ - Time spent upfront is tangible effect on the time spent later. (RA)
+ - The biggest one [benefit of DDD] is that you end up with a project that's fit for purpose.
+ - You're way more likely to deliver a project that does what the customer needs if you have listened and understood what they said. (RA)
+ - It's in the customer's best interest for you to get it right the first time. (OD)
+ - You have to be proactive. It doesn't happen by default. (RA)
+chapters: []
+episode_filename: 15-ddd.mp3
+file_size: 24444165
+duration: 0:56:08
+use: [podcast_episodes]
+---
+
+This week, Oliver discusses Domain-Driven Design with PHP UK speaker, Rob Allen.
diff --git a/sculpin/source/_podcast_episodes/16-simon-graham-weight-loss.md b/sculpin/source/_podcast_episodes/16-simon-graham-weight-loss.md
new file mode 100644
index 000000000..aaf898714
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/16-simon-graham-weight-loss.md
@@ -0,0 +1,20 @@
+---
+date: 2024-05-01
+topic: Health, fitness and weight loss
+guests:
+ - Simon Graham
+transistor:
+ id: da3e6309
+links:
+ - - simongpt.co.uk
+ - https://www.simongpt.co.uk
+talking_points: []
+quotes: []
+chapters: []
+episode_filename: 16-health-fitness-weight.mp3
+file_size: 22258088
+duration: 0:42:06
+use: [podcast_episodes]
+---
+
+This week, Oliver discusses health, fitness and weight loss with Personal Trainer and Weight Loss Coach, Simon Graham.
diff --git a/sculpin/source/_podcast_episodes/17-jochen-lillich.md b/sculpin/source/_podcast_episodes/17-jochen-lillich.md
new file mode 100644
index 000000000..310c23bb2
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/17-jochen-lillich.md
@@ -0,0 +1,28 @@
+---
+date: 2024-07-18
+topic: Automation, Linux training and mechanical keyboards
+guests:
+ - Jochen Lillich
+transistor:
+ id: d8858593
+links:
+ - - Jochen's personal website
+ - https://www.geewiz.dev
+ - - Jochen on Twitch
+ - https://www.twitch.tv/monospacementor
+ - - monospacementor.com
+ - https://monospacementor.com
+ - - freistil.it
+ - https://www.freistil.it
+talking_points: []
+quotes: []
+chapters: []
+episode_filename: 17-automation-linux.mp3
+file_size: 26449557
+duration: 0:54:23
+use: [podcast_episodes]
+---
+
+This week, Oliver is joined by the Monospace Mentor - Jochen Lillich.
+
+Jochen and I discuss content creation, live streaming on Twitch and YouTube, infrastructure automation, Linux, mentoring and, of course, mechanical keyboards.
diff --git a/sculpin/source/_podcast_episodes/18-andy-hoang.md b/sculpin/source/_podcast_episodes/18-andy-hoang.md
new file mode 100644
index 000000000..fd7867db6
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/18-andy-hoang.md
@@ -0,0 +1,24 @@
+---
+date: 2024-08-09
+topic: LEGO, robotics and open-source software
+guests:
+ - Andy Hoang
+transistor:
+ id: 82fb51bf
+links:
+ - - Beyond Blocks website
+ - https://www.beyondblocks.co.uk
+ - - Open Web Alliance
+ - https://www.drupal.org/association/blog/drupal-association-co-founds-the-open-website-alliance
+ - - Open Source Initiative
+ - https://opensource.org
+talking_points: []
+quotes: []
+chapters: []
+episode_filename: 18-lego-robotics.mp3
+file_size: 25854381
+duration: 0:51:06
+use: [podcast_episodes]
+---
+
+This week, Oliver is joined by Andy Hoang. They discuss teaching children to code with LEGO and robotics, and how to leverage open-source software.
diff --git a/sculpin/source/_podcast_episodes/19-sam-mortenson.md b/sculpin/source/_podcast_episodes/19-sam-mortenson.md
new file mode 100644
index 000000000..8bdcd5c94
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/19-sam-mortenson.md
@@ -0,0 +1,30 @@
+---
+date: 2024-08-27
+topic: Single File Components and Static Drupal Websites
+guests:
+ - Sam Mortenson
+transistor:
+ id: 824e9f2b
+links:
+ - - Sam Mortenson on Drupal.org
+ - https://www.drupal.org/u/samuelmortenson
+ - - Sam Mortenson on Twitter/X
+ - https://x.com/mortensonsam
+ - - mortenson.coffee
+ - https://mortenson.coffee
+ - - The Tome project on Drupal.org
+ - https://www.drupal.org/project/tome
+ - - The Bookish distribution
+ - https://github.com/drupal-tome/bookish
+ - - AwaySync
+ - https://awaysync.com
+talking_points: []
+quotes: []
+chapters: []
+episode_filename: 19-sfc-static-drupal.mp3
+file_size: 15153981
+duration: 0:30:58
+use: [podcast_episodes]
+---
+
+In this episode, Oliver speaks with Sam Mortenson (samuelmortenson) about single file components in Drupal and Tome (a Drupal-based static website generator).
diff --git a/sculpin/source/_podcast_episodes/2-alternate-realities.md b/sculpin/source/_podcast_episodes/2-alternate-realities.md
new file mode 100644
index 000000000..e9432f934
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/2-alternate-realities.md
@@ -0,0 +1,29 @@
+---
+date: 2023-11-17
+topic: Drupal's Alternate Realities
+guests:
+ - Panagiotis Moutsopoulos
+links:
+ - - DrupalCon Lille
+ - https://events.drupal.org/lille2023
+ - - Panagiotis' BoF session
+ - https://events.drupal.org/lille2023/session/drupals-alternate-realities
+ - - Panagiotis on Drupal.org (vensires)
+ - https://www.drupal.org/u/vensires
+ - - E-Sepia
+ - https://www.e-sepia.gr
+ - - Panagiotis on LinkedIn
+ - https://www.linkedin.com/in/panagiotis-moutsopoulos/
+ - - https://drupal.org.gr
+ - https://drupal.org.gr
+ - - Frontend United
+ - https://www.frontendunited.com
+transistor:
+ id: 5f7e3397
+episode_filename: 2-alternate-realities.mp3
+file_size: 19346455
+duration: 0:43:51
+use: [podcast_episodes]
+---
+
+In this episode, Oliver is joined by Panagiotis Moutsopoulos to discuss about the whole first-time DrupalCon experience of Lille (France) and more specifically, his session Drupal’s Alternate Realities A BoF session presenting some history but mainly the different ways to tackle a problem in Drupal using different methodologies.
diff --git a/sculpin/source/_podcast_episodes/20-george-gordon.md b/sculpin/source/_podcast_episodes/20-george-gordon.md
new file mode 100644
index 000000000..ac2351b5d
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/20-george-gordon.md
@@ -0,0 +1,24 @@
+---
+date: 2024-09-06
+topic: Bootcamps, Hackathons, Meetups and Drupal
+guests:
+ - George Gordon
+transistor:
+ id: ec999352
+links:
+ - - School of Code
+ - https://schoolofcode.co.uk
+ - - Versantus
+ - https://www.versantus.co.uk
+ - - George on LinkedIn
+ - https://www.linkedin.com/in/george-gordon-70aa3a82
+talking_points: []
+quotes: []
+chapters: []
+episode_filename: 20-bootcamps.mp3
+file_size: 23938605
+duration: 0:48:12
+use: [podcast_episodes]
+---
+
+In this episode, Oliver speaks with George Gordon - a graduate from the School of Code who was recently employed in their first Junior Developer role - about their move into tech and software development, their experiences at hack days and meetups, and their introduction to Drupal.
diff --git a/sculpin/source/_podcast_episodes/21-eirik-morland-violinist-2.md b/sculpin/source/_podcast_episodes/21-eirik-morland-violinist-2.md
new file mode 100644
index 000000000..2ec8a33fb
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/21-eirik-morland-violinist-2.md
@@ -0,0 +1,34 @@
+---
+date: 2024-09-13
+topic: Violinist, Render Arrays and Feature Flags
+guests:
+ - Eirik Morland
+transistor:
+ id: a4f651a7
+links:
+ - - violinist.io
+ - https://violinist.io
+ - - Violinist on Drupal.org
+ - https://www.drupal.org/violinist
+ - - Eirik on Drupal.org
+ - https://www.drupal.org/u/eiriksm
+ - - The violinist.io Roadmap
+ - https://violinist.io/roadmap
+ - - The Violinist Teams module
+ - https://www.drupal.org/project/violinist_teams
+ - - Behat
+ - https://behat.org
+ - - Feature Flags
+ - https://martinfowler.com/articles/feature-toggles.html
+ - - Deployments vs releases
+ - /daily/2023/06/21/deployments-or-releases
+talking_points: []
+quotes: []
+chapters: []
+episode_filename: 21-violinist.mp3
+file_size: 30666885
+duration: 1:01:55
+use: [podcast_episodes]
+---
+
+Oliver is joined again by Eirik Morland (the first returning guest) to discuss recent improvements and enhancements to Violinist, such as Team support (a.k.a. multi-user subscriptions).
diff --git a/sculpin/source/_podcast_episodes/22-dave-liddament.md b/sculpin/source/_podcast_episodes/22-dave-liddament.md
new file mode 100644
index 000000000..6419f06de
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/22-dave-liddament.md
@@ -0,0 +1,34 @@
+---
+date: 2024-09-30
+topic: Static Analysis
+guests:
+ - Dave Liddament
+transistor:
+ id: 9b2a9587
+links:
+ - - Dave's website
+ - https://www.daveliddament.co.uk
+ - - PHPStan
+ - https://phpstan.org
+ - - php-language-extensions
+ - https://github.com/DaveLiddament/php-language-extensions
+ - - Static Analysis Results Baseliner
+ - https://github.com/DaveLiddament/sarb
+ - - Types in Laravel by Nuno Maduro
+ - https://www.youtube.com/watch?v=jObcE58UCB8
+ - - PHP South West
+ - https://phpsw.uk
+ - - Dave on X
+ - https://x.com/DaveLiddament
+ - - Dave on Mastodon
+ - https://mastodon.social/@daveliddament@phpc.social
+talking_points: []
+quotes: []
+chapters: []
+episode_filename: 22-static-analysis.mp3
+file_size: 25543869
+duration: 0:58:55
+use: [podcast_episodes]
+---
+
+In this episode, Oliver is joined by Dave Liddament - Director at Lamp Bristol and Organiser of PHP South West - to discuss PHPStan and static analysis.
diff --git a/sculpin/source/_podcast_episodes/23-jurgen-haas-eca.md b/sculpin/source/_podcast_episodes/23-jurgen-haas-eca.md
new file mode 100644
index 000000000..3b8d24182
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/23-jurgen-haas-eca.md
@@ -0,0 +1,26 @@
+---
+date: 2024-10-09
+topic: Drupal Events, Conditions and Actions
+guests:
+ - Jürgen Haas
+transistor:
+ id: f68650c9
+links:
+ - - Jürgen on Drupal.org
+ - https://www.drupal.org/u/jurgenhaas
+ - - ECA module
+ - https://www.drupal.org/project/eca
+ - - ECA Guide
+ - https://ecaguide.org
+ - - NWDUG talk and demo
+ - https://www.youtube.com/watch?v=b512Lk1PSSk
+talking_points: []
+quotes: []
+chapters: []
+episode_filename: 23-eca.mp3
+file_size: 21181821
+duration: 0:51:53
+use: [podcast_episodes]
+---
+
+In this episode, Oliver is joined by Jürgen Haas to discuss the Drupal Event, Condition, Action (ECA) module.
diff --git a/sculpin/source/_podcast_episodes/24-chris-ballard-generative-ai.md b/sculpin/source/_podcast_episodes/24-chris-ballard-generative-ai.md
new file mode 100644
index 000000000..b0025e69d
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/24-chris-ballard-generative-ai.md
@@ -0,0 +1,28 @@
+---
+date: 2024-10-16
+topic: Generative AI in PHP
+guests:
+ - Chris Ballard
+transistor:
+ id: ab87d408
+links:
+ - - Chris on LinkedIn
+ - https://www.linkedin.com/in/chris-ballard-3b573b3
+ - - Scoot Software
+ - https://www.scootsoftware.com
+ - - Byte to Eat (Chris' example application)
+ - https://github.com/chris-ballard/byte-to-eat
+ - - The Drupal AI module
+ - https://www.drupal.org/project/ai
+ - - AI content migration demo from DrupalCon
+ - https://youtu.be/nhPiL4g972A?si=pAQ9sPIuQqdDf3y0&t=2687
+talking_points: []
+quotes: []
+chapters: []
+episode_filename: 24-generative-ai.mp3
+file_size: 29900397
+duration: 00:58:33
+use: [podcast_episodes]
+---
+
+In this episode, Oliver is joined by Chris Ballard to discuss his recent talk at PHP South West about using generative AI in PHP, speaking at meetups and live coding.
diff --git a/sculpin/source/_podcast_episodes/25-jess-archer-drush-laravel-prompts.md b/sculpin/source/_podcast_episodes/25-jess-archer-drush-laravel-prompts.md
new file mode 100644
index 000000000..28262bcd8
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/25-jess-archer-drush-laravel-prompts.md
@@ -0,0 +1,30 @@
+---
+date: 2024-11-07
+topic: Drush and Laravel Prompts
+guests:
+ - Jess Archer
+transistor:
+ id: 8f4510b0
+links:
+ - - Jess' website
+ - https://jessarcher.com
+ - - Jess on X
+ - https://x.com/jessarchercodes
+ - - Laravel
+ - https://laravel.com
+ - - Laravel Prompts
+ - https://laravel.com/docs/prompts
+ - - Drush
+ - https://www.drush.org
+ - - Unveiling Laravel Prompts (Laracon US 2023)
+ - https://youtu.be/PW-2_-KxF-8?si=7Z8i9yqxsgmi-4oH
+talking_points: []
+quotes: []
+chapters: []
+episode_filename: 25-laravel-prompts.mp3
+file_size: 20523767
+duration: 1:02:41
+use: [podcast_episodes]
+---
+
+In this episode, Oliver and Jess Archer (Engineering Team Lead at Laravel) discuss Laravel Prompts and Drush, PHP, Linux and working on the command line with tools like Neovim and tmux.
diff --git a/sculpin/source/_podcast_episodes/26-mark-conroy-the-confident.md b/sculpin/source/_podcast_episodes/26-mark-conroy-the-confident.md
new file mode 100644
index 000000000..21ceb57d8
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/26-mark-conroy-the-confident.md
@@ -0,0 +1,23 @@
+---
+date: 2024-12-31
+topic: The Confident and open source sustainability
+guests:
+ - Mark Conroy
+transistor:
+ id: 30d1a880
+links:
+ - - mark.ie
+ - https://mark.ie
+ - - LocalGov Drupal
+ - https://localgovdrupal.org
+ - - The Confident
+ - https://the-confident.com
+quotes: []
+chapters: []
+episode_filename: 26-the-confident.mp3
+file_size: 25367036
+duration: 00:48:53
+use: [podcast_episodes]
+---
+
+For the final Beyond Blocks episode of 2024, I caught up with Mark Conroy to discuss changes since we spoke in February; namely leaving Annertech and setting up The Confident - a digital agency focused on LocalGov Drupal, different ways to charge for development services and being sponsored to work on open source software.
diff --git a/sculpin/source/_podcast_episodes/27-drupalisms.md b/sculpin/source/_podcast_episodes/27-drupalisms.md
new file mode 100644
index 000000000..c4db3945d
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/27-drupalisms.md
@@ -0,0 +1,28 @@
+---
+date: 2025-02-28
+topic: Discussing Drupalisms
+guests:
+ - Emma Horrell
+ - Luke McCormick
+transistor:
+ id: ~
+links:
+ - - De-jargoning Drupal – working with the community to open up Drupal’s terminology
+ - https://www.drupal.org/association/blog/de-jargoning-drupal-working-with-the-community-to-open-up-drupals-terminology
+ - - Locating Drupalisms (core issue)
+ - https://www.drupal.org/project/drupal/issues/3381734
+ - - The Drupal terminology quiz
+ - https://www.drupal.org/project/drupal/issues/3480953
+ - - Emma on Drupal.org
+ - https://www.drupal.org/u/emma-horrell
+ - - Luke on Drupal.org
+ - https://www.drupal.org/u/cellear
+quotes: []
+chapters: []
+episode_filename: 27-drupalisms.mp3
+file_size: 24474764
+duration: 00:52:29
+use: [podcast_episodes]
+---
+
+In this episode, Oliver is joined by two guests - Emma Horrell, User Experience Manager at the University of Edinburgh, and Luke McCormick to discuss Drupalisms and Drupal's terminology, the Drupalisms working group and the work being done in Drupal and Drupal CMS to de-jargon Drupal.
diff --git a/sculpin/source/_podcast_episodes/28-using-ai-tools-web-coding.md b/sculpin/source/_podcast_episodes/28-using-ai-tools-web-coding.md
new file mode 100644
index 000000000..467e64b60
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/28-using-ai-tools-web-coding.md
@@ -0,0 +1,27 @@
+---
+date: 2025-05-23
+topic: Using AI tools for web coding
+guests:
+ - Luke McCormick
+transistor:
+ id: ~
+links:
+ - - Luke on Drupal.org
+ - https://www.drupal.org/u/cellear
+ - - Web Coding for Non-Coders -- using AI, of course
+ - https://webcamp.stanford.edu/session/web-coding-for-non-coders-using-ai-of-course
+ - - Cursor - the AI text editor
+ - https://www.cursor.com
+ - - The Cache-Size-Block module
+ - https://github.com/cellear/Cache-Size-Block
+ - - Simplify Drupal
+ - https://simplifydrupal.com
+quotes: []
+chapters: []
+episode_filename: 28-using-ai-web-coding.mp3
+file_size: 28682900
+duration: 00:56:04
+use: [podcast_episodes]
+---
+
+In this episode, Oliver is joined again by Luke McCormick, this time to discuss using AI for web coding and how to simplify Drupal websites.
diff --git a/sculpin/source/_podcast_episodes/29-drupal-cms-recipes.md b/sculpin/source/_podcast_episodes/29-drupal-cms-recipes.md
new file mode 100644
index 000000000..04219dca2
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/29-drupal-cms-recipes.md
@@ -0,0 +1,25 @@
+---
+date: 2025-05-30
+topic: Drupal CMS Recipes
+guests:
+ - Gareth Alexander
+transistor:
+ id: ~
+links:
+ - - The Drupal recipes cookbook
+ - https://www.drupal.org/docs/extending-drupal/contributed-modules/contributed-module-documentation/distributions-and-recipes-initiative/recipes-cookbook
+ - - The Recipes Initiative project on Drupal.org
+ - https://www.drupal.org/project/distributions_recipes
+ - - The Bad Judgement module
+ - https://www.drupal.org/project/bad_judgement
+ - - Gareth on Drupal.org
+ - https://www.drupal.org/u/the_g_bomb
+ - - Gareth's presentation at DrupalCamp England
+ - https://www.youtube.com/watch?v=b7MDA-kKCCk
+episode_filename: 29-drupal-cms-recipes.mp3
+file_size: 30861164
+duration: 00:59:29
+use: [podcast_episodes]
+---
+
+In this episode, Oliver is joined by Gareth Alexander - Drupal CMS Accessibility Tools Track Lead and recent speaker at DrupalCamp England - to discuss Drupal CMS recipes.
diff --git a/sculpin/source/_podcast_episodes/3-non-technical-contribution.md b/sculpin/source/_podcast_episodes/3-non-technical-contribution.md
new file mode 100644
index 000000000..57bd29f99
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/3-non-technical-contribution.md
@@ -0,0 +1,33 @@
+---
+date: 2023-11-24
+topic: Community engagement for non-technical Drupal enthusiasts
+guests:
+ - Niklas Franke
+links:
+ - - DrupalCon Lille
+ - https://events.drupal.org/lille2023
+ - - 'Beyond the code: Community engagement for non-technical Drupal enthusiasts'
+ - https://events.drupal.org/lille2023/session/beyond-code-community-engagement-non-technical-drupal-enthusiasts
+ - - 'How to organise your own Splash Awards'
+ - https://events.drupal.org/lille2023/session/how-organise-your-own-splash-awards
+ - - 'DrupalCon sponsored talk: What developers really want: Uncovering the essential work benefits for software developers (sponsored by Factorial)'
+ - https://events.drupal.org/lille2023/session/what-developers-really-want-uncovering-essential-work-benefits-software
+ - - 'Splash Awards'
+ - https://www.drupal.org/community/splash-awards
+ - 'Niklas Franke on Drupal.org'
+ - https://www.drupal.org/u/heroicnick
+ - - 'Factorial GmbH'
+ - https://www.factorial.io/en/drupal-agency
+ - - 'Factorial GmbH on Drupal.org'
+ - https://www.drupal.org/factorial-gmbh
+ - - 'Factorial on YouTube'
+ - https://www.youtube.com/channel/UCJslkuiIJLTRmYEvN2y-jzA
+transistor:
+ id: 85bdc4c7
+episode_filename: 3-community-engagement.mp3
+file_size: 24882117
+duration: 0:50:39
+use: [podcast_episodes]
+---
+
+This week, Oliver is joined by Niklas Franke to discuss DrupalCon Lille, contributing to Drupal as a non-technical enthusiast, the Splash Awards and the Drupal community in Germany.
diff --git a/sculpin/source/_podcast_episodes/30-growing-engineering-culture.md b/sculpin/source/_podcast_episodes/30-growing-engineering-culture.md
new file mode 100644
index 000000000..0569ea448
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/30-growing-engineering-culture.md
@@ -0,0 +1,23 @@
+---
+date: 2025-06-11
+topic: Growing Engineering Culture
+guests:
+ - Ev Maslovskiy
+links:
+ - - Ev on Drupal.org
+ - https://www.drupal.org/u/spleshka
+ - - SystemSeed
+ - https://systemseed.com
+ - - SystemSeed on Drupal.org
+ - https://www.drupal.org/systemseed
+ - - DrupalCamp England
+ - https://www.drupalcampengland.org
+ - - Video recording of Ev's talk
+ - https://www.youtube.com/watch?v=Q_9fWVCAUtU
+episode_filename: 30-engineering-culture.mp3
+file_size: 30861164
+duration: 01:08:43
+use: [podcast_episodes]
+---
+
+In this episode, Oliver is joined by Ev Maslovskiy - CTO at SystemSeed - to discuss engineering culture after his recent talk at DrupalCamp England.
diff --git a/sculpin/source/_podcast_episodes/31-modeler-api.md b/sculpin/source/_podcast_episodes/31-modeler-api.md
new file mode 100644
index 000000000..289dc2f51
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/31-modeler-api.md
@@ -0,0 +1,37 @@
+---
+date: 2025-06-27
+topic: Modeler API
+guests:
+ - Jürgen Haas
+links:
+- - Jürgen on Drupal.org
+ - https://www.drupal.org/u/jurgenhaas
+- - The ECA module
+ - https://www.drupal.org/project/eca
+- - The BPMN.iO module
+ - https://www.drupal.org/project/bpmn_io
+- - Drupal Events, Conditions and Actions with Jürgen Haas
+ - /podcast/23-jurgen-haas-eca
+- - Drupal Remote Dashboard
+ - https://www.drupal.org/project/drd
+- - Drupal Recipes
+ - https://www.drupal.org/docs/extending-drupal/drupal-recipes
+- - Discussing Drupal CMS Recipes with Gareth Alexander
+ - /daily/2025/06/03/discussing-drupal-cms-recipes-gareth-alexander
+- - Drupal CMS
+ - https://www.drupal.org/project/cms
+- - Discussing Drupalisms with Emma Horrell and Luke McCormick
+ - /podcast/27-discussing-drupalisms
+- - Model Context Protocol
+ - https://modelcontextprotocol.io
+- - The Model Context Protocol module
+ - https://www.drupal.org/project/mcp
+- - Using AI tools for web coding with Luke McCormick
+ - /podcast/28-using-ai-tools-web-coding
+episode_filename: 31-modeler-api.mp3
+file_size: 21761540
+duration: 00:46:05
+use: [podcast_episodes]
+---
+
+In this episode, Oliver is re-joined by Jürgen Haas to discuss the new Modeler API module and progress made with the ECA module.
diff --git a/sculpin/source/_podcast_episodes/32-accessibility.md b/sculpin/source/_podcast_episodes/32-accessibility.md
new file mode 100644
index 000000000..d3409c06d
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/32-accessibility.md
@@ -0,0 +1,39 @@
+---
+date: 2025-07-11
+topic: Web Accessibility
+guests:
+ - Mike Gifford
+links:
+- - Mike's website
+ - https://ox.ca
+- - Mike on Drupal.org
+ - https://www.drupal.org/u/mgifford
+- - My Web, My Way
+ - https://www.bbc.co.uk/programmes/p005khmk
+- - simplA11yPDFCrawler
+ - https://github.com/accessibility-luxembourg/simplA11yPDFCrawler
+- - Drupal.org's Accessibility page
+ - https://www.drupal.org/about/features/accessibility
+- - WCAG 2 Overview
+ - https://www.w3.org/WAI/standards-guidelines/wcag
+- - Drupal's core gates
+ - https://www.drupal.org/about/core/policies/core-change-policies/core-gates
+- - Accessibility Roles and Responsibilities Mapping (ARRM) Community Group
+ - https://www.w3.org/community/arrm
+- - Sustainable Web Interest Group
+ - https://www.w3.org/groups/ig/sustainableweb
+- - Writing Accessibility tests in Nightwatch
+ - https://www.drupal.org/docs/develop/automated-testing/javascript-testing-using-nightwatch
+- - The Sa11y module
+ - https://www.drupal.org/project/sa11y
+- - Drupal's Accessibility Coding Standards
+ - https://www.drupal.org/docs/develop/standards/accessibility-coding-standards
+- - Shift-left testing
+ - https://en.wikipedia.org/wiki/Shift-left_testing
+episode_filename: 32-web-accessibility.mp3
+file_size: 38235236
+duration: 01:23:01
+use: [podcast_episodes]
+---
+
+In this episode, Oliver is joined by Mike Gifford - Senior Strategist at CivicActions, W3C Invited Expert and Drupal Core Accessibility Maintainer - to discuss web accessibility.
diff --git a/sculpin/source/_podcast_episodes/4-ed-crompton-oxfam-case-study.md b/sculpin/source/_podcast_episodes/4-ed-crompton-oxfam-case-study.md
new file mode 100644
index 000000000..b60be8ad8
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/4-ed-crompton-oxfam-case-study.md
@@ -0,0 +1,39 @@
+---
+date: 2023-12-01
+topic: A case study from Oxfam
+guests:
+ - Ed Crompton
+transistor:
+ id: 0ff9802a
+links:
+ - - Ed Crompton on Drupal.org
+ - https://www.drupal.org/u/eddie_c
+ - - 'Oxfam country sites: From big bang to small batch delivery'
+ - https://www.linkedin.com/pulse/oxfam-country-sites-from-big-bang-small-batch-edward-crompton
+ - - Oxfam.org
+ - https://www.oxfam.org/en
+ - - https://cambodia.oxfam.org
+ - https://cambodia.oxfam.org
+ - - Pattern Lab
+ - https://patternlab.io
+ - - Fractal
+ - https://fractal.build
+ - - Twig
+ - https://twig.symfony.com
+ - - Working in small batches
+ - http://www.startuplessonslearned.com/2009/02/work-in-small-batches.html
+ - - Behat
+ - http://www.behat.org
+ - - Behat integration with Drupal
+ - https://www.drupal.org/project/behat
+ - - phenaproxima on Drupal.org
+ - https://www.drupal.org/u/phenaproxima
+ - - I've been using Behat wrong this whole time
+ - https://phenaproxima.net/2018/08/07/behat-insanity.html
+ - - Drupal Test Traits
+ - https://gitlab.com/weitzman/drupal-test-traits
+episode_filename: 4-oxfam.mp3
+file_size: 21505533
+duration: 0:47:36
+use: [podcast_episodes]
+---
diff --git a/sculpin/source/_podcast_episodes/5-dieter-blomme-technical-debt.md b/sculpin/source/_podcast_episodes/5-dieter-blomme-technical-debt.md
new file mode 100644
index 000000000..fd07a88fd
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/5-dieter-blomme-technical-debt.md
@@ -0,0 +1,33 @@
+---
+date: 2023-12-09
+topic: Managing technical debt
+guests:
+ - Dieter Blomme
+transistor:
+ id: 40066a7f
+links:
+ - - DrupalCon Lille
+ - https://events.drupal.org/lille2023
+ - - 'Managing technical debt: lessons learned and how modern Drupal helps you'
+ - https://events.drupal.org/lille2023/session/managing-technical-debt-lessons-learned-and-how-modern-drupal-helps-you
+ - - Understanding Technical Debt in the Drupal ecosystem
+ - https://www.youtube.com/watch?v=1YSL4Be7jDY
+ - - Dieter Blomme on Drupal.org
+ - https://www.drupal.org/u/daften
+ - - Dropsolid website
+ - https://dropsolid.com/
+ - - Dropsolid on Drupal.org
+ - https://www.drupal.org/dropsolid
+ - - Dropsolid on YouTube
+ - https://www.youtube.com/@dropsolid.experiencecompany
+ - - DrupalCon session video
+ - https://youtu.be/S-B7_YiskVM
+ - - Concerns vs objectives
+ - https://blog.holacracy.org/holacracy-basics-understanding-objections-d87b579d00d1
+ - - Decision by traffic light
+ - https://www.linkedin.com/pulse/decision-making-101-traffic-lights-puppy-case-study-valerio-magliulo
+episode_filename: 5-technical-debt.mp3
+file_size: 23248532
+duration: 0:52:22
+use: [podcast_episodes]
+---
diff --git a/sculpin/source/_podcast_episodes/6-dan-leech-php-tui.md b/sculpin/source/_podcast_episodes/6-dan-leech-php-tui.md
new file mode 100644
index 000000000..dd42a4720
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/6-dan-leech-php-tui.md
@@ -0,0 +1,41 @@
+---
+date: 2023-12-19
+topic: 'TUIs, CLIs and open-source'
+guests:
+ - Dan Leech
+transistor:
+ id: 45d57d43
+links:
+ - - PHP TUI on GitHub
+ - https://github.com/php-tui
+ - - Phpactor on GitHub
+ - https://github.com/phpactor
+ - - PHPbench on GitHub
+ - https://github.com/phpbench
+ - - Phpactor documentation
+ - https://phpactor.readthedocs.io
+ - - PHPBench documentation
+ - https://phpbench.readthedocs.io
+ - - Testing code performance with PHPBench
+ - https://youtu.be/-qxu6n9Q-3k?si=2N8Ee9GIgCul7_e1
+ - - PHP-TUI Progress
+ - https://www.dantleech.com/blog/2023/11/03/php-tui-progress
+ - - PHP Term
+ - https://www.dantleech.com/blog/2023/11/27/php-term
+ - - PHP Architecture Tester (phpat)
+ - https://github.com/carlosas/phpat
+ - - Jess Archer
+ - https://jessarcher.com
+ - - Neovim as a PHP and JavaScript IDE (Laracasts)
+ - https://laracasts.com/series/neovim-as-a-php-ide
+ - - Laravel Prompts
+ - https://github.com/laravel/prompts
+ - - Symfony Terminal component
+ - https://speakerdeck.com/fabpot/the-symfony-terminal-component
+episode_filename: 6-tuis-clis.mp3
+file_size: 22071243
+duration: 0:53:17
+use: [podcast_episodes]
+---
+
+In this week's episode, Oliver is joined by Dan Leech to discuss building command-line applications and TUIs with PHP based on Dan's open-source projects - Phpactor, PHPBench and, most recently, PHP-TUI.
diff --git a/sculpin/source/_podcast_episodes/7-mike-karthauser-testing-legacy.md b/sculpin/source/_podcast_episodes/7-mike-karthauser-testing-legacy.md
new file mode 100644
index 000000000..c9efdb416
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/7-mike-karthauser-testing-legacy.md
@@ -0,0 +1,39 @@
+---
+date: 2024-01-10
+topic: Testing Legacy
+guests:
+ - Mike Karthauser
+transistor:
+ id: c264ca69
+links:
+ - - Huboo
+ - https://huboo.com
+ - - PHPUnit
+ - https://phpunit.de
+ - - Laravel
+ - https://laravel.com
+ - - Expression Engine
+ - https://expressionengine.com
+ - - PHPBench
+ - https://phpbench.com
+ - - Working in small batches
+ - http://www.startuplessonslearned.com/2009/02/work-in-small-batches.html
+ - - Cyclomatic complexity
+ - https://en.wikipedia.org/wiki/Cyclomatic_complexity
+ - - Shopify
+ - https://www.shopify.com
+ - - Drupal Commerce
+ - https://drupalcommerce.org
+ - - PHP South West
+ - https://phpsw.uk
+ - - Automica labs
+ - https://automica.io
+ - - Mike on LinkedIn
+ - https://www.linkedin.com/in/mikekarthauser
+episode_filename: 7-testing-legacy.mp3
+file_size: 24294452
+duration: 0:53:23
+use: [podcast_episodes]
+---
+
+Oliver and Mike discuss e-commerce, legacy code, technical debt, automated testing, test-driven development, refactoring, code vs. no-code solutions, and Mike's recent talk at PHP South West in Bristol.
diff --git a/sculpin/source/_podcast_episodes/8-eirik-morland-violinist.md b/sculpin/source/_podcast_episodes/8-eirik-morland-violinist.md
new file mode 100644
index 000000000..f61aa67e4
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/8-eirik-morland-violinist.md
@@ -0,0 +1,31 @@
+---
+date: 2024-01-29
+topic: Violinist and automation
+guests:
+ - Eirik Morland
+transistor:
+ id: ba4405e5
+links:
+ - - violinist.io
+ - https://violinist.io
+ - - Violinist on Drupal.org
+ - https://www.drupal.org/violinist
+ - - Eirik on Drupal.org
+ - https://www.drupal.org/u/eiriksm
+ - - Composer
+ - https://getcomposer.org
+ - - Packagist
+ - https://packagist.org
+ - - Private Packagist
+ - https://packagist.com
+ - - Automatic Updates initiative
+ - https://www.drupal.org/about/core/strategic-initiatives/automatic-updates
+ - - Logo is a conductor, not a composer
+ - https://github.com/composer/getcomposer.org/issues/36
+episode_filename: 8-violinist.mp3
+file_size: 27664298
+duration: 1:00:01
+use: [podcast_episodes]
+---
+
+Oliver and Eirik Morland discuss automated dependency updates with Violinist, PHP and Composer, why automation is good, and focusing on providing value for clients and customers.
diff --git a/sculpin/source/_podcast_episodes/9-tim-lehnen.md b/sculpin/source/_podcast_episodes/9-tim-lehnen.md
new file mode 100644
index 000000000..62e918080
--- /dev/null
+++ b/sculpin/source/_podcast_episodes/9-tim-lehnen.md
@@ -0,0 +1,39 @@
+---
+date: 2024-02-05
+topic: The Drupal Association
+guests:
+ - Tim Lehnen
+links:
+ - - Drupal Association
+ - https://www.drupal.org/association
+ - - Drupal recognized as a Digital Public Good
+ - https://dri.es/drupal-recognized-as-a-digital-public-good
+ - - Drupal Association elections
+ - https://www.drupal.org/association/board/elections
+ - - Find a Drupal 7 migration partner
+ - https://www.drupal.org/about/drupal-7/d7eol/partners
+ - - Join partner programs
+ - https://www.drupal.org/association/programs
+ - - Drupal Events
+ - https://events.drupal.org
+ - - Drupal Jobs
+ - https://jobs.drupal.org
+ - - GitLab
+ - https://www.gitlab.com
+ - - GitLab CI on Drupal.org
+ - https://www.drupal.org/docs/develop/git/using-gitlab-to-contribute-to-drupal/gitlab-ci
+ - - Tim on Drupal.org
+ - https://www.drupal.org/u/hestenet
+ - - Tim on Twitter
+ - https://twitter.com/TimLehnen
+transistor:
+ id: e5d5ba38
+episode_filename: 9-drupal-association.mp3
+file_size: 36650781
+duration: 1:06:23
+use: [podcast_episodes]
+---
+
+This week, Oliver is joined by Tim Lehnen - the CTO of the Drupal Association.
+
+They discuss what is the Drupal Association is and does, how companies and individuals can contribute and support the Association, some recent and upcoming improvements to Drupal.org, Drupal 7's end-of-life, and more.
diff --git a/sculpin/source/_posts/1.md b/sculpin/source/_posts/1.md
new file mode 100644
index 000000000..58b4ae468
--- /dev/null
+++ b/sculpin/source/_posts/1.md
@@ -0,0 +1,36 @@
+---
+title: Ellipsis in pager template fails accessibility tests
+date: '2024-08-21 12:00:00'
+tags:
+ - Drupal
+ - Accessibility
+ - ARIA
+ - Software Development
+ - Web Development
+note: true
+permalink: /notes/1-ellipsis-in-pager-template-fails-accessibility-tests
+---
+
+```html
+
and must only directly contain
,
+{% endif %}
+```
+
+Now the pixel can be removed just by setting `add_facebook_pixel: false` in
+`sculpin_site.yml`, and without changing any other configuration or templates.
diff --git a/sculpin/source/_posts/fixing-drupal-simpletest-issues-inside-docker-containers.md b/sculpin/source/_posts/fixing-drupal-simpletest-issues-inside-docker-containers.md
new file mode 100644
index 000000000..ad7e045b6
--- /dev/null
+++ b/sculpin/source/_posts/fixing-drupal-simpletest-issues-inside-docker-containers.md
@@ -0,0 +1,130 @@
+---
+title: Fixing Drupal SimpleTest issues inside Docker Containers
+date: 2017-05-05
+excerpt: How I managed to get my Drupal SimpleTest tests to run and pass within Docker containers.
+tags:
+ - docker
+ - drupal
+ - drupal-planet
+ - simpletest
+ - testing
+---
+
+I’ve been a Drupal VM user for a long time, but lately I’ve been using a
+combination Drupal VM and Docker for my local development environment. There
+were a couple of issues preventing me from completely switching to Docker - one
+of which being that when I tried running of my Simpletest tests, a lot of them
+would fail where they would pass when run within Drupal VM.
+
+Here’s an excerpt from my `docker-compose.yml` file:
+
+**TL;DR** You need to include the name of your web server container as the
+`--url` option to `run-scripts.php`.
+
+I’ve been a [Drupal VM][1] user for a long time, but lately I’ve been using a
+combination Drupal VM and [Docker][0] for my local development environment.
+There were a couple of issues preventing me from completely switching to
+Docker - one of which being that when I tried running of my Simpletest tests, a
+lot of them would fail where they would pass when run within Drupal VM.
+
+Here’s an excerpt from my `docker-compose.yml` file:
+
+```yaml
+services:
+ php:
+ image: wodby/drupal-php:5.6
+ volumes:
+ - ./repo:/var/www/html
+
+ nginx:
+ image: wodby/drupal-nginx:7-1.10
+ environment:
+ NGINX_BACKEND_HOST: php
+ NGINX_SERVER_ROOT: /var/www/html/web
+ ports:
+ - "80:80"
+ volumes_from:
+ - php
+...
+```
+
+Nginx and PHP-FPM are running in separate containers, the volumes are shared
+across both and the Nginx backend is set to use the `php` container.
+
+This is the command that I was using to run the tests:
+
+```bash
+$ docker-compose run --rm \
+ -w /var/www/html/web \
+ php \
+ php scripts/run-tests.sh \
+ --php /usr/local/bin/php \
+ --class OverrideNodeOptionsTestCase
+```
+
+This creates a new instance of the `php` container, sets the working directory
+to my Drupal root and runs Drupal’s `run-tests.sh` script with some arguments.
+In this case, I'm running the `OverrideNodeOptionsTestCase` class for the
+override_node_options tests. Once complete, the container is deleted because of
+the `--rm` option.
+
+This resulted in 60 of the 112 tests failing, whereas they all passed when run
+within a Drupal VM instance.
+
+```
+Test summary
+------------
+
+Override node options 62 passes, 60 fails, 29 exceptions, and 17 debug messages
+
+Test run duration: 2 min 25 sec
+```
+
+Running the tests again with the`--verbose` option, I saw this message appear in
+the output below some of the failing tests:
+
+> simplexml_import_dom(): Invalid Nodetype to import
+
+\*\*Up After checking that I had all of the required PHP extensions installed, I
+ran `docker-compose exec php bash` to connect to the `php` container and ran
+`curl http://localhost` to check the output. Rather than seeing the HTML for the
+site, I got this error message:
+
+> curl: (7) Failed to connect to localhost port 80: Connection refused
+
+Whereas `curl http://nginx` returns the HTML for the page, so included it with
+the `--url` option to `run-tests.sh`, and this resulted in my tests all passing.
+
+```bash
+$ docker-compose run --rm \
+ -w /var/www/html/web \
+ php \
+ php scripts/run-tests.sh \
+ --php /usr/local/bin/php \
+ --url http://nginx \
+ --class OverrideNodeOptionsTestCase
+```
+
+```
+Test summary
+------------
+
+Override node options 121 passes, 0 fails, 0 exceptions, and 34 debug messages
+
+Test run duration: 2 min 31 sec
+```
+
+**Note:** In this example I have separate `nginx` and `php` containers, but I've
+tried and had the same issue when running Nginx and PHP-FPM in the same
+container - e.g. called `app` - and still needed to add `--url http://app` in
+order for the tests to run successfully.
+
+I don’t know if this issue is macOS specfic (I know that [Drupal CI][2] is based
+on Docker, and I don’t know if it’s an issue) but I’m going to test also on my
+Ubuntu Desktop environment and investigate further and also compare the test run
+times for Docker in macOS, Docker in Ubuntu and within Drupal VM. I’m also going
+to test this with PHPUnit tests with Drupal 8.
+
+[0]: https://www.docker.com
+[1]: https://www.drupalvm.com
+[2]: https://www.drupal.org/drupalorg/docs/drupal-ci
diff --git a/sculpin/source/_posts/forward-one-domain-another-using-modrewrite-htaccess.md b/sculpin/source/_posts/forward-one-domain-another-using-modrewrite-htaccess.md
new file mode 100644
index 000000000..54a9bf07f
--- /dev/null
+++ b/sculpin/source/_posts/forward-one-domain-another-using-modrewrite-htaccess.md
@@ -0,0 +1,29 @@
+---
+title: Forward one domain to another using mod_rewrite and .htaccess
+date: 2012-05-23
+excerpt: How to use the .htaccess file to forward to a different domain.
+tags:
+ - .htaccess
+ - apache
+ - code
+ - drupal
+ - mod_rewrite
+---
+
+How to use the .htaccess file to forward to a different domain.
+
+Within the mod_rewrite section of your .htaccess file, add the following lines:
+
+ RewriteCond %{HTTP_HOST} ^yoursite\.co\.uk$
+ RewriteRule (.*) http://yoursite.com/$1 [R=301,L]
+
+This automatically forwards any users from http://yoursite.co.uk to
+http://yoursite.com. This can also be used to forward multiple domains:
+
+ RewriteCond %{HTTP_HOST} ^yoursite\.co\.uk$ [OR]
+ RewriteCond %{HTTP_HOST} ^yoursite\.info$ [OR]
+ RewriteCond %{HTTP_HOST} ^yoursite\.biz$ [OR]
+ RewriteCond %{HTTP_HOST} ^yoursite\.eu$
+ RewriteRule (.*) http://yoursite.com/$1 [R=301,L]
+
+If any of the RewriteCond conditions apply, then the RewriteRule is executed.
diff --git a/sculpin/source/_posts/git-format-patch-your-friend.md b/sculpin/source/_posts/git-format-patch-your-friend.md
new file mode 100644
index 000000000..4f929dd37
--- /dev/null
+++ b/sculpin/source/_posts/git-format-patch-your-friend.md
@@ -0,0 +1,136 @@
+---
+title: git format-patch is your Friend
+date: 2014-05-21
+excerpt: An explanation of the "git format-patch" command, and how it could be used in Drupal's Git workflow.
+tags:
+ - drupal
+ - drupal-planet
+ - git
+ - patches
+---
+
+An explanation of the "git format-patch" command, and how it could be used in
+Drupal's Git workflow.
+
+## The Problem
+
+As an active contributor to the [Drupal](http://drupal.org) project, I spend a
+lot of time working with other peoples’ modules and themes, and occassionally
+have to fix a bug or add some new functionality.
+
+In the Drupal community, we use a patch based workflow where any changes that I
+make get exported to a file detailing the differences. The patch file (\*.patch)
+is attached to an item in an issue queue on Drupal.org, applied by the
+maintainer to their local copy of the code and reviewed, and hopefully
+committed.
+
+There is an option that the maintainer can add to the end of their commit
+message.
+
+For example:
+
+```bash
+--author="opdavies "
+```
+
+This differs slightly different for each Drupal user, and the code can be found
+on their Drupal.org profile page.
+
+If this is added to the end of the commit message, the resulting commit will
+show that it was committed by the maintainer but authored by a different user.
+This will then display on Drupal.org that you’ve made a commit to that project.
+
+
+
+The problem is that some project maintainers either don’t know about this option
+or occasionally forget to add it. [Dreditor](http://dreditor.org) can suggest a
+commit message and assign an author, but it is optional and, of course, not all
+maintainers use Dreditor (although they probably should).
+
+The `git format-patch` command seems to be the answer, and will be my preferred
+method for generating patch files in the future rather than `git diff`.
+
+## What does it do Differently?
+
+From the [manual page](http://git-scm.com/docs/git-format-patch):
+
+> Prepare each commit with its patch in one file per commit, formatted to
+> resemble UNIX mailbox format. The output of this command is convenient for
+> e-mail submission or for use with git am.
+
+Here is a section of a patch that I created for the
+[Metatag module](http://drupal.org/project/metatag) using `git format-patch`:
+
+```bash
+From 80c8fa14de7f4a83c2e70367aab0aedcadf4f3b0 Mon Sep 17 00:00:00 2001
+From: Oliver Davies <oliver@oliverdavies.co.uk>
+Subject: [PATCH] Exclude comment entities when checking if this is the page,
+ otherwise comment_fragment.module will break metatag
+---
+```
+
+As mentioned above, the patch is structured in an email format. The commit
+message is used as the subject line, and the date that the commit was made
+locally is used for the date. What we’re interested in is the “From” value. This
+contains your name and email address from your `~/.gitconfig` file and is used
+to author the patch automatically.
+
+Everything below this is the same as a standard patch file, the same as if was
+generated with `git diff`.
+
+The full patch file can be found at
+.
+
+## The Process
+
+How did I create this patch? Here are the steps that I took:
+
+1. Clone the source repository using
+ `$ git clone --branch 7.x-1.x http://git.drupal.org/project/metatag.git` and
+ move into that directory.
+2. Create a branch for this patch using
+ `$ git checkout -b 2265447-comment-fragment-conflict`.
+3. Add and commit any changes as normal.
+4. Generate the patch file using
+ `$ git format-patch 7.x-1.x --stdout > metatag-comment-fragment-conflict-2265447-4.patch`.
+
+_Note:_ I am defining 7.x-1.x in the last step as the original branch to compare
+(i.e. the original branch that we forked to make our issue branch). This will
+change depending on the project that you are patching, and it’s version number.
+Also, commits should always be made against the development branch and not the
+stable release.
+
+By default, a separate patch file will be created for each commit that we’ve
+made. This is overridden by the `--stdout` option which combines all of the
+patches into a single file. This is the recommended approach when uploading to
+Drupal.org.
+
+The resulting patch file can be uploaded onto a Drupal.org issue queue, reviewed
+by the Testbot and applied by a module maintainer, and you automatically get the
+commit attributed. Problem solved.
+
+## Committing the Patch
+
+If you need to commit a patch that was created using `git format-patch`, the
+best command to do this with is the `git am` command.
+
+For example, within your repository, run:
+
+```bash
+$ git am /path/to/file
+$ git am ~/Code/metatag-comment-fragment-conflict-2265447-4.patch
+```
+
+You should end up with some output similar to the following:
+
+```bash
+Applying: #2272799 Added supporters section
+Applying: #2272799 Added navigation tabs
+Applying: #2272799 Fixed indentation
+Applying: #2272799 Replaced URL
+```
+
+Each line is the commit message associated with that patch.
+
+Assuming that there are no errors, you can go ahead and push your updated code
+into your remote repository.
diff --git a/sculpin/source/_posts/github-actions-phpunit-colours.md b/sculpin/source/_posts/github-actions-phpunit-colours.md
new file mode 100644
index 000000000..5fad2fee5
--- /dev/null
+++ b/sculpin/source/_posts/github-actions-phpunit-colours.md
@@ -0,0 +1,11 @@
+---
+title: Coloured output with PHPUnit and GitHub Actions
+excerpt: How to have colours in your PHPUnit output when running with GitHub Actions.
+date: 2020-08-12
+tags:
+ - github-actions
+ - phpunit
+ - testing
+---
+
+
If you're using GitHub Actions to run tests for your PHP projects and want colours in the output, append `--colors=always` to your phpunit command. pic.twitter.com/0AVwxCP4Bv
diff --git a/sculpin/source/_posts/going-drupalcon.md b/sculpin/source/_posts/going-drupalcon.md
new file mode 100644
index 000000000..18ce7b234
--- /dev/null
+++ b/sculpin/source/_posts/going-drupalcon.md
@@ -0,0 +1,16 @@
+---
+title: Going to DrupalCon
+date: 2013-07-26
+excerpt: Precedent are sending myself and two of our other Drupal Developers to Drupalcon Prague.
+tags:
+ - drupalcon
+ - precedent
+---
+
+[Precedent](http://www.precedent.co.uk) are sending myself and two of our other
+Drupal Developers to [Drupalcon Prague](http://prague2013.drupal.org).
+
+Having wanted to attend the last few Drupalcons (London, especially) but not
+being able to, I'm definitely looking forward to this one.
+
+See you there!
diff --git a/sculpin/source/_posts/going-full-vim.md b/sculpin/source/_posts/going-full-vim.md
new file mode 100644
index 000000000..453e7051b
--- /dev/null
+++ b/sculpin/source/_posts/going-full-vim.md
@@ -0,0 +1,50 @@
+---
+title: Going "Full Vim" for my development work
+excerpt: I've recently been using Neovim for all of my coding, as well as for my blog posts and slide decks.
+tags:
+ - vim
+date: 2021-07-08
+---
+
+For the past few months, I've gone "full Vim" ([Neovim][], to be exact) when writing any code - including anything for work or freelance projects, this blog post, and any presentation slide decks.
+
+I've been a long-time casual Vim user, enabling Vi or Vim mode within other editors, including Sublime Text, PhpStorm, and VS Code, and using Vim to write Git commit messages or to edit single files before closing it again. I remember searching how to add Vim features like relative line numbers in other editors, and trying things that would work within Vim but not when using in a plugin or extension.
+
+I've seen people and companies like [Lorna Jane Mitchell][], Suz Hinton ([noopkat][]), and [Thoughtbot][] using Vim in their presentations and videos for a long time but I haven't tried to switch before myself.
+
+Inspired by them and others including [Robin Malfait][], [TheAltF4Stream][], [Codico][], [Michael Dyrynda][], [ThePrimeagen][] with their recent live streams, videos, podcasts, and courses, I decided to give it a try.
+
+## Plugins
+
+You can see the [full list of plugins on GitHub](https://github.com/opdavies/dotfiles/blob/main/.config/nvim/plugins.vim), but here are some of the main ones that I've been using so far:
+
+- [fzf](https://github.com/junegunn/fzf.vim) - a fuzzy-finder to easily locate and open files.
+- [CoC](https://github.com/neoclide/coc.nvim) and [Intelephense](https://intelephense.com) - adds auto-completion and code snippet support, including refactorings such as renaming symbols.
+- [NERDTree](https://github.com/preservim/nerdtree) - a tree explorer, though I usually use the fuzzy finder so this isn't used that often.
+- [Git gutter](https://github.com/airblade/vim-gitgutter) - displays Git diff information in the gutter of the current file.
+- [Blamer](https://github.com/APZelos/blamer.nvim) - inspired by the GitLens plugin for VS Code, displays `git blame` information for the current line.
+- [Nord](https://github.com/arcticicestudio/nord-vim), [jellybeans](https://github.com/nanotech/jellybeans.vim), and [ayu](https://github.com/ayu-theme/ayu-vim) - different themes that I'm trying and switching between.
+
+## Configuration
+
+If you'd like to see my full Neovim configuration, see the `.config/nvim` directory and the `init.vim` file in my [Dotfiles repository on GitHub](https://github.com/opdavies/dotfiles/tree/main/.config/nvim).
+
+## Conclusion
+
+I'm enjoying my first few months of using Vim full-time, and so far, I haven't looked back. I''ve had no issues using it in a Windows/WSL 2 environment either, which was great.
+
+I have a [cheat sheet on GitHub Gists](https://gist.github.com/opdavies/f944261b54f70b43f2297cab6779cf59) where I note the current things that I'm trying to learn and commit to memory.
+
+As I use it and learn more, I'm sure that I'll be posting more Vim-related content here too.
+
+Have any Vim/Neovim suggestions, tips, or tricks? Let me know on Twitter.
+
+[codico]: https://www.twitch.tv/codico
+[lorna jane mitchell]: https://lornajane.net
+[michael dyrynda]: https://dyrynda.com.au
+[neovim]: https://neovim.io
+[noopkat]: https://www.twitch.tv/noopkat
+[robin malfait]: https://twitter.com/malfaitrobin
+[thealtf4stream]: https://www.twitch.tv/thealtf4stream
+[theprimeagen]: https://twitter.com/theprimeagen
+[thoughtbot]: https://thoughtbot.com
diff --git a/sculpin/source/_posts/how-add-date-popup-calendar-custom-form.md b/sculpin/source/_posts/how-add-date-popup-calendar-custom-form.md
new file mode 100644
index 000000000..807b6b546
--- /dev/null
+++ b/sculpin/source/_posts/how-add-date-popup-calendar-custom-form.md
@@ -0,0 +1,44 @@
+---
+title: How to add a date popup calendar onto a custom form
+date: 2012-05-23
+excerpt: How to use a date popup calendar within your custom module.
+tags:
+ - calendar
+ - date
+ - drupal
+ - drupal-7
+ - drupal-planet
+ - form-api
+ - forms
+---
+
+How to use a date popup calendar within your custom module.
+
+First, I need to download the
+[Date](http://drupal.org/project/date 'Date module on Drupal.org') module, and
+make my module dependent on date_popup by adding the following line into my
+module's .info file.
+
+```ini
+dependencies[] = date_popup
+```
+
+Within my form builder function:
+
+```php
+$form['date'] = array(
+ '#title' => t('Arrival date'),
+
+ // Provided by the date_popup module
+ '#type' => 'date_popup',
+
+ // Uses the PHP date() format - http://php.net/manual/en/function.date.php
+ '#date_format' => 'j F Y',
+
+ // Limits the year range to the next two upcoming years
+ '#date_year_range' => '0:+2',
+
+ // Default value must be in 'Y-m-d' format.
+ '#default_value' => date('Y-m-d', time()),
+);
+```
diff --git a/sculpin/source/_posts/how-create-apply-patches.md b/sculpin/source/_posts/how-create-apply-patches.md
new file mode 100644
index 000000000..ab1e67a89
--- /dev/null
+++ b/sculpin/source/_posts/how-create-apply-patches.md
@@ -0,0 +1,50 @@
+---
+title: How to Create and Apply Patches
+date: 2010-10-10
+excerpt: How to create and apply patches, ready for the Drupal.org issue queues.
+tags:
+ - drupal-6
+ - drupal-planet
+ - modules
+ - patches
+---
+
+Earlier this year, I posted a solution to
+[an issue](http://drupal.org/node/753898) on the Drupal.org issue queue.
+Originally, I just posted the code back onto the issue, but have now created a
+patch that can easily be applied to any Drupal 6 installation. Here is a
+run-through of the process of creating and applying a patch. In this case, I
+made changes to the `user_pass_validate()` function that's found within
+`modules/user/user.pages.inc`.
+
+To begin with, a download a fresh copy of Drupal 6.19 and created a copy of the
+original user.pages.inc file. Within the duplicate file, I made the same changes
+to the function that I did in earlier code, and saved the changes. Now, within
+my Terminal, I can navigate to Drupal's root directory and create the patch.
+
+```bash
+diff -rup modules/user/user.pages.inc modules/user/user.pages2.inc > /Users/oliver/Desktop/different_messages_for_blocked_users.patch
+```
+
+This command compares the differences between the two files, and creates the
+specified patch file.
+
+To apply the patch to my Drupal installation, I go back to Terminal and run the
+following code:
+
+```bash
+patch -p0 < /Users/oliver/Desktop/different_messages_for_blocked_users.patch
+```
+
+If, for some reason, I need to reverse the patch, I can run this code:
+
+```bash
+patch -p0 -R < /Users/oliver/Desktop/different_messages_for_blocked_users.patch
+```
+
+And that's it!
+
+There is also a Git patch creation workflow, which is described at
+. Thanks to
+[Randy Fay](http://randyfay.com) for making me aware of this, and suggesting a
+slight change to my original patch creation command.
diff --git a/sculpin/source/_posts/how-fix-vagrant-loading-wrong-virtual-machine.md b/sculpin/source/_posts/how-fix-vagrant-loading-wrong-virtual-machine.md
new file mode 100644
index 000000000..38a324dc6
--- /dev/null
+++ b/sculpin/source/_posts/how-fix-vagrant-loading-wrong-virtual-machine.md
@@ -0,0 +1,26 @@
+---
+title: How to fix Vagrant Loading the Wrong Virtual Machine
+date: 2014-10-06
+excerpt: Here are the steps that I took to fix Vagrant and point it back at the correct VM.
+tags:
+ - vagrant
+ - virtualbox
+---
+
+A few times recently, I've had instances where
+[Vagrant](https://www.vagrantup.com) seems to have forgotten which virtual
+machine it's supposed to load, probably due to renaming a project directory or
+the .vagrant directory being moved accidentally.
+
+Here are the steps that I took to fix this and point Vagrant back at the correct
+VM.
+
+1. Stop the machine from running using the `$ vagrant halt` command.
+1. Use the `$ VBoxManage list vms` command to view a list of the virtual
+ machines on your system. Note the ID of the correct VM that should be
+ loading. For example,
+ `"foo_default_1405481857614_74478" {e492bfc3-cac2-4cde-a396-e81e37e421e2}`.
+ The number within the curly brackets is the ID of the virtual machine.
+1. Within the .vagrant directory in your project (it is hidden by default),
+ update the ID within the machines/default/virtualbox/id file.
+1. Start the new VM with `$ vagrant up`.
diff --git a/sculpin/source/_posts/how-install-configure-subversion-svn-server-ubuntu.md b/sculpin/source/_posts/how-install-configure-subversion-svn-server-ubuntu.md
new file mode 100644
index 000000000..4cfac16ea
--- /dev/null
+++ b/sculpin/source/_posts/how-install-configure-subversion-svn-server-ubuntu.md
@@ -0,0 +1,178 @@
+---
+title: How to Install and Configure Subversion (SVN) Server on Ubuntu
+date: 2011-10-19
+excerpt: How to install and configure your own SVN server.
+tags:
+ - svn
+ - ubuntu
+ - version-control
+---
+
+Recently, I needed to set up a Subversion (SVN) server on a Ubuntu Linux server.
+This post is going to outline the steps taken, and the commands used, to install
+and configure the service.
+
+Note: As I was using Ubuntu, I was using the 'apt-get' command to download and
+install the software packages. If you're using a different distribution of
+Linux, then this command may be different. I'm also assuming that Apache is
+already installed.
+
+Firstly, I'm going to ensure that all of my installed packages are up to date,
+and install any available updates.
+
+```bash
+$ sudo apt-get update
+```
+
+Now, I need to download the subversion, subversion-tools and libapache2
+packages.
+
+```bash
+$ sudo apt-get install subversion subversion-tools libapache2-svn
+```
+
+These are all of the packages that are needed to run a Subversion server.
+
+## Create subversion directory
+
+Now, I need to create the directory where my repositories are going to sit. I've
+chosen this directory as I know that it's one that is accessible to my managed
+backup service.
+
+```bash
+$ sudo mkdir /home/svn
+```
+
+## Create a test repository
+
+First, I'll create a new folder in which I'll create my test project, and then
+I'll create a repository for it.
+
+```bash
+$ sudo mkdir ~/test
+$ sudo svnadmin create /home/svn/test -m 'initial project structure'
+```
+
+This will create a new repository containing the base file structure.
+
+## Adding files into the test project
+
+```bash
+$ cd ~/test
+$ mkdir trunk tags branches
+```
+
+I can now import these new directories into the test repository.
+
+```bash
+$ sudo svn import ~/test file:///home/svn/test -m 'Initial project directories'
+```
+
+This both adds and commits these new directories into the repository.
+
+In order for Apache to access the SVN repositories, the `/home/svn` directory
+needs to be owned by the same user and group that Apache runs as. In Ubuntu,
+this is usually www-data. To change the owner of a directory, use the chown
+command.
+
+```bash
+$ sudo chown -R www-data:www-data /home/svn
+```
+
+## Configuring Apache
+
+The first thing that I need to do is enable the dav_svn Apache module, using the
+a2enmod command.
+
+```bash
+$ sudo a2enmod dav_svn
+```
+
+With this enabled, now I need to modify the Apache configuration file.
+
+```bash
+$ cd /etc/apache2
+$ sudo nano apache2.conf
+```
+
+At the bottom of the file, add the following lines, and then save the file by
+pressing Ctrl+X.
+
+```
+
+ DAV svn
+ SVNParentPath /home/svn
+
+```
+
+With this saved, restart the Apache service for the changes to be applied.
+
+```bash
+sudo service apache2 restart
+```
+
+I can now browse through my test repository by opening Firefox, and navigating
+to `http://127.0.0.1/svn/test`. Here, I can now see my three directories,
+although they are currently all empty.
+
+## Securing my SVN repositories
+
+Before I start committing any files to the test repository, I want to ensure
+that only authorised users can view it - currently anyone can view the
+repository and it's contents, as well as being able to checkout and commit
+files. To do this, I'm going to require the user to enter a username and a
+password before viewing or performing any actions with the repository.
+
+Re-open apache2.conf, and replace the SVN Location information with this:
+
+```
+
+ DAV svn
+ SVNParentPath /home/svn
+ AuthType Basic
+ AuthName "My SVN Repositories"
+ AuthUserFile /etc/svn-auth
+ Require valid-user
+
+```
+
+Now I need to create the password file.
+
+```bash
+$ htpasswd -cm /etc/svn-auth oliver
+```
+
+I'm prompted to enter and confirm my password, and then my details are saved.
+The Apache service will need to be restarted again, and then the user will need
+to authenticate themselves before viewing the repositories.
+
+## Checking out the repository and commiting files
+
+For example, now want to checkout the files within my repository into a new
+directory called 'test2' within my home directory. Firstly, I need to create the
+new directory, and then I can issue the checkout command.
+
+```bash
+$ cd ~
+$ mkdir test2
+$ svn checkout http://127.0.0.1/svn/test/trunk test2
+```
+
+I'm passing the command two arguments - the first is the URL of the repository's
+trunk directory, and the second is the directory where the files are to be
+placed. As no files have been commited yet into the trunk, it appears to be
+empty - but if you perform an ls -la command, you'll see that there is a hidden
+.svn directory.
+
+Now you can start adding files into the directory. Once you've created your
+files, perform a svn add command, passing in individual filenames as further
+arguments.
+
+```bash
+$ svn add index.php
+$ svn add *
+```
+
+With all the required files added, they can be committed using
+`svn commit -m 'commit message'` command, and the server can be updated using
+the svn up command.
diff --git a/sculpin/source/_posts/how-put-your-php-application-subdirectory-another-site-nginx.md b/sculpin/source/_posts/how-put-your-php-application-subdirectory-another-site-nginx.md
new file mode 100644
index 000000000..53bdf7f4d
--- /dev/null
+++ b/sculpin/source/_posts/how-put-your-php-application-subdirectory-another-site-nginx.md
@@ -0,0 +1,29 @@
+---
+title: How to put your PHP application in a subdirectory of another site with Nginx
+date: 2018-03-12
+excerpt: How to configure Nginx to serve a PHP application from within a subdirectory of another.
+tags:
+ - nginx
+ - php
+---
+
+In January, [Chris Fidao][0] posted a video to [Servers for Hackers][1] showing
+how to put different PHP applications in different subdirectories and have them
+serving on different paths with Nginx. I’ve had to do this a few times
+previously, and it’s great to have this video as a reference.
+
+> In this video, we work through how to put your PHP application in a
+> subdirectory of another site.
+>
+> For example, we may have an application running at example.org but need a
+> second application running at example.org/blog.
+>
+> This feels like it should be simple, but it turns out to be more complex and
+> fraught with confusing Nginx configurations! To make matter worse (or,
+> perhaps, to illustrate this point), a quick Google search reveals a TON of
+> confusing, non-working examples.
+
+
+
+[0]: https://twitter.com/fideloper
+[1]: https://serversforhackers.com
diff --git a/sculpin/source/_posts/how-run-drupal-8-phpunit-tests-within-docksal-phpstorm.md b/sculpin/source/_posts/how-run-drupal-8-phpunit-tests-within-docksal-phpstorm.md
new file mode 100644
index 000000000..f9a1af149
--- /dev/null
+++ b/sculpin/source/_posts/how-run-drupal-8-phpunit-tests-within-docksal-phpstorm.md
@@ -0,0 +1,217 @@
+---
+title: How to run Drupal 8 PHPUnit Tests within Docksal from PhpStorm
+date: 2018-07-19
+excerpt: How to configure PhpStorm to run automated tests within Docksal.
+tags:
+ - docksal
+ - drupal
+ - drupal-8
+ - phpstorm
+ - phpunit
+ - testing
+promoted: true
+---
+
+I’ve recently re-watched [A Clean PHPUnit Workflow in PHPStorm][0] on
+[Laracasts][1], where Jeffrey configures PhpStorm to run tests from within the
+IDE. With Drupal 8 using PHPUnit too, I decided to try and do the same with a
+local D8 site.
+
+Though because I’m using [Docksal][4] for my local development environment
+which, at least on a Mac, runs Docker containers within a virtual machine, there
+were some additional steps needed to achieve this and to have the tests run
+within the Docksal virtual machine and using the correct containers.
+
+In this post, I’ll be using my [Drupal Testing Workshop codebase][2] as an
+example, which is based on the [Drupal Composer project][3] with some
+pre-configured Docksal configuration.
+
+This post is separated into a few different sections:
+
+- [Allow PhpStorm to connect to the CLI container](#allow-phpstorm-to-connect-to-the-cli-container)
+- [Add a new deployment server](#add-a-new-deployment-server)
+- [Configure PHP interpreter](#configuring-the-php-interpreter)
+- [Set up PHPUnit in PhpStorm](#set-up-phpunit-in-phpstorm)
+- [Running tests](#running-tests)
+
+## Allow PhpStorm to connect to the CLI container
+
+The first thing to do is to allow PhpStorm to connect to Docksal’s CLI container
+to allow it to run the tests. We can do this by exposing the container’s SSH
+port so that it’s available to the host machine and PhpStorm.
+
+As this is going to be unique to my environment, I’m going to add this to
+`.docksal/docksal-local.yml` which I have in `.gitignore`, rather than
+committing it into the repository and enforcing the same port number for
+everyone else and potentially causing conflicts.
+
+In this case I’ll expose port 22 in the container to port 2225 locally.
+
+```
+version: '2.1'
+
+services:
+ cli:
+ ports:
+ - '2225:22'
+```
+
+Once added, run `fin start` to rebuild the project’s containers.
+
+You can verify the change by running `fin ps` and you should see something like
+`0.0.0.0:2225->22/tcp` under Ports for the CLI container.
+
+## Add a new Deployment server
+
+Now PhpStorm can connect to Docksal, I can configure it to do so by adding a new
+deployment server.
+
+- Open PhpStorm’s preferences, and go to 'Build, Execution, Deployment' and
+ 'Deployment'.
+- Click 'Add' to configure a new deployment server.
+- Enter a name like 'Docksal', and select SFTP as the server type.
+
+{.with-border
+.sm:max-w-sm}
+
+### Connection settings
+
+On the Connection tab:
+
+- Enter your domain name - e.g. `drupaltest.docksal` as the SFTP host. This will
+ resolve to the correct local IP address.
+- Enter the exposed port for the CLI container that was entered in the previous
+ step.
+- Enter "docker" as both the username and password.
+
+You should now be able to click "Test SFTP connection" and get a successfully
+connected confirmation message.
+
+
+
+### Mapping settings
+
+On the Mappings tab, add `/var/www` as the deployment path so that PhpStorm is
+looking in the correct place for the project code.
+
+{.with-border}
+
+## Configuring the PHP Interpreter
+
+In Preferences, search for 'PHP' within 'Languages & Frameworks', and add a new
+CLI interpreter.
+
+{.with-border}
+
+In this case I’ve called it 'Docksal PHP 7.1', used the Docksal deployment
+configuration, and set the path to the PHP executable to `/usr/local/bin/php`
+(the same path that we would get if we ran `fin run which php`). You should see
+both the deployment host URL displayed as well as the remote PHP version and
+configuration filenames.
+
+{.with-border}
+
+This can now be selected as the CLI interpreter for this project.
+
+{.with-border}
+
+## Set up PHPUnit in PhpStorm
+
+In Preferences, search for 'Test Frameworks' and add a new framework.
+
+{.with-border}
+
+Select 'PHPUnit by Remote Interpreter' and then the 'Docksal PHP 7.1' that we
+created in the last step.
+
+Select 'Use Composer autoloader' for the PHPUnit library setting so that
+PhpStorm uses the version required by Drupal core, and set the path to
+`/var/www/vendor/autoload.php`.
+
+Also specify the path to the default (phpunit.xml) configuration file. This will
+depend on how your project is structured, in this case it’s at
+`/var/www/web/core/phpunit.xml`.
+
+{.with-border}
+
+## Running tests
+
+With PHPUnit configured, next to each test class and method, you can see a green
+circle (or a red one if the test failed the last run). You can click the circle
+and select to run that test class or method. You can also right-click
+directories in the project sidebar to run all of the tests within that
+directory.
+
+{.with-border}
+
+When the tests start running, a new tool window will open that shows you all of
+the selected tests, how long each test took to run and whether it passed or
+failed. You can also see the CLI output from PHPUnit itself next to it.
+
+{.with-border}
+
+From here, you also have the ability to re-run all of the tests, as well as a
+single test method or a specific test class.
+
+Any test failures are shown here too, and for some failures like differences
+between two arrays you can use PhpStorm’s internal comparison tools to view the
+difference rather than needing to do so on the command line.
+
+{.with-border}
+
+{.with-border
+.sm:max-w-md}
+
+### Keyboard shortcuts
+
+As per the video, I’ve also added some keyboard shortcuts to my keymap, so I can
+press ⌘T to run the current test method or class that I’m in, and ⇧⌘T to re-run
+the last test.
+
+{.with-border}
+
+{.with-border}
+
+### Database issues
+
+When running functional tests that require a database, I was getting a database
+error like the one below:
+
+> Drupal\Core\Installer\Exception\InstallerException : Resolve all issues below
+> to continue the installation. For help configuring your database server, see
+> the installation
+> handbook, or contact your hosting provider.
+
+In `settings.php`, I check for the presence of `/.dockerenv` to ensure that
+we’re inside a Docker container, as well as the presence of a
+`docksal.settings.yml` file. The latter contains the database credentials for
+Drupal to connect to the MySQL database.
+
+```php
+if (file_exists('/.dockerenv') && file_exists(__DIR__ . '/docksal.settings.php')) {
+ include __DIR__ . '/docksal.settings.php';
+}
+```
+
+In order to get the tests to run, I had to prevent this file from being loaded
+during the tests. I can do this by checking that `SIMPLETEST_DB`, an environment
+variable set in phpunit.xml is not present.
+
+```php
+// settings.php
+
+if (file_exists('/.dockerenv') && file_exists(__DIR__ . '/docksal.settings.php') && !getenv('SIMPLETEST_DB')) {
+ include __DIR__ . '/docksal.settings.php';
+}
+```
+
+With this extra condition, the database credentials are loaded correctly and the
+functional tests run properly.
+
+Happy testing!
+
+[0]: https://laracasts.com/series/php-bits/episodes/2
+[1]: https://laracasts.com
+[2]: https://github.com/opdavies/drupal-testing-workshop
+[3]: https://github.com/drupal-composer/drupal-project
+[4]: https://docksal.io
diff --git a/sculpin/source/_posts/how-use-environment-variables-your-drupal-settings-docksal.md b/sculpin/source/_posts/how-use-environment-variables-your-drupal-settings-docksal.md
new file mode 100644
index 000000000..54b4431cc
--- /dev/null
+++ b/sculpin/source/_posts/how-use-environment-variables-your-drupal-settings-docksal.md
@@ -0,0 +1,92 @@
+---
+title: How to Use Environment Variables for your Drupal Settings with Docksal
+date: 2018-06-04
+excerpt: How to leverage environment variables with Drupal and Docksal.
+tags:
+ - docksal
+ - drupal
+ - drupal-planet
+---
+
+Within the [Docksal documentation for Drupal settings][0], the example database
+settings include hard-coded credentials to connect to the Drupal database. For
+example, within a `settings.php` file, you could add this:
+
+```php
+$databases['default']['default'] = [
+ 'driver' => 'mysql',
+ 'host' => 'db',
+ 'database' => 'myproject_db',
+ 'username' => 'myproject_user',
+ 'password' => 'myproject_pass',
+];
+```
+
+Whilst this is fine, it does mean that there is duplication in the codebase as
+the database credentials can also be added as environment variations within
+`.docksal/docksal.env` - this is definitely the case if you want to use a custom
+database name, for example.
+
+Also if one of these values were to change, then Drupal wouldn't be aware of
+that and would no longer be able to connect to the database.
+
+It also means that the file can’t simply be re-used on another project as it
+contains project-specific credentials.
+
+We can improve this by using the environment variables within the settings file.
+
+The relevant environment variables are `MYSQL_DATABASE` for the database name,
+and `MYSQL_USER` and `MYSQL_PASSWORD` for the MySQL username and password. These
+can be set in `.docksal/docksal.env`, and will need to be present for this to
+work.
+
+For example:
+
+```
+DOCKSAL_STACK=default
+MYSQL_DATABASE=myproject_db
+MYSQL_USER=myproject_user
+MYSQL_PASSWORD=myproject_pass
+```
+
+With these in place, they can be referenced within the settings file using the
+`getenv()` function.
+
+```
+$databases['default']['default'] = [
+ 'driver' => 'mysql',
+ 'host' => 'db',
+ 'database' => getenv('MYSQL_DATABASE'),
+ 'username' => getenv('MYSQL_USER'),
+ 'password' => getenv('MYSQL_PASSWORD'),
+];
+```
+
+Now the credentials are no longer duplicated, and the latest values from the
+environment variables will always be used.
+
+However, you may see a message like this when you try and load the site:
+
+> Drupal\Core\Database\DatabaseAccessDeniedException: SQLSTATE[HY000][1045]
+> Access denied for user ''@'172.19.0.4' (using password: NO) in
+> /var/www/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php on line 156
+
+If you see this, the environment variables aren’t being passed into Docksal’s
+`cli` container, so the values are not being populated. To enable them, edit
+`.docksal/docksal.yml` and add `MYSQL_DATABASE`, `MYSQL_PASSWORD` and
+`MYSQL_USER` to the `environment` section of the `cli` service.
+
+```yaml
+version: '2.1'
+services:
+ cli:
+ environment:
+ - MYSQL_DATABASE
+ - MYSQL_PASSWORD
+ - MYSQL_USER
+```
+
+After changing this file, run `fin start` to rebuild the project containers and
+try to load the site again.
+
+[0]: https://docksal.readthedocs.io/en/master/advanced/drupal-settings
diff --git a/sculpin/source/_posts/ignoring-phpcs-sniffs-phpunit-tests.md b/sculpin/source/_posts/ignoring-phpcs-sniffs-phpunit-tests.md
new file mode 100644
index 000000000..d5ec3334a
--- /dev/null
+++ b/sculpin/source/_posts/ignoring-phpcs-sniffs-phpunit-tests.md
@@ -0,0 +1,77 @@
+---
+title: Ignoring PHPCS sniffs for PHPUnit tests
+excerpt: How to exclude certain PHPCS sniffs within your PHPUnit tests, so that you can write your tests methods how you'd like without getting coding standards errors.
+tags:
+ - drupal
+ - drupal-planet
+ - php
+ - phpunit
+date: 2021-01-04
+---
+
+**Note:** This post is written with a Drupal context, but applies to any PHP project.
+
+This is a test that I wrote recently, which uses the camel case method name that is recommended by the Drupal and PSR-2 coding standards:
+
+```php
+public function testThatPathAliasesAreNotTransferredToTheNewLanguageWhenOneIsAdded(): void {
+ // ...
+}
+```
+It has a long method name that describes the test that is being run. However, it's quite hard to read. Generally, I prefer to write tests like this, using the `@test` annotation (so that I can remove the `test` prefix) and snake case method names:
+
+```php
+/** @test */
+public function path_aliases_are_not_transferred_to_the_new_language_when_one_is_added(): void {
+ // ...
+}
+```
+
+This to me is a lot easier to read, particularly for long and descriptive test method names, and is commonly used within parts of the PHP community.
+
+This approach, however, can result in some errors from PHPCS:
+
+- The open comment tag must be the only content on the line
+- Public method name "DefinedLanguageNodeTest::path_aliases_are_not_transferred_to_the_new_language_when_one_is_added" is not in lowerCamel format
+
+We can avoid the errors by excluding the files when running PHPCS, or modifying rules within phpcs.xml (or phpcs.xml.dist) file to change the severity value for the rules. These approaches would mean either ignoring all PHPCS sniffs within the test files or ignoring some checks within all files, neither of which is an ideal approach.
+
+## Ignoring whole or partial files
+
+We can tell PHPCS to ignore whole or partial files by adding comments - there's [an example of this](https://git.drupalcode.org/project/drupal/-/blob/ad34608ab0bb115c53f4aaa0573c30dd8dc5b23a/sites/default/default.settings.php#L3 "Drupal's default.settings.php file with a 'coding standards ignore' comment") at the top of `default.settings.php` file:
+
+```php
+// @codingStandardsIgnoreFile
+```
+
+The `@codingStandards` syntax, however, is deprecated and will be removed in PHP_CodeSniffer version 4.0. The new syntax to do this is:
+
+```php
+// phpcs:ignoreFile
+```
+
+As well as `phpcs:ignoreFile` which ignores all of the sniffs in an entire file, there are also commands to disable and re-enable PHPCS at different points within the same file:
+
+```php
+// Stop PHPCS checking.
+// phpcs:disable
+
+// Start PHPCS checking.
+// phpcs:enable
+```
+
+## Disabling specific rules in a file
+
+As well as excluding a section of code from checks, with `phpcs:ignore` you can also specify a list of sniffs to ignore. For example:
+
+```php
+// phpcs:disable Drupal.Commenting.DocComment, Drupal.NamingConventions.ValidFunctionName
+```
+
+By adding this to the top of the test class, these specific sniffs will be ignored so no errors will be reported, and any other sniffs will continue to work as normal.
+
+If you're unsure what the names of the sniffs are that you want to ignore, add `-s` to the PHPCS command to have it include the sniff names in its output.
+
+For more information on ignoring files, folders, part of files, and limiting results, see the [Advanced Usage page for the PHP CodeSniffer project](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Advanced-Usage) on GitHub.
+
+You can also see this being used in [some of the tests for this website](https://github.com/opdavies/oliverdavies-uk/tree/production/web/modules/custom/blog/tests/src/Kernel).
diff --git a/sculpin/source/_posts/imagefield-import-archive.md b/sculpin/source/_posts/imagefield-import-archive.md
new file mode 100644
index 000000000..2a121cc7c
--- /dev/null
+++ b/sculpin/source/_posts/imagefield-import-archive.md
@@ -0,0 +1,35 @@
+---
+title: Imagefield Import Archive
+date: 2011-05-23
+excerpt: I've finally uploaded my first module onto Drupal.org!
+tags:
+ - drupal-planet
+ - imagefield-import
+---
+
+I've finally uploaded my first module onto Drupal.org!
+
+I've written many custom modules, although the vast majority of them are either
+small tweaks for my own sites, or company/site-specific modules that wouldn't be
+good to anyone else, so there would be nothing achieved by contributing them
+back to the community. Previously, I've blogged about the
+[Imagefield Import](http://drupal.org/project/imagefield_import) module - a
+module that I use on a number of sites, both personal and for clients - and what
+I've looked for lately is for a way to speed up the process again. Gathering my
+images together and manually copying them into the import directory takes time -
+especially if I'm working somewhere with a slow Internet connection and I'm
+FTP-ing the images into the directory. Also, it's not always the easiest
+solution for site users - especially non-technical ones.
+
+So, I wrote the Imagefield Import Archive module. Including comments, the module
+contains 123 lines, and builds upon the existing functionality of the Imagefield
+Import module by adding the ability for the user to upload a .zip archive of
+images. The archive is then moved into the specified import directory and
+unzipped before being deleted, and the user is directed straight to the standard
+Imagefield Import page where their images are waiting to be imported, just as
+usual.
+
+The module is currently a
+[sandbox project](http://drupal.org/sandbox/opdavies/1165110) on Drupal.org,
+although I have applied for full project access so that I can be added as a
+fully-fledged downloadable module.
diff --git a/sculpin/source/_posts/improve-jpg-quality-imagecache-and-imageapi.md b/sculpin/source/_posts/improve-jpg-quality-imagecache-and-imageapi.md
new file mode 100644
index 000000000..2fc17e93a
--- /dev/null
+++ b/sculpin/source/_posts/improve-jpg-quality-imagecache-and-imageapi.md
@@ -0,0 +1,25 @@
+---
+title: Improve JPG Quality in Imagecache and ImageAPI
+date: 2010-06-02
+excerpt: How to fix the quality of uploaded images in Drupal.
+tags:
+ - drupal-planet
+ - drupal-6
+ - imagecache
+---
+
+Whilst uploading images for my Projects and Testimonials sections, I noticed
+that the Imagecache-scaled images weren't as high a quality the originals on my
+Mac. I did some searching online and found out that, by default, Drupal
+resamples uploaded jpgs to 75% of their original quality.
+
+To increase the quality of your images, change the setting in the two following
+places:
+
+- admin/settings/imageapi/config
+- admin/settings/image-toolkit
+
+The first one is for ImageAPI. Primarily, this means Imagecache presets. The
+second one is for core's image.inc. This is used for resizing profile pictures
+in core, and some contrib modules. Once changed, I did have to flush each of the
+Imagecache presets (admin/dist/imagecache) for the changes to take effect.
diff --git a/sculpin/source/_posts/include-css-fonts-using-sass-each-loop.md b/sculpin/source/_posts/include-css-fonts-using-sass-each-loop.md
new file mode 100644
index 000000000..f3e07f3df
--- /dev/null
+++ b/sculpin/source/_posts/include-css-fonts-using-sass-each-loop.md
@@ -0,0 +1,67 @@
+---
+title: Include CSS Fonts by Using a SASS each Loop
+date: 2014-11-18
+excerpt: How to use an SASS each loop to easily add multiple fonts to your CSS.
+tags:
+ - compass
+ - drupal-planet
+ - fonts
+ - sass
+---
+
+How to use an @each loop in SASS to quickly include multiple font files within
+your stylesheet.
+
+Using a file structure similar to this, organise your font files into
+directories, using the the font name for both the directory name and for the
+file names.
+
+```bash
+.
+├── FuturaBold
+│ ├── FuturaBold.eot
+│ ├── FuturaBold.svg
+│ ├── FuturaBold.ttf
+│ └── FuturaBold.woff
+├── FuturaBoldItalic
+│ ├── FuturaBoldItalic.eot
+│ ├── FuturaBoldItalic.svg
+│ ├── FuturaBoldItalic.ttf
+│ └── FuturaBoldItalic.woff
+├── FuturaBook
+│ ├── FuturaBook.eot
+│ ├── FuturaBook.svg
+│ ├── FuturaBook.ttf
+│ └── FuturaBook.woff
+├── FuturaItalic
+│ ├── FuturaItalic.eot
+│ ├── FuturaItalic.svg
+│ ├── FuturaItalic.ttf
+│ └── FuturaItalic.woff
+```
+
+Within your SASS file, start an `@each` loop, listing the names of the fonts. In
+the same way as PHP's `foreach` loop, each font name will get looped through
+using the `$family` variable and then compiled into CSS.
+
+```scss
+@each $family in FuturaBook, FuturaBold, FuturaBoldItalic, FuturaItalic {
+ @font-face {
+ font-family: #{$family};
+ src: url('../fonts/#{$family}/#{$family}.eot');
+ src: url('../fonts/#{$family}/#{$family}.eot?#iefix') format('embedded-opentype'),
+ url('../fonts/#{$family}/#{$family}.woff') format('woff'),
+ url('../fonts/#{$family}/#{$family}.ttf') format('truetype'),
+ url('../fonts/#{$family}/#{$family}.svg##{$family}') format('svg');
+ font-weight: normal;
+ font-style: normal;
+ }
+}
+```
+
+When the CSS has been compiled, you can then use in your CSS in the standard
+way.
+
+```scss
+font-family: "FuturaBook";
+```
diff --git a/sculpin/source/_posts/include-environment-specific-settings-files-pantheon.md b/sculpin/source/_posts/include-environment-specific-settings-files-pantheon.md
new file mode 100644
index 000000000..237a248e7
--- /dev/null
+++ b/sculpin/source/_posts/include-environment-specific-settings-files-pantheon.md
@@ -0,0 +1,100 @@
+---
+title: Include environment-specific settings files on Pantheon
+date: 2014-11-27
+excerpt: How to load a different settings file per environment on Pantheon.
+tags:
+ - drupal
+ - drupal-planet
+ - pantheon
+ - settings.php
+---
+
+I was recently doing some work on a site hosted on
+[Pantheon](http://getpantheon.com) and came across an issue, for which part of
+the suggested fix was to ensure that the `$base_url` variable was explicitly
+defined within settings.php (this is also best practice on all Drupal sites).
+
+The way that was recommended was by using a `switch()` function based on
+Pantheon's environment variable. For example:
+
+```php
+switch ($_SERVER['PANTHEON_ENVIRONMENT']) {
+ case 'dev':
+ // Development environment.
+ $base_url = 'dev-my-site.gotpantheon.com';
+ break;
+
+
+ case 'test':
+ // Testing environment.
+ $base_url = 'test-my-site.gotpantheon.com';
+ break;
+
+
+ case 'live':
+ // Production environment.
+ $base_url = 'live-my-site.gotpantheon.com';
+ break;
+}
+```
+
+Whilst this works, it doesn't conform to the DRY (don't repeat yourself)
+principle and means that you also might get a rather long and complicated
+settings file, especially when you start using multiple switches and checking
+for the value of the environment multiple times.
+
+My alternative solution to this is to include an environment-specific settings
+file.
+
+To do this, add the following code to the bottom of settings.php:
+
+```php
+if (isset($_SERVER['PANTHEON_ENVIRONMENT'])) {
+ if ($_SERVER['PANTHEON_ENVIRONMENT'] != 'live') {
+ // You can still add things here, for example to apply to all sites apart
+ // from production. Mail reroutes, caching settings etc.
+ }
+
+ // Include an environment-specific settings file, for example
+ // settings.dev.php, if one exists.
+ $environment_settings = __DIR__ . '/settings.' . $_SERVER['PANTHEON_ENVIRONMENT'] . '.php';
+ if (file_exists($environment_settings)) {
+ include $environment_settings;
+ }
+}
+```
+
+This means that rather than having one long file, each environment has it's own
+dedicated settings file that contains it's own additional configuration. This is
+much easier to read and make changes to, and also means that less code is loaded
+and parsed by PHP. Settings that apply to all environments are still added to
+settings.php.
+
+Below this, I also include a
+[similar piece of code](/blog/include-local-drupal-settings-file-environment-configuration-and-overrides/)
+to include a settings.local.php file. The settings.php file then gets committed
+into the [Git](http://git-scm.com) repository.
+
+Within the sites/default directory, I also include an example file
+(example.settings.env.php) for reference. This is duplicated, renamed and
+populated accordingly.
+
+```php
+. This post was accurate at the time of
+writing, whereas the documentation page will be kept up to date with any future
+changes._
+
+## Initial configuration
+
+### Download the Library
+
+The library can be downloaded directly from GitHub, and should be placed within
+you _sites/all/libraries/nomensa_amp_ directory.
+
+```bash
+drush dl libraries nomensa_amp
+git clone https://github.com/nomensa/Accessible-Media-Player sites/all/libraries/nomensa_amp
+cd sites/all/libraries/nomensa_amp
+rm -rf Accessible-media-player_2.0_documentation.pdf example/ README.md
+drush en -y nomensa_amp
+```
+
+### Configure the Module
+
+Configure the module at admin/config/media/nomensa-amp and enable the
+players that you want to use.
+
+## Adding videos
+
+Within your content add links to your videos. For example:
+
+### YouTube
+
+```html
+Checking colour contrast
+```
+
+### Vimeo
+
+```html
+Screen readers are strange, when you're a stranger by Leonie Watson
+```
+
+## Adding captions
+
+The best way that I can suggest to do this is to use a File field to upload your
+captions file:
+
+1. Add a File field to your content type;
+1. On your page upload the captions file.
+1. Right-click the uploaded file, copy the link location, and use this for the
+ path to your captions file.
+
+For example:
+
+```html
+Checking colour contrastCaptions for Checking Colour Contrast
+```
+
+## Screencast
+
+
+
+
diff --git a/sculpin/source/_posts/installing-nagios-centos.md b/sculpin/source/_posts/installing-nagios-centos.md
new file mode 100644
index 000000000..75bf01702
--- /dev/null
+++ b/sculpin/source/_posts/installing-nagios-centos.md
@@ -0,0 +1,15 @@
+---
+title: Installing Nagios on CentOS
+date: 2012-04-17
+excerpt: How to install Nagios on CentOS.
+tags:
+ - nagios
+ - centos
+ - linux
+---
+
+A great post details that details the steps needed to install
+[Nagios](http://nagios.org) - a popular open source system and network
+monitoring software application - on CentOS.
+
+
diff --git a/sculpin/source/_posts/interview-drupal-expert-code-enigma.md b/sculpin/source/_posts/interview-drupal-expert-code-enigma.md
new file mode 100644
index 000000000..e4063d1e0
--- /dev/null
+++ b/sculpin/source/_posts/interview-drupal-expert-code-enigma.md
@@ -0,0 +1,13 @@
+---
+title: Interview with a Drupal Expert (with Code Enigma)
+excerpt: I recently did an interview with Code Enigma for their blog.
+tags:
+ - drupal
+ - interview
+ - personal
+date: 2020-08-31
+---
+
+I recently did an interview with Drupal and PHP agency [Code Enigma](https://www.codeenigma.com) for their blog, in which we talked about getting started in the Drupal community, [working for 10 years full-time with Drupal and PHP](/blog/10-years-working-full-time-drupal-php), companies adopting open source technologies, and my favourite Drupal events.
+
+To read it, go to [the Code Enigma blog](https://blog.codeenigma.com/interview-with-a-drupal-expert-9fcd8e0fad28 "'Interview with a Drupal Expert' on the Code Enigma blog").
diff --git a/sculpin/source/_posts/introducing-drupal-distribution-meetups.md b/sculpin/source/_posts/introducing-drupal-distribution-meetups.md
new file mode 100644
index 000000000..f796a7669
--- /dev/null
+++ b/sculpin/source/_posts/introducing-drupal-distribution-meetups.md
@@ -0,0 +1,29 @@
+---
+title: Introducing a Drupal distribution for meetup websites
+excerpt: I'm starting development on a new Drupal distribution for building meetup group websites.
+tags:
+ - drupal
+ - drupal-9
+ - drupal-distribution
+ - drupal-meetup-distribution
+ - personal
+ - php
+ - php-south-wales
+date: 2021-10-07
+---
+
+I'm the current organiser of the [PHP South Wales user group](https://www.phpsouthwales.uk) and built the current website with Drupal 8, which I started in 2019.
+
+There are some basic pages, but also functionality to display upcoming and past events, show current sponsors, and to populate event data from Meetup.com - functionality that could needed by other meetup groups for their websites - such as other PHP and Drupal user groups that I've organised and attended.
+
+Inspired by other Drupal distributions like [LocalGov](https://www.drupal.org/project/localgov), I've decided to refactor the current site into a reusable distribution that other meetup groups can use. It's not intended to be a clone of Meetup.com, but to be used for a website for a single meetup group to show their events and showcase their own community.
+
+This also means that any new functionality can be added straight to the distribution and immediately available for everyone.
+
+I've created a [project page on Drupal.org][drupalorg] and a [Drupal Meetup organisation on GitHub][github] which contains repositories for the distribution as well as a project template that are pushed to [Packagist][packagist] to that they can be installed with Composer - e.g. `composer create-project --stability dev drupal-meetup/drupal-meetup-project my-new-meetup`.
+
+This seems like a good opportunity to do some more Drupal contribution and may benefit others too who want to build their own meetup group websites.
+
+[drupalorg]: https://www.drupal.org/project/meetup
+[github]: https://github.com/drupal-meetup
+[packagist]: https://packagist.org/packages/opdavies/?query=drupal-meetup
diff --git a/sculpin/source/_posts/introducing-the-drupal-meetups-twitterbot.md b/sculpin/source/_posts/introducing-the-drupal-meetups-twitterbot.md
new file mode 100644
index 000000000..cf12e4c10
--- /dev/null
+++ b/sculpin/source/_posts/introducing-the-drupal-meetups-twitterbot.md
@@ -0,0 +1,29 @@
+---
+title: Introducing the Drupal Meetups Twitterbot
+date: 2017-06-09
+excerpt: I’ve written a twitterbot for promoting Drupal meetups.
+tags:
+ - php
+ - twitter
+---
+
+

+
+The [Drupal Meetups Twitterbot][0] is a small project that I worked on a few
+months ago, but hadn't got around to promoting yet. It’s intention is to provide
+[one Twitter account][1] where people can get the up to date news from various
+Drupal meetups.
+
+It works by having a whitelist of [Twitter accounts and hashtags][2] to search
+for, uses [Codebird][3] to query the Twitter API and retweets any matching
+tweets on a scheduled basis.
+
+If you would like your meetup group to be added to the list of searched
+accounts, please [open an issue][4] on the GitHub repo.
+
+[0]: https://github.com/opdavies/drupal-meetups-twitterbot
+[1]: https://twitter.com/drupal_meetups
+[2]:
+ https://github.com/opdavies/drupal-meetups-twitterbot/blob/master/bootstrap/config.php
+[3]: https://www.jublo.net/projects/codebird/php
+[4]: https://github.com/opdavies/drupal-meetups-twitterbot/issues/new
diff --git a/sculpin/source/_posts/leaving-nomensa-joining-precedent.md b/sculpin/source/_posts/leaving-nomensa-joining-precedent.md
new file mode 100644
index 000000000..a5b3c45b3
--- /dev/null
+++ b/sculpin/source/_posts/leaving-nomensa-joining-precedent.md
@@ -0,0 +1,32 @@
+---
+title: Leaving Nomensa, Joining Precedent
+date: 2013-04-20
+excerpt: Yesterday was my last day working at Nomensa. Next week, I'll be starting as a Senior Developer at Precedent.
+tags:
+ - nomensa
+ - personal
+ - precedent
+---
+
+Yesterday was my last day working at
+[Nomensa](http://www.nomensa.com 'Nomensa'). Next week, I'll be starting as a
+Senior Developer at [Precedent](http://www.precedent.co.uk 'Precedent').
+
+The last 14 months that I've been working at Nomensa have been absolutely
+fantastic, and had allowed me to work on some great projects for great clients -
+mainly [unionlearn](http://www.unionlearn.org 'unionlearn') and
+[Digital Theatre Plus](http://www.digitaltheatreplus.com 'Digital Theatre Plus').
+I've learned so much about accessibility and web standards, and have pretty much
+changed my whole approach to front-end development to accommodate best
+practices. I've also been involved with the Drupal Accessibility group since
+starting at Nomensa, and have written several accessibility-focused Drupal
+modules, including the
+[Nomensa Accessible Media Player](http://drupal.org/project/nomensa_amp 'The Nomensa Accessible Media Player Drupal module')
+module and the
+[Accessibility Checklist](http://drupal.org/project/a11y_checklist 'The accessibility checklist for Drupal').
+I'll definitely be continuing my interest in accessibility, championing best
+practices, and incorporating it into my future work wherever possible.
+
+With that all said, I'm really looking forward to starting my new role at
+Precedent, tackling some new challenges, and I'm sure that it'll be as great a
+place to work as Nomensa was.
diff --git a/sculpin/source/_posts/live-blogging-symfonylive-london-2019.md b/sculpin/source/_posts/live-blogging-symfonylive-london-2019.md
new file mode 100644
index 000000000..1cdc3a9ad
--- /dev/null
+++ b/sculpin/source/_posts/live-blogging-symfonylive-london-2019.md
@@ -0,0 +1,713 @@
+---
+title: Live Blogging From SymfonyLive London 2019
+date: 2019-09-13
+tags:
+ - conference
+ - php
+ - symfony
+ - symfonylive
+---
+
+Inspired by [Matt Stauffer](https://twitter.com/stauffermatt)'s
+[live blogging of the keynote](https://mattstauffer.com/blog/introducing-laravel-vapor)
+at Laracon US, I’m going to do the same for the sessions that I’m attending at
+[SymfonyLive London 2019](https://london2019.live.symfony.com)...
+
+## Keynote (Back to the basics)
+
+**Embrace the Linux philosophy**
+
+- How we grow the Symfony ecosystem. Built abstracts.
+- HttpFoundation, HttpKernel
+- Moved to infrastructure
+- A few abstractions on top of PHP. Improved versions of PHP functions (`dump`
+ and `var_dump`)
+- Started a add higher level abstractions (e.g. Mailer), built on the lower
+ ones.
+- Recently worked on PHPUnit assertions. Mailer in Symony 4.4. Can test if an
+ email is sent or queued
+
+**Building flexible high-level abstractions on top of low-level ones**
+
+### What's next?
+
+- Mailer announced in London last year. New component.
+- System emails? e.g. new customer, new invoice.
+- Symfony Mailer = Built-in responsive, flexible, and generic system emails
+ - Twig with TwigExtraBundle
+ - Twig `inky-extra` package (Twig 1.12+)
+ - Zurb Foundation for Emails CSS stylesheet
+ - Twig `cssinliner-extra` package (Twig 1.12+)
+ - Optimised Twig layouts
+- `SystemEmail` class extends templated email
+- Can set importance,
+- Customisable
+- Always trying to keep flexible, so things can be overidden and customised
+
+### Sending SMS messages
+
+- new `Texter` and `SmsMessage` class for sending SMS messages
+- Same abstraction as emails, but for SMS messages
+- Based on HttpClient + Symfony Messenger and third-party providers (Twilio and
+ Nexmo) `twilio://` and `nemxo://`
+- Can set via transport `$sms->setTransport('nexmo')`
+- Extend the `SystemEmail` and do what you want
+- Failover
+
+### Sending Messages
+
+- Create `ChatMessage`
+- Telegram and Slack
+- `$message->setTransport('telegram')`, `$bus->dispatch($message)`
+- Send to Slack **and** Telegram
+- `SlackOptions` and `TelegramOptions` for adding emojis etc
+- Common transport layer `TransportInterface`, `MessageInterface`
+- Failover - e.g. if Twilio is down, send to Telegram
+
+### New component - SymfonyNotifier
+
+- Channels - email, SMS, chat
+- Transport, slack, telegram, twilio
+- Create a notification, arguments are message and transports (array)
+- Receiver
+- Customise notifications, `InvoiceNotification` extends `Notification`.
+ `getChannels`
+ - Override default rendering
+ - `ChatNotificationInterface` - `asChatMessage()`
+- Semantic configuration
+ - `composer req twilio-notifier telegram-notifier`
+- Channels
+ - Mailer
+ - Chatter
+ - Texter
+ - Browser
+ - Pusher (iOS, Android, Desktop native notifications)
+ - Database (web notification centre)
+ - **A unified way to notify Users via a unified Transport layer**
+- Each integration is only 40 lines of code
+
+### What about a SystemNotification?
+
+- Autoconfigured channels
+- `new SystemNotification`, `Notifier::getSystemReceivers`
+- Importance, automatically configures channels
+- Different channels based on importance
+- `ExceptionNotification` - get email with stack trace attached
+
+Notifier
+
+- send messages via a unified api
+- send to one or many receivers
+- Default configu or custom one
+
+### How can we leverage this new infrastructure?
+
+- `Monolog NotifierHandler` - triggered on `Error` level logs
+- Uses notified channel configuration
+- Converts Error level logs to importance levels
+- Configurablelike other Notifications
+- 40 lines of code
+- Failed Messages Listener - 10 lines of glue code
+
+- **Experimental component in 5.0**
+- Can't in in 4.4 as it's a LTS version
+- First time an experimental component is added
+- Stable in 5.1
+
+## Queues, busses and the Messenger component (Tobias Nyholm)
+
+- Stack is top and buttom - Last-in, first-out
+- Queue is back and front - last in, first out
+
+### 2013
+
+- Using Symfony, used 40 or 50 bundles in a project - too much information!
+- Used to copy and paste, duplicate a lot of code
+- Testing your controllers - controllers as services?
+- Controllers are 'comfortable'
+- Tried adding `CurrentUserProvider` service to core, should be passed as an
+ argument. Cannot test.
+- 'Having Symfony all over the place wasn't the best thing' - when to framework
+ (Matthias Noback)
+ - Hexagonal architecture
+ - Keep your kernel away from infrastructure. Let the framework handle the
+ infrastructure.
+- Controller -> Command -> Command Bus -> `CommandHandler`
+
+#### What did we win?
+
+- Can leverage Middleware with a command bus
+- Queues as a service (RabbitMQ)
+- Work queue - one producer, multiple consumers
+- Queues should be durable - messages are also stored on disk, consumers should
+ acknowledge a message once a message is handled
+- Publish/subscribe
+ - Producer -> Fanout/direct with routing (multiple queues) -> multiple
+ consumers
+- Topics - wildcards
+
+### 2016
+
+- New intern. Understand everything, 'just PHP'. Plain PHP application, not
+ 'scary Symfony'
+
+### Symfony Messenger
+
+- `composer req symfony/messager` - best MessageBus implementation
+- Message -> Message bus -> Message handler
+- Message is a plain PHP class
+- Handler is a normal PHP class which is invokable
+- `messenger:message_hander` tag in config
+- Autowire with `MessageHandlerInterface`
+- What if it takes 20 seconds to send a message? Use asynchronous.
+- Transports as middleware (needs sender, receiver, configurable with DSN,
+ encode/decode). `MESSENGER_DSN` added to `.env`
+- Start consumer with `bin/console messager:consume-messages`. Time limit with
+ `--time-limit 300`
+- PHP Enqueue - production ready, battle-tested messaging solution for PHP
+
+### Issues
+
+- Transformers, takes an object and transforms into an array -
+ `FooTransformer implements TransformerInterface`.
+- Don't break other apps by changing the payload.
+
+#### Multiple buses
+
+- Command bus, query bus, event bus
+- Separate actions from reactions
+
+#### Envelope
+
+- Stamps for metadata - has the item been on the queue already?
+
+#### Failures
+
+- Requeue, different queue or same queue after a period of time
+- Failed queue 1 every minute, failed queue 2 every hour - temporary glitches or
+ a bug?
+
+#### Creating entities
+
+- What if two users registered at the same tiem? Use uuids rather than IDs.
+- Symfony validation - can be used on messages, not just forms.
+
+- Cache everything
+
+ - Option 1: HTTP request -> Thin app (gets responses from Redis) -> POST to
+ queue. Every GET request would warm cache
+ - Option 2: HTTP request -> Thin app -> return 200 response -> pass to workers
+
+- Tip: put Command and CommandHandlers in the same directory
+
+## HttpClient (Nicolas Grekas)
+
+- new symfony component, released in may
+- Httpclient contracts, separate package that contains interfaces
+ - Symfony
+ - PHP-FIG
+ - Httplug
+- `HttpClient::create()`. `$client->get()`
+- JSON decoded with error handling
+- Used on symfony.com website (#1391). Replaces Guzzle `Client` for
+ `HttpClientInterface`
+- Object is stateless, Guzzle is not. Doesn't handle cookies, cookies are state
+- Remove boilerplate - use `toArray()`
+- Options as third argument - array of headers, similar to Guzzle
+
+### What can we do with the Response?
+
+- `getStatusCode(): int`
+- `getHeaders(): array`
+- `getContent(): string`
+- `toArray(): array`
+- `cancel(): void`
+- `getInfo(): array` - metadata
+- Everything is lazy!
+- 80% of use-cases covered
+
+### What about PSR-18?
+
+- Decorator/adapter to change to PSR compatible
+- Same for Httplug
+
+### What about the remaining 20%?
+
+- Options are part of the abstraction, not the implementation
+
+#### Some of the options
+
+- `timeout` - control inactivity periods
+- `proxy` - get through a http proxy
+- `on_progress` - display a progress bar / build a scoped client
+- `base_url` - resolve relative URLS / build a scoped client
+- `resolve` - protect webhooks against calls to internal endpoints
+- `max_redirects` - disable or limit redirects
+
+- Robust and failsafe by default
+
+- Streamable uploads - `$mimeParts->toIterable()`.
+- donwload a file
+
+ ```php
+ foreach ($client->stream($response) as $chunk) {
+ // ...
+ }
+ ```
+
+* Responses are lazy, requests are concurrent
+* Asychronus requests. Reading in network order
+
+```
+foreach ($client->stream($responses) as $response => $chunk) {
+ if ($chunk->isLast()) {
+ // a $response completed
+ } else {
+ // a $response's got network activity or timeout
+ }
+}
+```
+
+- 379 request completed in 0.4s!
+- `Stream` has second argument, max number of seconds to wait before yielding a
+ timeout chunk
+- `ResponseInterface::getInfo()` - get response headers, redirect count and URL,
+ start time, HTTP method and code, user data and URL
+ - `getInfo('debug')` - displays debug information
+
+### The components
+
+- `NativeHttpClient` and `CurlHttpClient`
+ - both provide
+ - 100% contracts
+ - secure directs
+ - extended (time) info
+ - transparent HTTP compression and (de)chunking
+ - automatic HTTP proxy configuration via env vars
+
+#### `NativeHttpClient`
+
+- is most portable, works for everyone
+- based on HTTP stream wrapper with fixed redirect logic
+- blocking until response headers arrive
+
+#### `CurlHttpClient`
+
+- Requires ext-curl with fixed redirection logic
+- Multiplexing response headers and bodies
+- Leverages HTTP/2 and PUSH when available
+- Keeps connections open also between synchronous requests, no DNS resolution so
+ things are faster
+
+#### Decorators
+
+- ScopingHttpClient - auto-configure options based on request URL
+- MockHttpClient - for testing, doesn't make actual HTTP requests
+- CachingHttpClient - adds caching on a HTTP request
+- Psr18Client
+- HttplugClient
+- TraceableHttpClient
+
+### Combining
+
+#### FrameworkBundle/Autowiring
+
+```yaml
+framework:
+ http_client:
+ max_host_connections: 4
+ deault_options:
+ # ....
+ scoped_client:
+ # ...
+```
+
+#### HttpBrowser
+
+- HttpClient + DomCrawler + CssSelector + HttpKernel + BrowserKit
+- RIP Goutte!
+
+### Coming in 4.4...
+
+- `max_duration`
+- `buffer` based on a callable
+- `$chunk->isInformational()`
+- `$response->toStream()`
+- Async-compatible extensibility, when decoration is not enough
+
+`composer req symfony/http-client`
+
+## Symfony Checker is coming (Valentine Boineau)
+
+- Static analysis tool for Symfony
+ - Does a method exist?
+ - Is it deprecated?
+- insight.symfony.com
+- @symfonyinsight
+- Released soon
+
+### Differences
+
+- Specialise in Symfony - can see more relevant things
+- Different interface to other services
+
+## Feeling unfulfilled by SPA promises? Go back to Twig (Dan Blows)
+
+A way on the front-end JS, CSS, images at the beginning of the request, sends a
+HTTP request (XHR/AJAX) to the back-end
+
+### Why SPAs?
+
+- A way on the front-end JS, CSS, images at the beginning of the request, sends
+ a HTTP request (XHR/AJAX) to the back-end
+- no full page refresh
+- Supposed to be much quicker
+- 'Right tool for the job' - JS on the front-end, PHP on the back-end
+- Division of responsibility == faster development
+- Reusable API - Api -> Mobile App and SPA - easy to add another consumer
+- Easier to debug?
+
+### Why not SPAs?
+
+- Lots of HTTP requests (400 to load the initial page on one project) == slow
+ front end
+- Blurred responsibilities == tightly coupled teams
+- harder to debug, bugs fall between systems and teams. Huge gap between
+ front-end and back-end, passing responsibilites.
+- You can fix these problems in SPAs, but is it worth it?
+ - Examples of good SPAs - Trello, Flickr
+
+### Using Twig as an alternative to an SPA?
+
+#### Faster UI - Try and figure out where the problem is.
+
+If you're trying to speed things up, find out where the problem is.
+
+- Browser tools
+- Web Debug Toolbar
+- Blackfire
+- Optimise and monitor
+
+#### Speed up Twig
+
+- Speeding up Symfony
+- ext/twig (PHP5 only, not PHP 7)
+- Store compiled templates in Opcache, make sure it's enabled
+- Render assets though the webserver (assetic not running all the time)
+
+#### Edge side includes
+
+- Component cached differently to the rest of the page
+- Varnish/Nginx
+- `render_esi`
+- News block that caches frequently, rest of the page
+
+#### HTTP/2 with Weblink
+
+- slow finding CSS files to load - 'push' over CSS files, doesn't need to wait
+- `preload()` - https://symfony.com/doc/current/web_link.html
+
+#### Live updating pages
+
+- Instantly update when sports results are updated, news articles are added
+- Mercure - https://github.com/symfony/mercure
+- LiveTwig - whole block or whole section, and live update `render_live`
+- Turbolinks - replace whole body, keeps CSS and JS in memory. Merges new stuff
+ in. `helthe/turbolinks`
+- ReactPHP - shares kernel between requests
+
+### Writing better code with Twig
+
+- Keep templates simple. Avoid spaghetti code, only about UI. HTML or small
+ amounts of Twig.
+- Avoid delimeter chains
+ - Bad:`blog_post.authors.first.user_account.email_address`
+ - Good `{{ blog_post.authors_email_address }}`
+ - Less brittle, slow
+
+* Filters
+ - Use filters to be precise
+ - Custom filters
+ - Avoid chains. Can cause odd results. Create a new filter in PHP
+* Functions
+ - Write your own functions
+ - Simpler templates
+ - Get data, can use boolean statements
+* Components
+ - Break a page into components rather than one large page
+ - `include()`
+ - Use `only` to only pass that data. less tightenly coupled.
+ * `render` calls the whole of Symfony, boots Kernel, can be expensive and slow
+ * Loosely couple templates and controllers
+ - Keep responses simple
+ - What makes sense
+ - if you need extra data in the template, get it in the template
+ * View models
+ - Mixed results
+ - `BlogPostViewModel`
+ - Can result in boilerplate code
+ - Can be useful if the view model is different to the Entity
+ * DRY
+ - "Don't repeat yourself"
+
+- Faster development
+ - Separate UI tests from back-end tests. Different layers for different teams.
+ People don't need to run everything if they are only changing certain
+ things.
+
+* Help your front end
+ - Webpack - Encore
+ - Type hinting in functions and filters, easier to debug
+ - Logging
+ - Friendly exceptions - help front-end devs by returning meaningful, readbale
+ errors
+ - Web Debug Toolbar and Profiler, provide training for toolbar and profilers
+ - Twig-friendly development environment - Twig support in IDEs and text
+ editors
+
+SPAs are sometimes teh right solution. Why do they want to use it, can the same
+benefits be added with Twig?
+
+3 most important points:
+
+- Profile, identidy, optimise, monitor
+- Loosely couple templates to your app code
+- Help your front ends - put your front end developers first
+- You don't need to use a SPA for single pages, use JavaScript for that one
+ page. It doesn't need to be all or nothing.
+
+## BDD Your Symfony Application (Kamil Kokot)
+
+- Applying BDD to Sylius
+- 2 years since release of Sylius (Symfony 2 alpha)
+- The business part is more important than the code part
+
+### What is BDD?
+
+- Behaviour driven development. Combines TDD and DDD, into an agile methodology
+- Encourages communication and creates shared understanding
+- Living, executable documentation that non-programmers understand. Always
+ correct.
+- Feature file
+ - Feature
+ - Scenario - example of the behaviour for this feature. Simple, atomic. (e.g.
+ I need a product in order to add it to a cart)
+ - In order to...
+ - Who gets the benefit?
+
+### BDD in practice
+
+- Feature: booking flight tickets
+- Scenario: booking flight ticket for one person
+ - Given there are the following flights...
+ - When I visit '/flight/LTN-WAW'
+ - Then I should be on '/flight/LTN-WAW'
+ - Add I should see "Your flight has been booked." in "#result"
+- In the BDD way - what is the business logic? What is the value for this
+ scenario? What is the reason 'why', and who benefits from this?
+ - We just need to know that there are 5 seats left on a flight
+ - Talk and communicate about how the feature is going to work - not just
+ developers
+ - BDD aids communication
+- Questions we can ask
+ - Can we get a different outcome when the context changes?
+ - When there was only one seat available
+ - When there were no available seats
+ - Can we get the same outcome when the event changes? Can we change 'When' and
+ 'Then stays the same'
+ - When it is booked for an adult and a child
+ - When it is booked for an adult
+ - Does anything else happen that is not mentioned?
+ - Generate an invoice if a seat is booked
+ - a pilot would like to get a notification that a seat was booked.
+ * Figuring out the rules
+ - Adults are 15+ years old
+ - Children are 2-14 years old
+ - Infants and children can only travel with an adult
+ - We don't allow for overbooking
+ - Translating rules into examples
+ - Add a new scenario for each rule - e.g. don't allow over booking
+ - "And the flight should be no longer available..."
+
+### Behat
+
+- Used to automate and execute BDD tests, also SpecDDD
+- maps steps to PHP code
+- Given a context, when an event, then an outcome
+- Domain Context, API context
+- class implements `Context`, annotations for `@Given`, `@When`, `@Then`. allows
+ for arguments and regular expressions
+- Suites: change what code is executed, and what scenarios are executed. context
+ and tags
+- FriendsOfBehat SymfonyExtension - integrates Behat with Symfony
+ - Contexts registered as Symfony services - inject dependencies, service as a
+ context in Behat. Need to be 'public' for it to work
+ - Reduces boilerplate code. Supports autowiring.
+ - Zero configuration
+
+### Domain context
+
+- `Given` verb matches `@Given` annotation. Same for `When` and `Then`.
+- Transformers, type hint name string, return Client instance
+
+### API context
+
+- inject `FlightBookingService` and `KernelBrowser`
+- Use `$this->kernelBrowser->request()`
+- Use `assert()` function wuthin `@Then`
+
+### Back to reality - how it's done with Sylius
+
+- Business part applies to all context. Start talking about what needs to be
+ done, start communicating
+- Implement contexts for UI and API
+- 12716 steps, 1175 scenarios, 8 min 8 sec, 2.4 scenarios /sec
+- 12x faster than JS (17 min 48 sec, 0.19 scenario / sec)
+- Treat test CI environment like production
+
+ - Turn off debug settings, add caching
+ - Enable OPcache
+
+- Write features in a natural way
+- Too many setup steps - merge steps. less visual debt. e.g. Create currency,
+ zone and locale when creating a store
+- Avoid scenarios that are too detailed. You should specify only what's
+ important to this scenario.
+
+## Migrating to Symfony one route at a time (Steve Winter)
+
+- New client with an old application, built in an old version of another
+ framework with unusual dependency management, no tests, no version control and
+ deploying via FTP. Done over a ~3 month period.
+
+- Subscription based index of suppliers
+- New requirements to implement by the client
+- Our requirements: Needed a deployment process, make it testable, fix the build
+ chain
+- Solution attempt 1: Migrate to a new version of the current framework
+ - Minor template and design changes were fine
+ - Modifiy features, add new dependencies.
+- Solution attempt 2: Upgrade to the latest version - same outcome due to
+ multiple BC breaks (no semver), lots of manual steps
+- Solution attempt 3: Symfony!
+ - Semver! Backwards compatibility promise
+ - Symfony app to run in parallel, Apache proxy rules and minor changes to the
+ legacy app, added data transfer mechanisms
+ - Anything new done in Symfony
+ - Installed on the same server with it's own vhost but not publicly accessible
+ - Deployed independently of legacy app
+
+### Apache proxy rules
+
+Proxy `/public` to symfony app
+
+### Legacy app
+
+- Shared cookie for single login between apps - user account details (name etc),
+ session details (login time)
+
+### Added functionality
+
+- Built in Symfony
+- new proxy rules for new routes
+- Add menu links to legacy app menu
+- How do we show how many reminders are active?
+ - Symfony based API called from the front-end
+
+### Migrating routes
+
+- Rebuilt or extend in Symfony app
+- Test and deploy, then update the apache config to add new proxy rules
+
+### A gotcha
+
+- Legacy app uses CSRF
+- Needed to track the token, added to shared cookie and pass through to the
+ Symfony side
+
+### Storing data
+
+- Both apps using the same data with different credentials
+- Some shared tables, some tables are specific to each app
+
+### Remaining challenges
+
+- User session management, still handled by legacy app
+- Templating/CSS - two versions of everything
+ - Next step: move all CSS to Symfony
+
+### Summary
+
+- Add Symfony app, Apache proxy rules for routes
+- User transfer mechanisms
+- New functionality added in Symfony
+
+### Is this right for you?
+
+It depends. Fine for a 'modest' size. Use a real proxy for larger scale apps,
+use different servers with database replication.
+
+## Closing Keynote: The fabulous World of Emojis and other Unicode symbols (Nicolas Grekas)
+
+- ASCII. Still used today. Map between the first 128 numbers to characters. OK
+ for UK and US.
+- 256 numbers in Windows-1252 (character sets). Each country had their own set.
+- It's legacy. 0.2% for Windows-1252. 88.8% for UTF-8 (Feb 2017)
+- Unicode: 130k characters, 135 scripts (alphabets)
+- Validation errors using native alphabet - e.g. invalid last name when
+ submitting a form
+- 17 plans, each square is 255 code points
+- Emojis are characters, not images
+- Gliph is a visual representation of a character
+- From code points to bytes
+ - UTF-8: 1,2,3 or 4 bytes
+ - UTF16: 2 or 4 bytes
+ - UTF-32: 4 bytes
+- UTF-8 is compatible with ASCII
+- Case sensitivity - 1k characters are concerned. One uppercase letter, two
+ lower case variants. Turkish exception (similar looking letters that are
+ different letters with different meanings). Full case folding.
+- Collations - ordering is depends on the language. 'ch' in Spanish is a single
+ character.
+- Single number in unicode to represent accents. Combining characters.
+- Composed (NFC) and decomposed (NFD) forms - normalisation for comparison
+- Grapheme clusters - multiple characters, but one letter as you write it
+ (separate characters for letters and accent)
+- Emjois - combining characters. e.g. Combine face with colour. Different codes
+ and character names. Also applies to ligatures. A way to combine several
+ images together into one single visual representation.
+
+### unicode fundamentals
+
+- uppercase, lowercase, folding
+- compositions, ligatures
+- comparistions - normalisations and collations
+- segmentation: characters, words, sentences and hyphens
+- locales: cultural conventions, translitterations
+- identifiers & security, confusables
+- display: direction, width
+
+### unicode in practice
+
+- MySQL - `utf*_*`. `SET NAMES utf8mb4` for security and storing emojis. Cannot
+ store emojis with `utf8`
+
+### in php
+
+- `mb_*()`
+- `iconv_*()`
+- `preg_*()`
+- `grapheme_*()` `normalizer_*()`
+- `symfony/polyfill-*` - pure PHP implementation
+- Made a component - **symfony/string** -
+ https://github.com/symfony/symfony/pull/33553
+- Object orientated api for strings. Immutable value objects
+- `AbstractString`
+ - `GraphemeString`
+ - `Utf8String`
+ - `BinaryString`
+
+* AbstractString - Methods to serialize, get length, to binary or grapheme or
+ utf8
+ - Methods for starts with, ends with, is empty, join, prepend, split, trim,
+ title etc
diff --git a/sculpin/source/_posts/looking-forward-to-drupalcamp-london.md b/sculpin/source/_posts/looking-forward-to-drupalcamp-london.md
new file mode 100644
index 000000000..ca95135b7
--- /dev/null
+++ b/sculpin/source/_posts/looking-forward-to-drupalcamp-london.md
@@ -0,0 +1,66 @@
+---
+title: Looking forward to DrupalCamp London
+date: 2018-02-27
+excerpt: This weekend is DrupalCamp London 2018. I’ll be there along with a number of my Microserve colleagues.
+tags:
+ - drupal
+ - drupalcamp
+ - drupalcamp-london
+ - speaking
+---
+
+This weekend is [DrupalCamp London 2018][1]. I’ll be there along with a number
+of my [Microserve][2] colleagues.
+
+I look forward to DrupalCamp London every year, partly because it was the first
+DrupalCamp that I attended back in 2014. It was also the first DrupalCamp that I
+[gave a talk][3] at, when I presented a session about Git Flow having given only
+one user group talk before.
+
+I’ve presented sessions at every DrupalCamp London since (including two last
+year), and I’m lucky enough to be [speaking again this year][4] due to one of
+the originally announced speakers no longer being able to make it to the event.
+
+Here are some other sessions that I’m hoping to see (in no particular order):
+
+- Keynote by [Ryan Szrama][5] from [Commerce Guys][6]
+- [Drupal 8 Services And Dependency Injection](https://drupalcamp.london/session/drupal-8-services-and-dependency-injection)
+ by Phil Norton
+- [Growing developers with Drupal](https://drupalcamp.london/session/growing-developers-drupal)
+ by Fran Garcia-Linares (fjgarlin)
+- [How to make it easier for newcomers to get involved in Drupal](https://drupalcamp.london/session/how-make-it-easier-newcomers-get-involved-drupal)
+ by heather
+- [Let’s take the best route - Exploring Drupal 8 Routing System](https://drupalcamp.london/session/lets-take-best-route-exploring-drupal-8-routing-system)
+ by surbhi
+- [New recipe of Decoupling: Drupal 8, Symfony and Slim Framework](https://drupalcamp.london/session/new-recipe-decoupling-drupal-8-symfony-and-slim-framework)
+ by Jyoti Singh
+- [Plugin API by examples](https://drupalcamp.london/session/plugin-api-examples)
+ by Gabriele (gambry)
+- [Value of mentorship in the community](https://drupalcamp.london/session/value-mentorship-community)
+ by Hussain Abbas (hussainweb)
+- [Warden - Helping Drupal Agencies Sleep at Night](https://drupalcamp.london/session/warden-helping-drupal-agencies-sleep-night)
+ by Mike Davis
+
+Unfortunately there are some time slots where I’d like to see more than one of
+the talks (including when I’m going to be speaking). This regularly happens at
+conferences, but I’ll look forward to watching those on [YouTube][7] after the
+event.
+
+I’m also looking forward to catching up with former colleagues, spending some
+time in the "hallway track" and hopefully doing some sprinting too!
+
+## Finally
+
+For nostalgia, [here’s the blog post][0] that I wrote before I attended my first
+DrupalCamp London.
+
+See everyone this weekend!
+
+[0]: {{site.url}}/blog/2014/02/09/drupalcamp-london-2014
+[1]: https://drupalcamp.london
+[2]: {{site.companies.microserve.url}}
+[3]: {{site.url}}/talks/git-flow
+[4]: {{site.url}}/talks/deploying-drupal-fabric
+[5]: http://ryanszrama.com
+[6]: https://commerceguys.com
+[7]: https://www.youtube.com/channel/UCsaB96zszIP4Y3czs-ndiIA
diff --git a/sculpin/source/_posts/mediacurrent-contrib-half-hour-is-back.md b/sculpin/source/_posts/mediacurrent-contrib-half-hour-is-back.md
new file mode 100644
index 000000000..0402a70f9
--- /dev/null
+++ b/sculpin/source/_posts/mediacurrent-contrib-half-hour-is-back.md
@@ -0,0 +1,67 @@
+---
+title: Yay, the Mediacurrent Contrib Half Hour is Back!
+date: 2018-03-02
+excerpt: Mediacurrent’s "contrib half hour sessions" are back.
+tags:
+ - contribution
+ - drupal
+ - open-source
+has_tweets: true
+---
+
+Back in November, [Mediacurrent introduced][1] the contrib half hour - a weekly
+online meeting to provide guidance and assistance on contributing to Drupal and
+Drupal projects. A range of topics were covered in the first few sessions,
+including finding and testing bug fixes, Composer, Drush, and how to re-roll
+patches.
+
+From Damien's [introductory blog post][2]:
+
+> Not sure what this whole "patch" thing is? Have a core change that you can't
+> quite finish? Running into a problem with a contrib module, or a theme, or a
+> 3rd party library, and not sure how to fix it? New to maintaining a module and
+> unsure of what to do next? Wondering how to get your module through the
+> security opt-in process? Is your project's issue queue getting you down? Join
+> us every Thursday at noon EST for the Mediacurrent Contrib Half Hour where
+> we'll be available to help solve contrib challenges.
+>
+> Each week we'll host a live meeting to give step-by-step guidance on some best
+> practices for contributing to Drupal, and provide Q and A assistance for our
+> favorite open source (OSS) content management system (CMS). The meetings will
+> be lead by yours truly, Damien McKenna, a prolific contributor to the Drupal
+> community, and my coworkers here at Mediacurrent.
+
+There is also an [updates blog post][3] that continues to show the latest
+information, and the video recordings are [uploaded to YouTube][0] after the
+session. Here is the first one from November:
+
+
+
+I enjoyed watching the first few videos, as I’m always interested in
+contribution to Drupal and open-source and how to encourage it, but then no new
+videos were uploaded for a while and I hoped that it hadn’t faded away.
+
+I’m glad to see today that it’s back and that all of the previous videos have
+been uploaded and added to the [YouTube playlist][0], and that [on the update
+post][3] there are scheduled topics for the rest of this month including
+documentation and automated testing.
+
+
+
+I do enjoy watching these, and I like both the presentation and Q&A format that
+they alternate between. I’ll look forward to catching up over the next few days,
+and to hopefully seeing them continue to be uploaded after future meetings.
+
+Thanks Damien and Mediacurrent!
+
+[0]: https://www.youtube.com/playlist?list=PLu-MxhbnjI9rHroPvZO5LEUhr58Yl0j_F
+[1]:
+ https://www.mediacurrent.com/blog/introducing-mediacurrent-contrib-half-hour
+[2]:
+ https://www.mediacurrent.com/blog/introducing-mediacurrent-contrib-half-hour
+[3]:
+ https://www.mediacurrent.com/blog/updates-mediacurrent-contrib-half-hour-weekly-meeting
diff --git a/sculpin/source/_posts/migrating-drupal-8-introduction.md b/sculpin/source/_posts/migrating-drupal-8-introduction.md
new file mode 100644
index 000000000..cef812713
--- /dev/null
+++ b/sculpin/source/_posts/migrating-drupal-8-introduction.md
@@ -0,0 +1,22 @@
+---
+title: 'Migrating to Drupal 8: Introduction'
+excerpt: An introduction to the 'Migrating to Drupal 8' blog post series.
+date: 2020-08-12
+tags:
+ - drupal
+ - drupal-8
+ - drupal-planet
+---
+
+I recently finished porting this website from a static site generator to Drupal 8, meaning that this site has now been powered by three different major versions of Drupal (6, 7 and 8) as well as by two static site generators since it was first launched in early 2010.
+
+The majority of the content was imported using migrations from JSON feeds that I created. This included:
+
+- Blog tags
+- Blog posts
+- Talks
+- Redirects
+
+In some follow-up posts, I'll be looking at each migration separately, describing any issues and look at how it was used to import its respective content.
+
+I'll update this post with the links to the follow-up posts, and they are also available from the [blog series' page](/taxonomy/term/165).
diff --git a/sculpin/source/_posts/minimum-core-version.md b/sculpin/source/_posts/minimum-core-version.md
new file mode 100644
index 000000000..aaeb69e63
--- /dev/null
+++ b/sculpin/source/_posts/minimum-core-version.md
@@ -0,0 +1,96 @@
+---
+title: How to Define a Minimum Drupal Core Version
+date: 2015-04-03
+excerpt: How to define a minimum Drupal core version for your module or theme.
+tags:
+ - drupal
+ - drupal-7
+ - drupal-planet
+meta:
+ og:
+ title: 'How to Define a Minimum Drupal Core Version'
+ excerpt: 'How to define a minimum Drupal core version for your module or theme.'
+ type: article
+---
+
+This week, my first code patch was
+[committed to Drupal core](https://www.drupal.org/node/2394517#comment-9773143).
+The patch adds the `user_has_role()` function to the user module, to simplify
+the way to check whether a user in Drupal has been assigned a specific role.
+This is something that I normally write a custom function for each project, but
+it's now available in Drupal core as of
+[7.36](https://www.drupal.org/drupal-7.36-release-notes).
+
+But what if someone is using a core version less than 7.36 and tries using the
+function? The site would return an error because that function wouldn't exist.
+
+If you're building a new Drupal site, then I'd assume that you're using a latest
+version of core, or you have the opportunity to update it when needed. But what
+if you're writing a contrib module? How can you be sure that the correct minimum
+version of core?
+
+## Setting Dependencies
+
+What I'm going to be doing for my contrib projects is defining a minimum version
+of Drupal core that the module is compatible with. If this dependency isn't met,
+the module won't be able to be enabled. This is done within your module's .info
+file.
+
+### Adding a Simple Dependency
+
+You can define a simple dependency for your module by adding a line this this to
+your project's .info file:
+
+```bash
+dependencies[] = views
+```
+
+This would make your module dependant on having the
+[Views](https://www.drupal.org/project/views) module present and enabled, which
+you'd need if you were including views as part of your module, for example.
+
+### Adding a Complex Dependency
+
+In the previous example, our module would enable if _any_ version of Views was
+enabled, but we need to specify a specific version. We can do this by including
+version numbers within the dependencies field in the following format:
+
+```bash
+dependencies[] = modulename (major.minor)
+```
+
+This can be a for a specific module release or a branch name:
+
+```bash
+dependencies[] = modulename (1.0)
+dependencies[] = modulename (1.x)
+```
+
+We can also use the following as part of the field for extra granularity:
+
+- = or == equals (this is the default)
+- > greater than
+- < lesser than
+- > = greater than or equal to
+- <= lesser than or equal to
+- != not equal to
+
+In the original scenario, we want to specify that the module can only be enabled
+on Drupal core 7.36 or later. To do this, we can use the "greater than or equal
+to" option.
+
+```ini
+dependencies[] = system (>=7.36)
+```
+
+Because we need to check for Drupal's core version, we're using the system
+module as the dependency and specifying that it needs to be either equal to or
+greater than 7.36. If this dependency is not met, e.g. Drupal 7.35 is being
+used, then the module cannot be enabled rather than showing a function not found
+error for `user_has_role()` when it is called.
+
+
+
+## External Links
+
+- [Writing module .info files (Drupal 7.x)](https://www.drupal.org/node/542202#dependencies)
diff --git a/sculpin/source/_posts/my-first-blog-post-published-for-inviqa.md b/sculpin/source/_posts/my-first-blog-post-published-for-inviqa.md
new file mode 100644
index 000000000..f1aca7ab0
--- /dev/null
+++ b/sculpin/source/_posts/my-first-blog-post-published-for-inviqa.md
@@ -0,0 +1,12 @@
+---
+title: My first blog post published for Inviqa
+excerpt: My first blog post has been published on the inviqa.com website.
+date: 2020-04-29
+tags:
+ - drupal
+ - testing
+---
+
+My first blog post was published on the Inviqa website last week. Is an introduction to automated testing in Drupal, which also includes a recap of the workshop that I recently gave at DrupalCamp London.
+
+The blog post can be found at , and there's more information about the workshop specifically at .
diff --git a/sculpin/source/_posts/my-first-six-months-transport-wales.md b/sculpin/source/_posts/my-first-six-months-transport-wales.md
new file mode 100644
index 000000000..6f08b3c52
--- /dev/null
+++ b/sculpin/source/_posts/my-first-six-months-transport-wales.md
@@ -0,0 +1,45 @@
+---
+title: "My first six months at Transport for Wales"
+excerpt:
+ It's been a busy six months since I started working as a Lead Software
+ Developer at Transport for Wales.
+date: 2021-12-03
+tags: ["personal"]
+---
+
+It's been a busy six months since I started working as a Lead Software
+Developer at Transport for Wales back in June.
+
+My main focus has been being part on the development team for the new
+[tfw.wales] and [trc.cymru] websites, which replaced the previous tfwrail.wales
+and trctrenau.cymru websites.
+
+Some of my personal highlights have been:
+
+- Completion of the initial phase 1 project that went live in September, based
+ on Drupal 8.9.
+- Upgrading the phase 1 project from Drupal 8.9 to 9.2 over several iterations
+ alongside new development and other BAU tasks.
+- Creating containerised versions of each of our applications using Docker,
+ creating consistent local environments for us to use.
+- The introduction of automated testing and other code quality tools, such as
+ PHPStan for static analysis, for any new code, and automating this using
+ Bitbucket Pipelines and the new Docker images.
+- Continuing to work with a range of technologies - Drupal, Symfony and API
+ Platform, Vue.js, TypeScript, Acquia Cloud, and Amazon AWS and S3.
+
+We've already been working on the second phase of the TfW websites, integrating
+some of our other websites, and adding new features whilst continuing to
+maintain and improve the existing codebase.
+
+I'm currently supporting another project team at the moment too - their project
+is going live next week - and will look into writing more week- or month-notes
+going forward.
+
+I've continued [working with Neovim](/blog/going-full-vim) as my daily
+IDE/text-editor tool, have continued working on my [freelance development and
+consulting projects](/drupal-php-developer), and started to squeeze in [some
+more live streams](https://oliverdavies.live) too!
+
+[tfw.wales]: https://tfw.wales
+[trc.cymru]: https://trc.cymru
diff --git a/sculpin/source/_posts/my-new-drupal-modules.md b/sculpin/source/_posts/my-new-drupal-modules.md
new file mode 100644
index 000000000..3c2cb4c58
--- /dev/null
+++ b/sculpin/source/_posts/my-new-drupal-modules.md
@@ -0,0 +1,24 @@
+---
+title: My new Drupal modules
+date: 2012-07-12
+excerpt: After a busy few days, I've released two new contrib Drupal modules.
+tags:
+ - accessibility
+ - drupal
+ - drupal-6
+ - drupal-7
+ - drupal-modules
+ - drupal-planet
+---
+
+After a busy few days, I've released two new contrib Drupal modules.
+
+- [Block Aria Landmark Roles](http://drupal.org/project/block_aria_landmark_roles) -
+ Inspired by [Block Class](http://drupal.org/project/block_class), this module
+ adds additional elements to the block configuration forms that allow users to
+ assign a ARIA landmark role to a block.
+- [Nomensa Accessible Media Player](http://drupal.org/project/nomensa_amp) -
+ Provides integration with Nomensa's
+ [Accessible Media Player](https://github.com/nomensa/Accessible-Media-Player).
+
+Documentation for both to follow shortly on Drupal.org.
diff --git a/sculpin/source/_posts/my-sublime-text-2-settings.md b/sculpin/source/_posts/my-sublime-text-2-settings.md
new file mode 100644
index 000000000..375ace339
--- /dev/null
+++ b/sculpin/source/_posts/my-sublime-text-2-settings.md
@@ -0,0 +1,106 @@
+---
+title: My Sublime Text 2 settings
+date: 2012-10-25
+excerpt: Sublime Text 2 has been my text editor of choice for the past few months, and I use it at home, in work, and on any virtual machines that I run. So rather than having to manually re-enter my settings each time, I thought that I'd document them here for future reference.
+tags:
+ - sublime-text
+---
+
+[Sublime Text 2](http://www.sublimetext.com/2) has been my text editor of choice
+for the past few months, and I use it at home, in work, and on any virtual
+machines that I run. So rather than having to manually re-enter my settings each
+time, I thought that I'd document them here for future reference.
+
+These preferences ensure that the code is compliant with
+[Drupal coding standards](http://drupal.org/coding-standards 'Drupal coding standards on Drupal.org') -
+using two spaces instead of a tab, no trailing whitespace, blank line at the end
+of a file etc.
+
+## Preferences
+
+These can be changed by going to Preferences > Settings - User.
+
+```json
+{
+ "color_scheme": "Packages/Theme - Aqua/Color Schemes/Tomorrow Night Aqua.tmTheme",
+ "default_line_ending": "unix",
+ "ensure_newline_at_eof_on_save": true,
+ "fallback_encoding": "UTF-8",
+ "file_exclude_patterns":
+ [
+ "*.pyc",
+ "*.pyo",
+ "*.exe",
+ "*.dll",
+ "*.obj",
+ "*.o",
+ "*.a",
+ "*.lib",
+ "*.so",
+ "*.dylib",
+ "*.ncb",
+ "*.sdf",
+ "*.suo",
+ "*.pdb",
+ "*.idb",
+ ".DS_Store",
+ "*.class",
+ "*.psd",
+ "*.db",
+ "*.sublime*"
+ ],
+ "folder_exclude_patterns":
+ [
+ ".svn",
+ ".git",
+ ".hg",
+ "CVS",
+ "FirePHPCore"
+ ],
+ "font_options":
+ [
+ "no_bold",
+ "no_italic"
+ ],
+ "font_size": 16.0,
+ "highlight_line": true,
+ "ignored_packages":
+ [
+ ],
+ "line_padding_bottom": 1,
+ "rulers":
+ [
+ 80
+ ],
+ "save_on_focus_lost": true,
+ "shift_tab_unindent": true,
+ "tab_size": 2,
+ "theme": "Soda Light.sublime-theme",
+ "translate_tabs_to_spaces": true,
+ "trim_automatic_white_space": true,
+ "trim_trailing_white_space_on_save": true,
+ "word_wrap": false
+}
+```
+
+## Key bindings
+
+These can be changed by going to Preferences > Key Bindings - User.
+
+```json
+[
+ { "keys": ["alt+s"], "command": "toggle_side_bar" },
+ { "keys": ["alt+r"], "command": "reindent" }
+]
+```
+
+## Packages
+
+These are the packages that I currently have installed.
+
+- [DocBlockr](https://github.com/spadgos/sublime-jsdocs 'DocBlockr on GitHub')
+- [Drupal API](https://github.com/BrianGilbert/Sublime-Text-2-Goto-Drupal-API)
+- [LESS](https://github.com/danro/LESS-sublime)
+- [Package Control](http://wbond.net/sublime_packages/package_control)
+- [Sublime CodeIntel](http://github.com/Kronuz/SublimeCodeIntel)
+- [Theme - Soda](https://github.com/buymeasoda/soda-theme)
diff --git a/sculpin/source/_posts/nginx-redirects-query-string-arguments.md b/sculpin/source/_posts/nginx-redirects-query-string-arguments.md
new file mode 100644
index 000000000..cc7be79b4
--- /dev/null
+++ b/sculpin/source/_posts/nginx-redirects-query-string-arguments.md
@@ -0,0 +1,57 @@
+---
+title: Nginx Redirects With Query String Arguments
+date: 2017-01-31
+excerpt: How to redirect from an old domain to a new one, and also to redirect from the root example.com domain to the canonical www subdomain.
+tags:
+ - nginx
+---
+
+This is an example of how my Nginx configuration looked to redirect from an old
+domain to a new one, and also to redirect from the root `example.com` domain to
+the canonical `www` subdomain.
+
+```nginx
+server {
+ listen 80;
+
+ server_name example.com;
+ server_name my-old-domain.com;
+ server_name www.my-old-domain.com;
+
+ return 301 https://www.example.com$uri;
+}
+```
+
+It also redirects the URI value, e.g. from `http://example.com/test` to
+`http://example.com/test`, but I noticed recently though that any the query
+string would be lost - e.g. `http://example.com/?test` would redirect to
+`http://www.example.com` and the `?test` would be dropped. The application that
+I built references images based on the query string, so I wanted these to be
+included within the redirect.
+
+This was fixed by making a small change to my `return` statement.
+
+Before:
+
+```nginx
+return 301 https://www.example.com$uri;
+```
+
+After:
+
+```nginx
+return 301 https://www.example.com$uri$is_args$args;
+```
+
+`$is_args` is an empty string if there are no arguments, or a `?` to signify the
+start of the query string. `$args` then adds the arguments (`$query_string`
+could also be used with the same result).
+
+Here is an demo of it working on this website:
+
+
+
+## Resources
+
+- [Query string](https://en.wikipedia.org/wiki/Query_string)
+- [Nginx ngx_http_core_module](http://nginx.org/en/docs/http/ngx_http_core_module.html)
diff --git a/sculpin/source/_posts/nix-php-developers-php-thames-valley.md b/sculpin/source/_posts/nix-php-developers-php-thames-valley.md
new file mode 100644
index 000000000..1869f1bbc
--- /dev/null
+++ b/sculpin/source/_posts/nix-php-developers-php-thames-valley.md
@@ -0,0 +1,5 @@
+---
+title: Nix for PHP Developers at PHP Thames Valley
+date: ~
+draft: true
+---
diff --git a/sculpin/source/_posts/null-users-system-users-drupal.md b/sculpin/source/_posts/null-users-system-users-drupal.md
new file mode 100644
index 000000000..b63109cf1
--- /dev/null
+++ b/sculpin/source/_posts/null-users-system-users-drupal.md
@@ -0,0 +1,155 @@
+---
+title: Null Users and System Users in Drupal
+date: 2018-08-16
+excerpt: Announcing the Null User and System User modules.
+tags:
+ - drupal
+ - drupal-7
+ - drupal-8
+ - drupal-modules
+ - drupal-planet
+ - php
+---
+
+Have you ever needed to have a 'special user' to perform tasks on your Drupal
+site, such as performing actions based on an API request, or for sending an
+internal site message?
+
+If you just create a new user, how do you identify that user going forward? Do
+you hard-code the 'magic' user ID in your custom code? What if the user has a
+different ID on different environments of your site? You could declare it in
+each environment’s settings file and retrieve it from there, but what then if
+you need to do the same on another site? That would mean some duplication of
+code - and something that could have been abstracted and re-used.
+
+I had to do this recently, and rather than just duplicate the code I decided to
+make it into it’s own module - which then became two modules.
+
+## System users
+
+The [System User module][1] provides a re-usable, generic way to denote users as
+'system users', which is not specific to a certain site or environment as this
+is value is stored against each individual user in the database.
+
+'System user' is a term used in Linux, which I thought also applies well to this
+scenario.
+
+From :
+
+> A system account is a user account that is created by an operating system
+> during installation and that is used for operating system defined purposes.
+> System accounts often have predefiend user ids. Examples of system accounts
+> include the root account in Linux.
+
+A system user isn’t an account that we’d expect a person to log in with and
+perform routine tasks like updating content, but rather for the system (site) to
+use to perform tasks like the earlier examples.
+
+### Declaring a user as a system user
+
+System User module adds a base field to Drupal’s User entity, which determines
+whether or not each user is a system user - i.e. if this field is `TRUE`, that
+user is a system user. This means that users can easily be queried to identify
+which are system users, without having to rely on magic, environment and site
+specific user IDs. This also means that we can have multiple system users, if
+needed.
+
+{.border
+.p-1}
+
+In the Drupal 8 version of the module, a `SystemUser` is a custom entity, that
+contains it’s own `create` method for creating new system users. This is a
+essentially a wrapper around `User::create()` that automatically sets the value
+of the system user field as part of the creation.
+
+The original intention is that system users would always be created manually in
+an custom install or update hook, however since releasing the module, I’ve also
+added an install hook to the module to automatically create a new system user
+when the module is installed, basing the username on the site name.
+
+There is also an open issue to add a Drush command to create a new system user,
+and I’d imagine I’ll also add a Drupal Console command too.
+
+### Retrieving system users
+
+Whilst you could easily write your own query that retrieves users based on the
+value of the system user field, but the module contains a `SystemUserManager`
+service that contains methods to do so. It also provides a static helper class
+that determines if a specified user is a system user by checking the value of
+the system user field.
+
+```
+// Retrieve the first system user.
+$system_user = $this->systemUserManager->getFirst();
+
+// Is the specified user a system user?
+$is_system_user = SystemUserManager::isSystemUser($user);
+```
+
+But what do we return if there are no system users? You could return `NULL` or
+`FALSE`, but I decided to take a different approach, which became the second
+module.
+
+## Null users
+
+The [Null User module][2] is an implementation of the [null object pattern][3]
+for users in Drupal 8. In this case, a [NullUser][4] is an extension of Drupal’s
+`AnonymousUserSession`, which means that it inherits sensible defaults to return
+for a non-existent User. Though, through inheritance, the `id`, `getRoles` and
+`hasPermission` methods are overridden to return relevant values.
+
+```php
+use Drupal\Core\Session\AnonymousUserSession;
+
+class NullUser extends AnonymousUserSession {
+ ...
+}
+```
+
+Null User module is a dependency of System User in Drupal 8, so When no system
+user is found from the `getFirst()` method, a `NullUser` is returned. Whilst I
+could alternatively have returned `NULL` or `FALSE`, we then would need to check
+if the returned value was an object or not before calling methods on it.
+
+```php
+$system_user = $this->systemUserManager->getFirst(); // Returns NULL or FALSE.
+
+// Need to check if a user was returned or not.
+if (!$system_user) {
+ return;
+}
+
+if ($system_user->isActive()) {
+ ...
+}
+```
+
+Because instead we’re returning a `NullUser`, which through class inheritance
+has the same methods and properties as a regular user, there is no need to do
+the additional check as you will always receive a relevant object, and the
+expected methods will always be present.
+
+```php
+$system_user = $this->systemUserManager->getFirst(); // Returns a NullUser.
+
+if ($system_user->isActive()) {
+ ...
+}
+```
+
+This means we have less code, which also is simpler and more readable.
+
+System User module is the only one that I’m aware of that makes use of Null
+User, but I’ve added a list to the [project page][2] so let me know if you can
+think of any others.
+
+## Resources
+
+- [Null object pattern][3]
+- [Null User module][2]
+- [System User module][1]
+
+[1]: https://www.drupal.org/project/system_user
+[2]: https://www.drupal.org/project/null_user
+[3]: https://en.wikipedia.org/wiki/Null_object_pattern
+[4]: http://cgit.drupalcode.org/null_user/tree/src/NullUser.php?h=8.x-1.x
diff --git a/sculpin/source/_posts/open-sublime-text-2-mac-os-x-command-line.md b/sculpin/source/_posts/open-sublime-text-2-mac-os-x-command-line.md
new file mode 100644
index 000000000..596589c0e
--- /dev/null
+++ b/sculpin/source/_posts/open-sublime-text-2-mac-os-x-command-line.md
@@ -0,0 +1,23 @@
+---
+title: Open Sublime Text 2 from the Mac OS X Command Line
+date: 2012-11-17
+excerpt: How to open Sublime Text from the command line.
+tags:
+ - mac-os-x
+ - sublime-text
+ - terminal
+---
+
+How to open Sublime Text from the command line.
+
+Paste the following code into the Mac OS X Terminal, assuming that you've
+installed Sublime Text 2 into the /Applications folder.
+
+```bash
+$ ln -s "/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl" ~/bin/sublime
+```
+
+Now you can type `sublime ` open a file or directory in Sublime Text,
+or `sublime .` to open the current directory.
+
+You can also type `sublime --help` to see a list of the available commands.
diff --git a/sculpin/source/_posts/perpetually-publishing.md b/sculpin/source/_posts/perpetually-publishing.md
new file mode 100644
index 000000000..7e335e697
--- /dev/null
+++ b/sculpin/source/_posts/perpetually-publishing.md
@@ -0,0 +1,5 @@
+---
+title: Perpetually publishing
+date: ~
+draft: true
+---
diff --git a/sculpin/source/_posts/presenting-on-tailwind-css-and-ansible-at-cms-philly.md b/sculpin/source/_posts/presenting-on-tailwind-css-and-ansible-at-cms-philly.md
new file mode 100644
index 000000000..e669aa450
--- /dev/null
+++ b/sculpin/source/_posts/presenting-on-tailwind-css-and-ansible-at-cms-philly.md
@@ -0,0 +1,23 @@
+---
+title: Presenting on Tailwind CSS and Ansible at CMS Philly
+excerpt: I'll be presenting on Tailwind CSS and deployments with Ansible at CMS Philly on May 1st.
+date: 2020-04-24
+tags:
+ - ansible
+ - ansistrano
+ - conference
+ - drupal
+ - drupal-planet
+ - speaking
+ - tailwind-css
+---
+
+{.mx-auto}
+
+I'm happy to be presenting two talks remotely at this year's [CMS Philly](https://cmsphilly.org) conference (formerly [Drupaldelphia](https://www.drupaldelphia.org)).
+
+The first talk is [Deploying PHP applications with Ansible, Ansible Vault and Ansistrano](/talks/deploying-php-ansible-ansistrano) at 1pm (6pm UK time) where I'll be doing an introduction to Ansible and show how to use Ansistrano to do deploy a Drupal 8 application.
+
+The second talk is [Taking Flight with Tailwind CSS](/talks/taking-flight-with-tailwind-css) at 2pm (7pm UK time) where I'll show how to configure and use Tailwind CSS.
+
+CMS Philly is happening virtually on Friday, May 1st via GoToWebinar.
diff --git a/sculpin/source/_posts/presenting-pdf-slides-using-pdfpc-pdf-presenter-console.md b/sculpin/source/_posts/presenting-pdf-slides-using-pdfpc-pdf-presenter-console.md
new file mode 100644
index 000000000..2275139b3
--- /dev/null
+++ b/sculpin/source/_posts/presenting-pdf-slides-using-pdfpc-pdf-presenter-console.md
@@ -0,0 +1,91 @@
+---
+title: Presenting from PDF slides using pdfpc (PDF Presenter Console)
+excerpt: My notes from using pdfpc (PDF Presenter Console).
+tags:
+ - speaking
+date: 2021-04-23
+---
+
+I recently started using PDF files for presentation slides and gave [a talk about a tool called rst2pdf](/talks/building-presenting-slide-decks-rst2pdf) that I use to write slides in reStructuredText and convert them to PDF. This blog post is about another tool that I use to present from the PDF file, which is called the [PDF Presenter Console](https://pdfpc.github.io "The pdfpc website") (or `pdfpc`).
+
+This is the basic version that you get by running `pdfpc slides.pdf`:
+
+
+
+
+
+
Image
+
+
+
+
+
+
+
+It opens a speaker notes window that shows the current and next slide, the total number of slides in the presentation, the current slide number, and an increasing amount of time since you started the presentation.
+
+Here are some tips that I usually use with `pdfpc`.
+
+## Setting a duration
+
+If you don't want a timer that shows the amount of time that you've been presenting so far then you can set a duration for the presentation using `pdfpc --duration 10`, and the timer will count down instead.
+
+
+
+
+
+
Image
+
+
+
+
+
+
+
+This is great if your presentation is has a strict end time such as during a conference schedule.
+
+## Setting a start time
+
+As well as a duration, you can add the start time for the presentation by running `pdfpc --start-time=13:00`. This will show you a timer that will count down until the specified start time when you need to start presenting.
+
+
+
+
+
+
Image
+
+
+
+
+
+
+
+Again, this is great if your presentation is has a strict start time such as during a conference.
+
+## Swapping screens
+
+When using both my laptop screen and external monitor for presenting (one for the slides, and the other for the speaker notes) it seems that the windows usually open on the opposite screens to the ones that I want.
+
+I can override this though using the `--switch-screens` option and swap the presentation/presenter screens.
+
+There are also `--presenter-screen` and `--presentation-screen` options to set the monitor to use for each but I usually find that just swapping them works for me.
+
+## Presentation overview
+
+When presenting, you can press the `Tab` key to see an overview of the presentation, with each slide and its number, and where you can easily navigate between slides which is very helpful if, during the Q&A section of the presentation, someone would like to ask a question about or reference a particular slide.
+
+
+
+
+
+
Image
+
+
+
+
+
+
+
+## And more...
+
+There are a lot more options in `pdfpc`. To see more, run `pdfpc --help` or press `?` within the presenter view to see the help page (or `pdfpc --list-bindings`).
diff --git a/sculpin/source/_posts/prevent-apache-displaying-text-files-within-web-browser.md b/sculpin/source/_posts/prevent-apache-displaying-text-files-within-web-browser.md
new file mode 100644
index 000000000..5427894b7
--- /dev/null
+++ b/sculpin/source/_posts/prevent-apache-displaying-text-files-within-web-browser.md
@@ -0,0 +1,28 @@
+---
+title: Prevent Apache from displaying text files within a web browser
+date: 2012-05-23
+excerpt: How to prevent Apache from displaying the contents of files like CHANGELOG.txt.
+tags:
+ - apache
+ - code
+ - drupal
+---
+
+When you download [Drupal](http://drupal.org/project/drupal), there are several
+text files that are placed in the root of your installation. You don't want or
+need these to be visible to anyone attempting to view them in a browser -
+especially CHANGELOG.txt as that includes the exact version of Drupal you are
+running and could therefore have security implications.
+
+Rather than delete these files or change the file permissions manually for each
+file, I can add the following lines into my VirtualHost configuration.
+
+```
+
+ Order deny,allow
+ Deny from all
+
+```
+
+This prevents any files with a .txt extension from being accessed and rendered
+in a web browser.
diff --git a/sculpin/source/_posts/proctor-stevenson.md b/sculpin/source/_posts/proctor-stevenson.md
new file mode 100644
index 000000000..eeed687e2
--- /dev/null
+++ b/sculpin/source/_posts/proctor-stevenson.md
@@ -0,0 +1,20 @@
+---
+title: Proctor & Stevenson
+date: 2011-03-31
+excerpt: I’m moving jobs.
+tags:
+ - personal
+---
+
+2 weeks ago, I handed in my notice of resignation to
+[Horse & Country TV](http://horseandcountry.tv)because I've been offered a new
+role at [Proctor & Stevenson](http://proctors.co.uk) - a Marketing Design and
+Communications agency in Bristol.
+
+Proctors have an [extensive client list](http://www.proctors.co.uk/clients) -
+including [BMW](http://www.proctors.co.uk/clients/bmw-financial-services),
+[Panasonic](http://www.proctors.co.uk/clients/panasonic), the
+[Open University](http://www.proctors.co.uk/clients/open-university) and
+[VOSA](http://www.proctors.co.uk/clients/vosa), and it's going to be a fantastic
+opportunity for me to continue expanding my skillset whilst gaining vital
+experience.
diff --git a/sculpin/source/_posts/proctors-hosting-next-drupal-meetup.md b/sculpin/source/_posts/proctors-hosting-next-drupal-meetup.md
new file mode 100644
index 000000000..d9dfc0347
--- /dev/null
+++ b/sculpin/source/_posts/proctors-hosting-next-drupal-meetup.md
@@ -0,0 +1,15 @@
+---
+title: Proctors Hosting the next Drupal Meetup
+date: 2011-05-20
+excerpt: Proctor & Stevenson are going to be hosting the next Bristol & South West Drupal meetup.
+tags:
+ - drupal-bristol
+ - meetups
+---
+
+My employer, [Proctor & Stevenson](http://www.proctors.co.uk), are going to be
+hosting the next Bristol & South West Drupal meetup on the 25th May at our
+offices.
+
+You can [view more details](http://groups.drupal.org/node/147324), or register
+[on our website](http://www.proctors.co.uk/Drupal-SWUG-Meetup).
diff --git a/sculpin/source/_posts/psr4-autoloading-test-cases-drupal-7.md b/sculpin/source/_posts/psr4-autoloading-test-cases-drupal-7.md
new file mode 100644
index 000000000..c106daf0a
--- /dev/null
+++ b/sculpin/source/_posts/psr4-autoloading-test-cases-drupal-7.md
@@ -0,0 +1,143 @@
+---
+title: Using PSR-4 Autoloading for your Drupal 7 Test Cases
+excerpt: How to use the PSR-4 autoloading standard for Drupal 7 Simpletest test cases.
+tags:
+ - drupal
+ - drupal-planet
+ - drupal-7
+ - testing
+ - simpletest
+ - php
+ - psr
+date: 2020-02-04
+---
+
+
{{ page.excerpt }}
+
+## The Traditional Way
+
+The typical way of including test cases in Drupal 7 is to add one or more
+classes within a `.test` file - e.g. `opdavies.test`. This would typically
+include all of the different test cases for that module, and would be placed in
+the root of the module’s directory alongside the `.info` and `.module` files.
+
+In order to load the files, each file would need to be declared within the
+`.info` file for the module.
+
+There is a convention that if you have multiple tests for your project, these
+can be split into different files and grouped within a `tests` directory.
+
+```ini
+; Load a test file at the root of the module
+files[] = opdavies.test
+
+; Load a test file from within a subdirectory
+files[] = tests/foo.test
+files[] = tests/bar.test
+```
+
+## Using the xautoload Module
+
+Whilst splitting tests into separate files makes things more organised, each
+file needs to be loaded separately. This can be made simpler by using the
+[Xautoload module][], which supports wildcards when declaring files.
+
+[xautoload module]: https://www.drupal.org/project/xautoload
+
+```ini
+files[] = tests/**/*.test
+```
+
+This would load all of the `.test` files within the tests directory.
+
+## Using PSR-4 Autoloading
+
+Another option is to use PSR-4 (or PSR-0) autoloading.
+
+This should be a lot more familiar to those who have worked with Drupal 8,
+Symfony etc, and means that each test case is in its own file which is cleaner,
+files have the `.php` extension which is more standard, and the name of the file
+matches the name of the test class for consistency.
+
+To do this, create a `src/Tests` (PSR-4) or `lib/Drupal/{module_name}/Tests`
+(PSR-0) directory within your module, and then add or move your test cases
+there. Add the appropriate namespace for your module, and ensure that
+`DrupalWebTestCase` or `DrupalUnitTestCase` is also namespaced.
+
+```php
+// src/Tests/Functional/OliverDaviesTest.php
+
+namespace Drupal\opdavies\Tests\Functional;
+
+class OliverDaviesTest extends \DrupalWebTestCase {
+ // ...
+}
+```
+
+This also supports subdirectories, so you can group classes within `Functional`
+and `Unit` directories if you like.
+
+If you want to see an real-world example, see the Drupal 7 branch of the
+[Override Node Options module][override_node_options].
+
+[override_node_options]:
+ https://git.drupalcode.org/project/override_node_options/tree/7.x-1.x
+
+### Digging into the simpletest_test_get_all function
+
+This is the code within `simpletest.module` that makes this work:
+
+```php
+// simpletest_test_get_all()
+
+// ...
+
+$module_dir = DRUPAL_ROOT . '/' . dirname($filename);
+
+// Search both the 'lib/Drupal/mymodule' directory (for PSR-0 classes)
+// and the 'src' directory (for PSR-4 classes).
+foreach (array(
+ 'lib/Drupal/' . $name,
+ 'src',
+) as $subdir) {
+
+ // Build directory in which the test files would reside.
+ $tests_dir = $module_dir . '/' . $subdir . '/Tests';
+
+ // Scan it for test files if it exists.
+ if (is_dir($tests_dir)) {
+ $files = file_scan_directory($tests_dir, '/.*\\.php/');
+ if (!empty($files)) {
+ foreach ($files as $file) {
+
+ // Convert the file name into the namespaced class name.
+ $replacements = array(
+ '/' => '\\',
+ $module_dir . '/' => '',
+ 'lib/' => '',
+ 'src/' => 'Drupal\\' . $name . '\\',
+ '.php' => '',
+ );
+ $classes[] = strtr($file->uri, $replacements);
+ }
+ }
+ }
+}
+```
+
+It looks for a the tests directory (`src/Tests` or
+`lib/Drupal/{module_name}/Tests`) within the module, and then finds any `.php`
+files within it. It then converts the file name into the fully qualified
+(namespaced) class name and loads it automatically.
+
+### Running the Tests
+
+You can still run the tests from within the Simpletest UI, or from the command
+line using `run-tests.sh`.
+
+If you want to run a specific test case using the `--class` option, you will now
+need to include the fully qualified name.
+
+```
+php scripts/run-tests.sh --class Drupal\\opdavies\\Tests\\Functional\\OliverDaviesTest
+```
diff --git a/sculpin/source/_posts/published-my-first-docker-images-docker-hub-adr-tools-sculpin-rst2pdf.md b/sculpin/source/_posts/published-my-first-docker-images-docker-hub-adr-tools-sculpin-rst2pdf.md
new file mode 100644
index 000000000..bf2bba18d
--- /dev/null
+++ b/sculpin/source/_posts/published-my-first-docker-images-docker-hub-adr-tools-sculpin-rst2pdf.md
@@ -0,0 +1,97 @@
+---
+title: Published my first Docker images on Docker Hub (ADR Tools, Sculpin, rst2pdf)
+excerpt: I recently released my first images to the Docker Hub, for ADR Tools, the Sculpin site generator, and rst2pdf.
+date: 2021-04-20
+tags:
+ - docker
+ - rst2pdf
+ - sculpin
+---
+
+I've used Docker for some time for local development, making use of container images from Docker Hub and creating my own project-specific images, but I hadn't pushed any to [my Docker Hub profile](https://hub.docker.com/u/opdavies) for anyone else to use - until now.
+
+I've pushed several images to Docker Hub recently:
+
+- One for using [ADR Tools](https://github.com/npryce/adr-tools) to work with architectural decision records.
+- Two for generating and serving sites built with the [Sculpin static site generator](https://sculpin.io).
+- One for working with [rst2pdf](https://rst2pdf.org) that I use for presentation slides, with another image coming for watching and automatically re-compiling the PDF.
+
+
+
+
+
+
Image
+
+
+
+
+
+
+
+## ADR Tools
+
+- GitHub: https://github.com/opdavies/docker-image-adr-tools
+- Docker Hub: https://hub.docker.com/r/opdavies/adr-tools
+
+The ADR Tools image a simple one that allows me to initialise and configure ADR Tools itself as well as creating new ADR documents without needing to install `adr-tools` locally.
+
+For example:
+
+```
+# Initialise the ADR directory
+docker run --rm -v $(pwd):/adr opdavies/adr-tools init
+
+# List the current ADRs.
+docker run --rm -v $(pwd):/adr opdavies/adr-tools list
+
+# Create a new ADR.
+docker run --rm -v $(pwd):/adr opdavies/adr-tools new 'A new ADR'
+```
+
+## Sculpin
+
+- GitHub: https://github.com/opdavies/docker-image-sculpin-serve
+- Docker Hub: https://hub.docker.com/r/opdavies/sculpin, https://hub.docker.com/r/opdavies/sculpin-serve
+
+The Sculpin image repository contains two images - one for running Sculpin commands such as `sculpin content:create page` to create a new page, and one for generating and serving the Sculpin site that uses the `sculpin generate` command to generate and serve the site as well as watching for changes.
+
+```
+# Run a "sculpin" CLI command and "composer install" if needed.
+docker run --rm -v $(pwd):/app opdavies/sculpin
+
+# Generate and serve the Sculpin site.
+docker run --rm -p 8000:8000 -v $(pwd):/app opdavies/sculpin-serve
+```
+
+I've tested this with some of my own personal and client Sculpin projects, as well as the official [Sculpin Blog skeleton](https://github.com/sculpin/sculpin-blog-skeleton).
+
+
+
+
+
+
Image
+
+
+
+
+
+
+
+## rst2pdf
+
+- GitHub: https://github.com/opdavies/docker-image-rst2pdf
+- Docker Hub: https://hub.docker.com/r/opdavies/rst2pdf
+
+rst2pdf is a tool that I use primarily for presentation slide decks (I [gave a talk about this](/talks/building-presenting-slide-decks-rst2pdf) at one of the PHP South Wales meetups).
+
+The rst2pdf image installs Python and rst2pdf so that I can easily generate the PDF files from reStructuredText input files, including any extra arguments that are required.
+
+```
+# A simple example.
+docker run --rm -it -v $(pwd):/rst2pdf rst2pdf input.rst
+
+# An example including some additional arguments.
+docker run --rm -it -v $(pwd):/rst2pdf rst2pdf slides.rst -b2 -s main -e preprocess
+```
+
+I'm currently adding an `rst2pdf-watch` image too, similar to `sculpin-watch` that will watch the files using `nodemon` and automatically regenerate the PDF files when files change. I'm not entirely sure of the syntax for this yet but I'll push it to Docker Hub too once I've figured it out and have it working.
diff --git a/sculpin/source/_posts/published-my-first-npm-package.md b/sculpin/source/_posts/published-my-first-npm-package.md
new file mode 100644
index 000000000..2937b23e5
--- /dev/null
+++ b/sculpin/source/_posts/published-my-first-npm-package.md
@@ -0,0 +1,117 @@
+---
+title: Published my first NPM package
+date: 2018-12-16
+excerpt: Yesterday I published my first module onto NPM, and it’s a plugin for Tailwind CSS to be used alongside Vue.js.
+tags:
+ - npm
+ - tailwind-css
+ - vuejs
+---
+
+Yesterday I published my first module onto NPM, and it’s a plugin for [Tailwind
+CSS][tailwind] to be used alongside [Vue.js](https://vuejs.org).
+
+The plugin adds classes for showing and hiding elements in different display
+variations in combination with Vue's
+[v-cloak directive](https://vuejs.org/v2/api/#v-cloak), which I originally saw
+in [the first 'Building Kitetail' video](https://youtu.be/XUXpcbYQ_iQ?t=2360).
+These are useful for when you want an element to be visible whilst Vue is
+compiling, and hidden afterwards.
+
+Here is the compiled CSS that is added by the plugin:
+
+```css
+[v-cloak] .v-cloak-block {
+ display: block;
+}
+
+[v-cloak] .v-cloak-flex {
+ display: flex;
+}
+
+[v-cloak] .v-cloak-hidden {
+ display: none;
+}
+
+[v-cloak] .v-cloak-inline {
+ display: inline;
+}
+
+[v-cloak] .v-cloak-inline-block {
+ display: inline-block;
+}
+
+[v-cloak] .v-cloak-inline-flex {
+ display: inline-flex;
+}
+
+[v-cloak] .v-cloak-invisible {
+ visibility: hidden;
+}
+
+.v-cloak-block,
+.v-cloak-flex,
+.v-cloak-inline,
+.v-cloak-inline-block,
+.v-cloak-inline-flex {
+ display: none;
+}
+```
+
+The `v-cloak` directive exists on an element until Vue finishes compiling, after
+which it is removed. Therefore adding a `v-cloak-block` class to an element will
+make it `display: block` whilst Vue is compiling and the element is cloaked, and
+`display: none` afterwards when the Vue markup is compiled and rendered.
+
+In my `base.html.twig` template, I’ve added `v-cloak` to the wrapper div within
+the `body`.
+
+
+
+```twig
+
+
+ {# ... #}
+
+
+```
+
+
+
+Within my `navbar.html.twig` partial, I have a placeholder div that also
+contains the site name, which is instantly visible but has the `v-cloak-block`
+class so it’s hidden once Vue has compiled and the `Navbar` Vue component is
+visible instead.
+
+
+
+```twig
+
+
+
+ {{ site.title }}
+
+
+
+
+
+```
+
+
+
+I was originally surprised that these classes weren’t included as part of
+Tailwind or as part of an existing plugin, but as I’ve already used these styles
+on several projects that include Vue.js with Symfony or Sculpin, it made sense
+to extract it into a plugin and make it available as a npm package which I can
+easily add to any project - as well as making it easier to maintain if I need to
+add additional variations at a later point.
+
+**You can view [the package on npmjs.com][npm], and [the code repository on
+GitHub][github].**
+
+[github]: https://github.com/opdavies/tailwindcss-vuejs
+[npm]: https://www.npmjs.com/package/tailwindcss-vuejs
+[tailwind]: https://tailwindcss.com
diff --git a/sculpin/source/_posts/publishing-sculpin-sites-with-github-pages.md b/sculpin/source/_posts/publishing-sculpin-sites-with-github-pages.md
new file mode 100644
index 000000000..7f1c02137
--- /dev/null
+++ b/sculpin/source/_posts/publishing-sculpin-sites-with-github-pages.md
@@ -0,0 +1,112 @@
+---
+title: Publishing Sculpin Sites with GitHub Pages
+date: 2017-07-13
+excerpt: How I moved my website to GitHub pages.
+tags:
+ - github
+ - php
+ - sculpin
+meta:
+ image:
+ url: '/images/blog/jackson-octocat.png'
+ type: 'image/png'
+ height: 200
+ width: 451
+---
+
+

+
+Earlier this week I moved this site from my personal Linode server to [GitHub
+Pages][0].
+
+This made sense as I already kept the source code in [on GitHub][1], the issue
+was that GitHub Pages doesn’t know how to dynamically parse and generate a
+Sculpin site like it does with some other static site generators. It can though
+parse and serve HTML files, which is what Sculpin generates. It’s just a case of
+how those files are added to GitHub.
+
+I’ve seen different implementations of this, mostly where the Sculpin code is on
+one branch, and the generated HTML code is on a separate `gh-pages` or `master`
+branch (depending on your repository name). I’m not fond of this approach as it
+means automatically checking out and merging branches which can get messy, and
+also it’s weird to look at a repo’s branches page and see one branch maybe tens
+or hundreds of commits both ahead and behind the default branch.
+
+This has been made simpler and tidier now that we can use a `docs` directory
+within the repository to serve content.
+
+
+
+This means that I can simply re-generate the site after making changes and add
+it as an additional commit to my main branch with no need to switch branches or
+perform a merge.
+
+To simplify this, I’ve added a new [publish.sh script][3] into my repository to
+automate the sites. This is how it currently looks:
+
+```bash
+#!/usr/bin/env bash
+
+SITE_ENV="prod"
+
+# Remove the existing docs directory, build the site and create the new
+# docs directory.
+rm -rf ./docs
+vendor/bin/sculpin generate --no-interaction --clean --env=${SITE_ENV}
+touch output_${SITE_ENV}/.nojekyll
+mv output_${SITE_ENV} docs
+
+# Ensure the correct Git variables are used.
+git config --local user.name 'Oliver Davies'
+git config --local user.email oliver@oliverdavies.uk
+
+# Add, commit and push the changes.
+git add --all docs
+git commit -m 'Build.'
+git push origin HEAD
+```
+
+This begins by removing the deleting the existing `docs` directory and
+re-generating the site with the specified environment. Then I add a `.nojekyll`
+file and rename the output directory to replace `docs`.
+
+Now the changes can be added, committed and pushed. Once pushed, the new code is
+automatically served by GitHub Pages.
+
+## HTTPS
+
+GitHub Pages unfortunately does [not support HTTPS for custom domains][7].
+
+As the site was previously using HTTPS, I didn’t want to have to go back to
+HTTP, break any incoming links and lose any potential traffic. To continue using
+HTTPS, I decided to [use Cloudflare][6] to serve the site via their CDN which
+does allow for HTTPS traffic.
+
+## Next Steps
+
+- Enable automatically running `publish.sh` when new changes are pushed to
+ GitHub rather than running it manually. I was previously [using Jenkins][4]
+ and Fabric for this, though I’m also going to look into using Travis to
+ accomplish this.
+- Add the pre-build steps such as running `composer install` and `yarn` to
+ install dependencies, and `gulp` to create the front-end assets. This was
+ previously done by Jenkins in my previous setup.
+
+## Resources
+
+- [Publishing your GitHub Pages site from a /docs folder on your master
+ branch][2]
+- [Bypassing Jekyll on GitHub Pages][5]
+- [Secure and fast GitHub Pages with CloudFlare][6]
+
+[0]: https://pages.github.com
+[1]: https://github.com/opdavies/oliverdavies.uk
+[2]: https://help.github.com/articles/configuring-a-publishing-source-for-github-pages/#publishing-your-github-pages-site-from-a-docs-folder-on-your-master-branch
+[3]: https://github.com/opdavies/oliverdavies.uk/blob/master/publish.sh
+[4]: /blog/2015/07/21/automating-sculpin-jenkins
+[5]: https://github.com/blog/572-bypassing-jekyll-on-github-pages
+[6]: https://blog.cloudflare.com/secure-and-fast-github-pages-with-cloudflare
+[7]: https://github.com/blog/2186-https-for-github-pages
diff --git a/sculpin/source/_posts/queuing-private-messages-drupal-8.md b/sculpin/source/_posts/queuing-private-messages-drupal-8.md
new file mode 100644
index 000000000..9fcd1d1cf
--- /dev/null
+++ b/sculpin/source/_posts/queuing-private-messages-drupal-8.md
@@ -0,0 +1,84 @@
+---
+title: Queuing Private Messages in Drupal 8
+date: 2018-02-27
+excerpt: Introducing the Private Message Queue module for Drupal 8.
+tags:
+ - drupal
+ - drupal-8
+ - drupal-modules
+ - drupal-planet
+ - open-source
+---
+
+My current project at [Microserve][0] is a Drupal 8 website that uses the
+[Private Message][1] module for users to send messages to each other.
+
+In some cases though, the threads could contain hundreds of recipients so I
+decided that it would be good to queue the message requests so that they can be
+processed as part of a background process for better performance. The Private
+Message module does not include this, so I've written and released a separate
+[Private Message Queue][2] module.
+
+## Queuing a Message
+
+The module provices a `PrivateMessageQueuer` service
+(`private_message_queue.queuer`) which queues the items via the `queue()`
+method.
+
+The method accepts an array of `User` objects as the messsage recipients, the
+message body text and another user as the message owner. (I’m currently
+considering [whether to make the owner optional][4], and default to the current
+user if one is not specified)
+
+Here is an example:
+
+```php
+$recipients = $this->getRecipients(); // An array of User objects.
+$message = 'Some message text';
+$owner = \Drupal::currentUser();
+
+$queuer = \Drupal::service('private_message_queue.queuer');
+$queuer->queue($recipients, $message, $owner);
+```
+
+These three pieces of data are then saved as part of the queued item. You can
+see these by checking the "queue" table in the database or by running
+`drush queue-list`.
+
+
+
+```
+$ drush queue-list
+Queue Items Class
+private_message_queue 19 Drupal\Core\Queue\DatabaseQueue
+```
+
+## Processing the Queue
+
+The module also provides a `PrivateMessageQueue` queue worker, which processes
+the queued items. For each item, it creates a new private message setting the
+owner and the message body.
+
+It uses the `PrivateMessageThread` class from the Private Message module to find
+for an existing thread for the specified recipients, or creates a new thread if
+one isn't found. The new message is then added to the thread.
+
+The queue is processed on each cron run, so I recommend adding a module like
+[Ultimate Cron][3] so that you can process the queued items frequently (e.g.
+every 15 minutes) and run the heavier tasks like checking for updates etc less
+frequently (e.g. once a day).
+
+You can also process the queue manually with Drush using the
+`drush queue-run ` command - e.g.
+`drush queue-run private_message_queue`.
+
+```
+$ drush queue-run private_message_queue
+Processed 19 items from the private_message_queue queue in 3.34 sec.
+```
+
+[0]: {{site.companies.microserve.url}}
+[1]: https://www.drupal.org/project/private_message
+[2]: https://www.drupal.org/project/private_message_queue
+[3]: https://www.drupal.org/project/ultimate_cron
+[4]: https://www.drupal.org/project/private_message_queue/issues/2948233
diff --git a/sculpin/source/_posts/quick-project-switching-phpstorm.md b/sculpin/source/_posts/quick-project-switching-phpstorm.md
new file mode 100644
index 000000000..86b3c9807
--- /dev/null
+++ b/sculpin/source/_posts/quick-project-switching-phpstorm.md
@@ -0,0 +1,67 @@
+---
+title: Quick Project Switching in PhpStorm
+date: 2018-09-04
+excerpt: How to quickly switch between projects in PhpStorm.
+tags:
+ - phpstorm
+has_tweets: true
+---
+
+Following a recent conversation on Twitter with
+[socketwench](https://twitter.com/socketwench) about project switching in
+PhpStorm, I thought I’d document my workflow here.
+
+Here is the original tweet and my initial response. I also have a lot of
+PhpStorm projects, and as I’m always working on multiple projects I regularly
+need to switch between them.
+
+{% include 'tweet' with {
+ content: '
I think you can start typing and it will filter?
— Oliver Davies (@opdavies) August 28, 2018',
+} %}
+
+On the PhpStorm welcome screen that displays when you first open it, your recent
+projects are displayed on the left-hand side of the screen, and are filterable.
+That means that I can start typing a project name, e.g. `oli`, and I will only
+see projects that start with that input.
+
+{.with-border
+.with-padding}
+
+That’s great when opening a project from scratch, but what about when we’re
+already within a project and just want to be able to switch to another?
+
+{% include 'tweet' with {
+ content: '
You can also use 'Open recent' within the actions list, and then filter the list of projects. pic.twitter.com/k8G9iIQNP0
— Oliver Davies (@opdavies) August 28, 2018',
+} %}
+
+There’s also a way to access this list once PhpStorm is open, by clicking 'Open
+Recent' within the File menu. The issue here though is that this list is not
+filterable.
+
+You can also access this list using the keyboard, though the 'Search everywhere'
+or 'Find action' panes, and these are filterable.
+
+{.with-border
+.with-padding}
+
+Once the 'Open Recent' option is selected, you see the same project list as on
+the welcome screen, which is filtered in the same way by starting to type
+potential project names.
+
+{.with-border
+.with-padding}
+
+## Adding a Keyboard Shortcut
+
+We can make this easier by adding a new keyboard shortcut. Within the Keymap
+preferences, you can search for 'Open Recent' and right-click it to add a new
+keyboard shortcut and define the key combination.
+
+{.with-border
+.with-padding}
+
+{.with-border
+.with-padding}
+
+This this shortcut added, you can now use it to instantly bring up your recent
+projects list, filter it and switch project.
diff --git a/sculpin/source/_posts/quickest-way-install-sublime-text-2-ubuntu.md b/sculpin/source/_posts/quickest-way-install-sublime-text-2-ubuntu.md
new file mode 100644
index 000000000..96b673365
--- /dev/null
+++ b/sculpin/source/_posts/quickest-way-install-sublime-text-2-ubuntu.md
@@ -0,0 +1,25 @@
+---
+title: The Quickest way to Install Sublime Text 2 in Ubuntu
+date: 2013-03-02
+excerpt: After reading numerous blog posts about how to install Sublime Text 2 in Ubuntu, this is definitely the quickest way!
+tags:
+ - linux
+ - sublime-text
+ - ubuntu
+---
+
+After reading numerous blog posts about how to install
+[Sublime Text 2](http://www.sublimetext.com/2 'Sublime Text 2') in
+[Ubuntu](http://www.ubuntu.com/2 'Ubuntu'), this is definitely the quickest way!
+
+Just paste the following lines into your Terminal:
+
+```bash
+$ sudo add-apt-repository ppa:webupd8team/sublime-text-2
+$ sudo apt-get update
+$ sudo apt-get install sublime-text
+```
+
+After running this, Sublime Text 2 has been installed within the
+_/usr/lib/sublime-text-2_ directory and can be launched from the Dashboard, or
+by typing `subl`, `sublime-text` or `sublime-text-2` into a Terminal window.
diff --git a/sculpin/source/_posts/quickly-apply-patches-using-git-curl-or-wget.md b/sculpin/source/_posts/quickly-apply-patches-using-git-curl-or-wget.md
new file mode 100644
index 000000000..4af017200
--- /dev/null
+++ b/sculpin/source/_posts/quickly-apply-patches-using-git-curl-or-wget.md
@@ -0,0 +1,29 @@
+---
+title: Quickly Apply Patches Using Git and curl or wget
+date: 2013-12-24
+excerpt: How to quickly download a patch file and apply it to a Git repository in one line.
+tags:
+ - drupal-planet
+ - git
+---
+
+Testing a patch file is usually a two-step process. First you download the patch
+file from the source, and then you run a separate command to apply it.
+
+You can save time and typing by running the two commands on one line:
+
+```bash
+$ curl http://drupal.org/files/[patch-name].patch | git apply -v
+```
+
+Or, if you don't have curl installed, you can use wget:
+
+```bash
+$ wget -q -O - http://drupal.org/files/[patch-name].patch | git apply -v
+```
+
+These commands need to be run within the root of your Git repository (i.e. where
+the .git directory is).
+
+These snippets were taken from
+[Applying Patches with Git](https://drupal.org/node/1399218) on Drupal.org.
diff --git a/sculpin/source/_posts/quickly-import-multiples-images-using-imagefieldimport-module.md b/sculpin/source/_posts/quickly-import-multiples-images-using-imagefieldimport-module.md
new file mode 100644
index 000000000..7bcab6b6d
--- /dev/null
+++ b/sculpin/source/_posts/quickly-import-multiples-images-using-imagefieldimport-module.md
@@ -0,0 +1,44 @@
+---
+title: Quickly Import Multiples Images Using the Imagefield_Import Module
+date: 2010-05-29
+excerpt: How to use the Imagefield Import module.
+tags:
+ - cck
+ - drupal
+ - drupal-6
+ - drupal-planet
+ - imagefield
+ - imagefield-import
+ - photo-gallery
+---
+
+**Thanks to Bob at [Mustardseed Media](http://mustardseedmedia.com) for
+[tweeting](http://twitter.com/mustardseedinc/status/14713096905) about this
+module. It's undoubtedly saved me hours of work today alone!**
+
+I've recently started a personal project converting a website to Drupal. It's
+currently a static HTML/CSS site which also uses the
+[Coppermine Photo Gallery](http://coppermine-gallery.net). As part of building
+the new website, I wanted to move all the photos from the existing site onto the
+new one. However, with 1260 photos in 17 albums, this could have been a lengthy
+process!
+
+I created a new Drupal-powered Gallery as described in
+[this screencast](http://lullabot.com/articles/photo-galleries-views-attach) by
+[Jeff Eaton](http://twitter.com/eaton) - using the CCK and Imagefield modules,
+and re-created each of my existing Gallery nodes. Using the
+[Imagefield_Import](http://drupal.org/project/imagefield_import) module, I was
+then able to quickly import the photos into the new Galleries.
+
+I downloaded all the photos from the previous Gallery via FTP, and installed and
+configured the Imagefield_Import module.
+
+I created an 'Import' folder, selected the target field and mode. In this case,
+I want each image to be imported into its own Photo node. I moved the photos for
+the first album into the Import folder, and loaded the 'Import Images' screen
+(admin/content/imagefield_import).
+
+After clicking 'Import', a node is created for each photo, the image is
+uploaded, and added to the selected Gallery.
+
+Just another 1248 photos to go...
diff --git a/sculpin/source/_posts/rebuilding-bartik-drupals-default-theme-vuejs-tailwind-css-part-2.md b/sculpin/source/_posts/rebuilding-bartik-drupals-default-theme-vuejs-tailwind-css-part-2.md
new file mode 100644
index 000000000..0a1001458
--- /dev/null
+++ b/sculpin/source/_posts/rebuilding-bartik-drupals-default-theme-vuejs-tailwind-css-part-2.md
@@ -0,0 +1,313 @@
+---
+title: Rebuilding Bartik (Drupal’s Default Theme) with Vue.js and Tailwind CSS - part 2
+date: 2018-12-27
+excerpt: A follow-up to my original post on rebuilding Bartik with Tailwind and Vue.js.
+tags:
+ - drupal
+ - tailwind-css
+ - tweet
+ - vuejs
+has_tweets: true
+---
+
+In [the original post](/blog/rebuilding-bartik-with-vuejs-tailwind-css) I
+detailed how I built [a clone of Drupal’s Bartik theme][netlify] with
+[Vue.js][vuejs] and [Tailwind CSS][tailwind]. This follow-up post details some
+updates that I’ve made to it since then.
+
+## Customising Tailwind’s colours
+
+During the first version of the page, my thoughts were to not edit the Tailwind
+configuration, however I changed my mind on this whilst working on the
+subsequent updates and did make some changes and customisations to the
+`tailwind.js` file.
+
+By default, Tailwind includes a full colour palette including colours such as
+yellows, oranges, reds that weren’t being used in this page so they were
+removed. This makes the file more readable as well as reduces the number of
+classes that Tailwind generates.
+
+Whist I was changing the colours, I also took the opportunity to tweak the
+values of the remaining colours to more closely match Bartik’s original colours.
+
+I also added a `black-60` class which uses
+[RGBA](https://css-tricks.com/the-power-of-rgba) to provide a semi-transparent
+background. I used this when adding the
+[skip to main content link](#adding-the-skip-to-main-content-link).
+
+```js
+let colors = {
+ transparent: 'transparent',
+
+ black: '#22292f',
+ 'grey-darkest': '#3d4852',
+ 'grey-dark': '#8795a1',
+ grey: '#b8c2cc',
+ 'grey-light': '#dae1e7',
+ 'grey-lighter': '#f0f0f0',
+ 'grey-lightest': '#F6F6F2',
+ white: '#ffffff',
+
+ 'black-60': 'rgba(0, 0, 0, .6)',
+
+ 'blue-dark': '#2779bd',
+ blue: '#3490dc',
+ 'blue-light': '#bcdefa',
+
+ 'green-dark': '#325E1C',
+ green: '#77B159',
+ 'green-light': '#CDE2C2',
+ 'green-lighter': '#F3FAEE',
+};
+```
+
+## Adding default styling for links
+
+In the first version, every link was individually styled which resulted in a lot
+of duplicate classes and a potential maintenance issue.
+
+I added a `style` section within `Welcome.vue`, and added some default styling
+for links based on their location on the page -
+[extracting some Tailwind components](https://tailwindcss.com/docs/extracting-components).
+
+
+```html
+
+ ...
+
+
+
+```
+
+Within the `style` section, I’m able to use Tailwind’s custom `@apply` directive
+to inject it’s rules into more traditional CSS, rather than needing to add them
+onto every link.
+
+```vue-html
+
+```
+
+
+
+## Extracting a Vue component for Drupal blocks
+
+As well as being able to extract re-usable components within Tailwind, the same
+can be done within Vue. As the page could potentially have multiple sidebar
+blocks, I extracted a `SidebarBlock` component which would act as a wrapper
+around the block’s contents.
+
+```vue-html
+// src/components/Sidebar.vue
+
+
+
+
+
+
+```
+
+The component provides the wrapping div and the appropriate classes in a single
+easy-to-maintain location, and
+[uses a slot](https://vuejs.org/v2/guide/components-slots.html) as a placeholder
+for the main content.
+
+That means that within `Welcome.vue`, the markup within the `sidebar-block` tags
+will be used as the block contents.
+
+```html
+
+
My block contents.
+
+```
+
+## Adding the Skip to Main Content Link
+
+One thing
+[that was missing](https://github.com/opdavies/rebuilding-bartik/issues/1) was
+the 'Skip to main content link'. This an accessibility feature that allows for
+users who are navigating the page using only a keyboard to bypass the navigation
+links and skip straight to the main content if they wish by clicking a link that
+is hidden and only visible whilst it’s focussed on.
+
+Here is the markup that I used, which is placed directly after the opening
+`` tag.
+
+```html
+
+ Skip to main content
+
+```
+
+I initially tried to implement the same feature on this website using
+[Tailwind’s visually hidden plugin](https://www.npmjs.com/package/tailwindcss-visuallyhidden)
+which also contains a `focussable` class, though I wasn’t able to style it the
+way that I needed. I created my own
+[Tailwind skip link plugin](https://www.npmjs.com/package/tailwindcss-skip-link)
+and moved the re-usable styling there.
+
+To enable the plugin, I needed to add it within the `plugins` section of my
+`tailwind.js` file:
+
+```js
+plugins: [
+ require('tailwindcss/plugins/container')(),
+ require('tailwindcss-skip-link')(),
+],
+```
+
+I added only the page-specific styling classes to the link (as well as the
+`skip-link` class that the plugin requires) as well as my own focus state to the
+skip link that I did within the `style` section of `App.vue`.
+
+```vue-html
+
+```
+
+{.border}
+
+## Adding the DrupalMessage component
+
+I also added a version of Drupal’s status message as another Vue component. This
+also uses a slot to include the message contents and accepts a
+[prop](https://vuejs.org/v2/guide/components-props.html) for the message type.
+
+```html
+
+
+
+
+
+
+
+
+```
+
+The value of the `type` prop is then used within some computed properties to
+determine the type specific classes to add (e.g. green for success, and red for
+warning), as well as whether or not to include the checkmark SVG image.
+
+```js
+
+```
+
+I did need to make one change to the `tailwind.js` file in order to change the
+border on links when they are hovered over - within `modules` I needed to enable
+the `borderStyle` module for hover and focus states in order for Tailwind to
+generate the additional classes.
+
+```js
+modules: {
+ // ...
+ borderStyle: ['responsive', 'hover', 'focus'],
+ // ...
+}
+```
+
+The message is included within the Welcome component by including the
+`` element, though rather than importing it there, it’s
+registed as a global component so it would be available to any other components
+that could be added in the future.
+
+This is done within `main.js`:
+
+```js
+// ...
+
+Vue.component('drupal-message', require('@/components/DrupalMessage').default);
+
+new Vue({
+ render: h => h(App),
+}).$mount('#app');
+```
+
+{.border}
+
+**The updated version is [live on Netlify][netlify], and the [latest source code
+is available on GitHub][github].**
+
+[github]: https://github.com/opdavies/rebuilding-bartik
+[netlify]: https://rebuilding-bartik.oliverdavies.uk
+[tailwind]: https://tailwindcss.com
+[vuejs]: https://vuejs.org
diff --git a/sculpin/source/_posts/rebuilding-bartik-drupals-default-theme-vuejs-tailwind-css.md b/sculpin/source/_posts/rebuilding-bartik-drupals-default-theme-vuejs-tailwind-css.md
new file mode 100644
index 000000000..2e8b03449
--- /dev/null
+++ b/sculpin/source/_posts/rebuilding-bartik-drupals-default-theme-vuejs-tailwind-css.md
@@ -0,0 +1,345 @@
+---
+title: Rebuilding Bartik (Drupal’s Default Theme) with Vue.js and Tailwind CSS
+date: 2018-11-20
+excerpt: How I rebuilt Drupal’s Bartik theme using Vue.js and Tailwind CSS.
+tags:
+ - drupal
+ - tailwind-css
+ - tweet
+ - vuejs
+has_tweets: true
+---
+
+Earlier this week, I built a clone of [Drupal][0]’s default theme, Bartik, with
+[Vue.js][1] and [Tailwind CSS][2]. You can [view the code on GitHub][3] and the
+[site itself on Netlify][4].
+
+{% include 'tweet' with {
+ content: '
— Oliver Davies (@opdavies) November 20, 2018',
+ data_cards: true,
+} %}
+
+## Why build a Bartik clone?
+
+I’m a big fan of utility based styling and Tailwind CSS in particular, I was and
+originally thinking of a way to more easily integrate Tailwind within Drupal -
+something like I’ve since done with the [Tailwind CSS starter kit theme][5].
+Whilst thinking about that, I wondered about doing the opposite - rebuilding
+Drupal (or Bartik) with Tailwind.
+
+Others including [Adam Wathan](https://adamwathan.me) (one of the creators of
+Tailwind CSS) have rebuilt existing UIs like Netlify, YouTube, Twitter, Coinbase
+and Transistor.fm with Tailwind as an opportunity for learning and also to
+demonstrate using Tailwind - this was my opportunity to do the same.
+
+Whilst
+[Drupal itself has adoped React](https://dri.es/drupal-looking-to-adopt-react),
+I’ve personally been looking into Vue.js and have used it for some small
+personal projects, including some elements of the site. So I decided to use Vue
+for the interactive parts of my Bartik clone to create a fully functional clone
+rather than focussing only on the CSS.
+
+## Building a static template with Tailwind
+
+The first stage was to build the desktop version, which was done as a simple
+HTML file with Tailwind CSS pulled in from it’s CDN. This stage took just over
+an hour to complete.
+
+As Tailwind was added via a CDN, there was no opportunity to customise it’s
+configuration, so I needed to use to Tailwind’s default configuration for
+colours, padding, spacing, fonts, breakpoints etc. The page is built entirely
+with classes provided by Tailwind and uses no custom CSS, except for one inline
+style that is used to add the background colour for the Search block, as there
+wasn’t a suitable Tailwind option.
+
+When I decided that I was going to later add some interactivity onto the mobile
+navigation menu, the existing code was ported into a new Vue.js application
+generated by the Vue CLI, with the majority of the markup within a `Welcome`
+component. This meant that Tailwind was also added as a dependency with it’s own
+configuration file, though although I had the opportunity to customise it I
+decided not to and made no changes to it and continued with the default values.
+
+`src/App.vue`:
+
+```
+
+
+
+
+
+
+````
+
+
+## Making it responsive
+
+The second stage began with making the existing desktop version responsive - particularly making the navigation menu behave and appear differently on mobile and tablet screens, and stacking the main content area and the sidebar on mobile screens. This was all achieved using Tailwind’s responsive variants.
+
+```html
+
+ ...
+
+````
+
+In this example, the `pb-4` class adds 1rem of bottom padding to the element by
+default, then increases it to 3rem at large screen sizes due to the `lg:pb-12`
+class.
+
+## Adding interactivity
+
+This is how the main navigation menu works on mobile:
+
+
+
+The show and hide text appears next to a hamburger menu, and clicking it toggles
+the visiblity of the menu links which are stacked below, as well as the wording
+of the text itself.
+
+The code for this was moved into a separate `MainMenu` component, which means
+that it was easier to have dedicated data properties for whether the menu was
+open or not, as well as computed properties for building the show/hide text. The
+`open` value can then be used to apply the appropriate classes to the main menu
+to toggle it.
+
+I also moved the links into `data` too - each link is it’s own object with it's
+`title` and `href` values. This means that I can use a `v-for` directive to loop
+over the data items and inject dynamic values, removing the duplication of
+markup which makes the component easier to read and maintain.
+
+`src/components/MainMenu.vue`:
+
+
+
+```vue-html
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## The result
+
+The whole task only took around two hours to complete, and although some of the
+colours and spacings are slightly different due to the decision to stick with
+the default Tailwind configuration values, I’m happy with the result.
+
+### The original version
+
+
+
+### The Vue.js and Tailwind CSS version
+
+
+
+
+I’ve also made some additional changes since this version, which are described in [this follow-up post](/blog/rebuilding-bartik-with-vuejs-tailwind-css-part-2).
+
+
+[0]: https://www.drupal.org
+[1]: https://vuejs.org
+[2]: https://tailwindcss.com
+[3]: https://github.com/opdavies/rebuilding-bartik
+[4]: https://rebuilding-bartik.oliverdavies.uk
+[5]: https://www.drupal.org/project/tailwindcss
diff --git a/sculpin/source/_posts/reflections-speaking-unifieddiff.md b/sculpin/source/_posts/reflections-speaking-unifieddiff.md
new file mode 100644
index 000000000..72eac3891
--- /dev/null
+++ b/sculpin/source/_posts/reflections-speaking-unifieddiff.md
@@ -0,0 +1,40 @@
+---
+title: Reflections on speaking at UnifiedDiff
+date: 2012-09-06
+excerpt: Yesterday evening I went along and spoke at the UnifiedDiff meetup in Cardiff.
+tags:
+ - speaking
+---
+
+Yesterday evening I went along and spoke at the
+[UnifiedDiff meetup](http://www.unifieddiff.co.uk) in Cardiff, having offered
+previously to do a presentation providing an introduction to Drupal.
+
+I'm an experienced Drupal Developer, but not an experienced public speaker
+(although I have done several user training sessions and Drupal demonstrations
+for clients previously), and I think that some of the nerves that I had
+beforehand were apparent during the presentation, and being the first speaker
+for the evening probably didn't help, although I did get a
+[nice tweet](https://twitter.com/craigmarvelley/status/243418608720543745)
+mid-way through.
+
+Initially, after aiming for a 20-minute presentation plus Q&A, I think I wrapped
+up the presentation in around 14 minutes, although I did about 6 minutes of
+answering questions afterwards including the apparently mandatory "Why use
+Drupal compared to WordPress or Joomla?" question, some Drupal 8 and Symfony
+questions, as well as an interesting question about the White House development
+project after I'd listed it within a list of example sites. Next time, I think
+that some more detailed presenter notes are needed. Typically, as soon as it sat
+back in my seat, the majority of things that I'd managed to remember beforehand
+all came flooding back to me and I thought "I should have said that whilst I was
+up speaking".
+
+Overall, considering my inexperience at speaking to this type of audience, I was
+fairly happy with my presentation, although I'm sure that I'll change my mind
+once I've watched the video of it on the UnifiedDiff website. Regardless, I
+think that it was a great experience and I enjoyed doing it, and I'd like to
+thank the organisers of UnifiedDiff for having me speak at their meetup. It was
+great to have a more relaxed conversation with some people after the other
+speakers had been up, and having introduced Drupal I would be more than happy to
+come back and do a more in-depth presentation if there is an interest for me to
+do so.
diff --git a/sculpin/source/_posts/renaming-gray-grey-tailwind-css.md b/sculpin/source/_posts/renaming-gray-grey-tailwind-css.md
new file mode 100644
index 000000000..25194d357
--- /dev/null
+++ b/sculpin/source/_posts/renaming-gray-grey-tailwind-css.md
@@ -0,0 +1,32 @@
+---
+title: Renaming gray to grey in Tailwind CSS
+excerpt: How to change the colour "gray" to "grey" in Tailwind CSS.
+tags:
+ - tailwind-css
+date: 2020-09-04
+---
+
+In `tailwind.config.js`:
+
+```
+const { colors } = require('tailwindcss/defaultTheme')
+
+module.exports = {
+ purge: ["./public/**/*.html"],
+ theme: {
+ extend: {
+ colors: {
+ // Remove the "gray" colours from the theme.
+ gray: {},
+
+ // Create a new set of "grey" colours, using the original "gray" values.
+ grey: colors['gray']
+ }
+ },
+ },
+ variants: {},
+ plugins: [],
+};
+```
+
+Based on a configuration file from https://github.com/tailwindlabs/tailwindcss-playground.
diff --git a/sculpin/source/_posts/restructuring-my-tailwindjs-configuration-files.md b/sculpin/source/_posts/restructuring-my-tailwindjs-configuration-files.md
new file mode 100644
index 000000000..59bc62c2b
--- /dev/null
+++ b/sculpin/source/_posts/restructuring-my-tailwindjs-configuration-files.md
@@ -0,0 +1,236 @@
+---
+title: Restructuring my tailwind.js configuration files
+date: 2019-03-08
+excerpt: How I’ve started structuring my tailwind.js configuration files in preparation for Tailwind 1.0.
+tags:
+ - laravel-mix
+ - tailwind-css
+---
+
+After watching Adam Wathan’s recent
+["Working on Tailwind 1.0" YouTube video](https://www.youtube.com/watch?v=SkTKN38wSEM)
+and seeing some of the proposed changes to the `tailwind.js` configuration file,
+I’ve started to structure my current config files a little differently in
+preparation for 1.0.
+
+## The current tailwind.js file format
+
+Currently when you run `tailwind init` to create a new config file, it includes
+all of Tailwind’s default values, and then you can add, edit and remove values
+as needed.
+
+Some values like colours, font families, plugins and modules you are likely to
+change for each project, whilst others like shadows, leading, z-index and
+opacity, you’re less likely to need to change.
+
+It’s 952 lines including comments, which is quite long and could potentially be
+daunting for new Tailwind users.
+
+The contents of the full file can be found in the
+[Tailwind CSS documentation](https://tailwindcss.com/docs/configuration#default-configuration),
+or it can be found in
+[various GitHub repositories](https://github.com/tailwindcss/plugin-examples/blob/master/tailwind.js).
+
+## A preview of the new tailwind.js file format
+
+In Adam’s [Laracon Online](https://laracon.net) talk, Tailwind CSS by Example,
+he showed the new configuration file format. Here is a snippet:
+
+```js
+module.exports {
+ theme: {
+ extend: {
+ spacing: {
+ 7: '1.75rem',
+ },
+ },
+ colors: {
+ white: {
+ default: '#fff',
+ 20: 'rgba(255,255,255,.2)',
+ 40: 'rgba(255,255,255,.4)',
+ 60: 'rgba(255,255,255,.6)',
+ 80: 'rgba(255,255,255,.8)',
+ },
+ ...
+ }
+ ...
+ }
+}
+```
+
+You’ll notice that the structure of the file is quite different, and that all of
+the default values have been removed and are now maintained by Tailwind itself.
+
+This means that the configuration file contains only your custom changes, where
+you've either overridden a default value (e.g. colours) or added your own using
+`extend` (e.g. adding another spacing value, as in this example).
+
+I think that's a great improvement and makes the file so much cleaner, and
+easier to read and understand.
+
+## An interim approach
+
+If you don’t want to wait until 1.0, or potentially 0.8, you can get some of
+this functionality now by restructuring your Tailwind configuration file.
+
+Here is the complete `tailwind.js` file for the
+[DrupalCamp Bristol 2019 static landing page](https://dcb-2019-static.netlify.com),
+which uses Tailwind in addition to the existing traditional CSS:
+
+```js
+let defaultConfig = require('tailwindcss/defaultConfig')();
+
+var colors = {
+ ...defaultConfig.colors,
+ black: '#000',
+};
+
+module.exports = {
+ ...defaultConfig,
+ colors: colors,
+ textColors: colors,
+ backgroundColors: colors,
+ borderColors: Object.assign({ default: colors['grey-light'] }, colors),
+ plugins: [
+ require('tailwindcss-interaction-variants')(),
+ require('tailwindcss-spaced-items'),
+ ],
+ modules: {
+ ...defaultConfig.modules,
+ textStyle: [...defaultConfig.modules.textStyle, 'hocus'],
+ },
+ options: {
+ ...defaultConfig.options,
+ prefix: 'tw-',
+ important: true,
+ },
+};
+```
+
+Here are the steps that I took to create this file:
+
+
+
+
**Get the default configuration**. This is done using `require('tailwindcss/defaultConfig')()`. Essentially this has the same contents as the current `tailwind.js` file, though now it’s owned and maintained within Tailwind itself, and not by the user.
+
Also any new or updated values within the default configuration will be automatically available.
+
This line is present but commented out in the current generated `tailwind.js` file.
+
+
+
+
**Create the colors object.** This will by default override Tailwind’s default colours, however you can add `...defaultConfig.colors` to include them and then add or edit values as needed.
+
In this example, I’m overridding the value used for the `black` colour classes to match the existing colour in the other CSS.
+
+
+
+
**Return the main configuration object.** For sites with no overrides, this could just be `module.exports = defaultConfig` for a minimal configuration.
+
To extend the defaults, add `...defaultConfig` at the beginning.
+
+
+
+
**Assign our colours.** Use them for `colors`, `textColors`, `backgroundColors` and `borderColours`.
+
+
+
+
**Add any plugins**. I use plugins on most projects, in this case I’m using [tailwindcss-interaction-variants](https://www.npmjs.com/package/tailwindcss-interaction-variants) and [tailwindcss-spaced-items](https://www.npmjs.com/package/tailwindcss-spaced-items). Usually the default `container` plugin would be here too.
+
+
+
+
**Add or override modules.** Here I’m adding the `hocus` (hover and focus) variant provided by the interaction variants plugin to the text style classes.
+
+
+
+
**Add or override options.** As this markup was originally from a Drupal website, I needed to override some of the options values. I’ve added the `tw-` prefix to avoid Tailwind classes from clashing with Drupal’s default markup, and set all Tailwind classes to use `!important` so that they override any existing styles.
+
+
+
+This file is only 27 lines long, so considerably shorter than the default file,
+and I think that it’s much easier to see what your additional and overridden
+values are, as well able to quickly recognise whether a class is generated from
+a custom value or from a Tailwind default value.
+
+To move this file to the new format I think would be much easier as there’s no
+default configuration to filter out, and you can move across only what is
+needed.
+
+## Other changes
+
+### Consistent spacing for padding and margin
+
+Similar to defining colours, you could also set some standard spacing values,
+and using those for padding, margin and negative margin to ensure that they are
+all consistent.
+
+In this case, we can use `defaultConfig.margin` to get the default, add or
+override any values, and then assign it to the relevant sections of the main
+object.
+
+```js
+const spacing = {
+ ...defaultConfig.margin,
+ '2px': '2px',
+};
+
+module.exports = {
+ ...defaultConfig,
+ // ...
+ padding: spacing,
+ margin: spacing,
+ negativeMargin: spacing,
+ // ...
+};
+```
+
+### Picking values with lodash
+
+In the opposite to extending, if we wanted to limit the number of values within
+a part of the configuration, we can do that too. I’d suggest using the
+[pick method](https://lodash.com/docs/4.17.11#pick) provided by
+[Lodash](https://lodash.com).
+
+From the documentation:
+
+> Creates an object composed of the picked object properties.
+
+For example, if we only wanted normal, medium and bold font weights:
+
+```js
+module.exports = {
+ ...defaultConfig,
+ // ...
+ fontWeights: _.pick(defaultConfig.fontWeights, ['normal', 'medium', 'bold']),
+ // ...
+};
+```
+
+### Renaming the file
+
+Also in Tailwind 1.0, it seems that the configuration file name is changing from
+`tailwind.js` to `tailwind.config.js`.
+
+If you use [Laravel Mix](https://laravel-mix.com) and the
+[Laravel Mix Tailwind plugin](https://github.com/JeffreyWay/laravel-mix-tailwind)
+like I do on this site (even though it’s a Sculpin site), it will look for a
+`tailwind.js` file by default or you can specify whatever filename you need.
+
+Here is an excerpt of the Tailwind configuration file for this site, using
+`tailwind.config.js`:
+
+```js
+mix
+ .postCss('assets/css/app.css', 'source/dist/css')
+ .tailwind('tailwind.config.js');
+```
+
+## Looking foward to Tailwind CSS 1.0!
+
+Adam has said that Tailwind 1.0 should be released within a few weeks of the
+time of writing this, I assume once
+[the 1.0 To-Do list](https://github.com/tailwindcss/tailwindcss/issues/692) is
+completed.
+
+I really like some of the improvements that are coming in 1.0, including the new
+configuration file format and the ability to easily add and extend values, as
+well as the file itself now being completely optional.
+
+I can’t wait for it to land!
diff --git a/sculpin/source/_posts/review-adminhover-module.md b/sculpin/source/_posts/review-adminhover-module.md
new file mode 100644
index 000000000..fcca76958
--- /dev/null
+++ b/sculpin/source/_posts/review-adminhover-module.md
@@ -0,0 +1,58 @@
+---
+title: Review of the Admin:hover Module
+date: 2010-08-10
+excerpt: My review of Drupal’s admin:hover module.
+tags:
+ - admin:hover
+ - administration
+ - drupal-6
+ - drupal-modules
+ - drupal-planet
+---
+
+Sorry for the lack of Blog posts lately, but
+[my new job](http://horseandcountry.tv) that I started a few weeks ago has
+certainly been keeping me busy! I've got a few more posts that I'm preparing
+content for, and I'll hopefully be back into my weekly-post routine before too
+long!
+
+Today, I'd like to just give a quick overview of the
+[Admin:hover](http://drupal.org/project/admin_hover) module. It basically adds
+an administrative menu that pops up when you hover over a node or block within
+your Drupal website - the kind of functionality that was present within previous
+versions of the [Admin module](http://drupal.org/project/admin). It also
+integrates well with the [Devel](http://drupal.org/project/devel) and
+[Clone](http://drupal.org/project/node_clone) modules.
+
+I've found this to be extremely useful whilst working on photo galleries etc.
+where multiple nodes are displayed in a grid format and I quickly need to
+publish or unpublish something for testing purposes. No longer do I need to open
+each node, or go into the administration area to perform the required actions.
+
+It is also possible to customise which links are available from within the
+adminstration area. The possible selections that I currently have on this site
+are as follows:
+
+**Node links:**
+
+- Edit
+- Publish
+- Unpublish
+- Promote
+- Unpromote
+- Make sticky
+- Make unsticky
+- Delete
+- Clone
+- Dev load
+- View author
+- Edit author
+- Add
+
+**Block links:**
+
+- Configure block
+- Add block
+
+Although, as I have additional contributed modules installed, some of these may
+not neccassaily be available out of the box.
diff --git a/sculpin/source/_posts/review-image-caption-module.md b/sculpin/source/_posts/review-image-caption-module.md
new file mode 100644
index 000000000..d7983ec09
--- /dev/null
+++ b/sculpin/source/_posts/review-image-caption-module.md
@@ -0,0 +1,40 @@
+---
+title: Review of the Image Caption Module
+date: 2010-08-20
+excerpt: My review of Drupal’s Image Caption module.
+tags:
+ - drupal
+ - drupal-6
+ - drupal-planet
+ - image-caption
+ - imagefield
+---
+
+Up until as recent as last week, whenever I added an image into one of my Blog
+posts, I was manually adding the caption below each image and styling it
+accordingly. That was until I installed the
+[Image Caption](http://drupal.org/project/image_caption) module.
+
+The Image Caption module uses jQuery to dynamically add captions to images. Here
+is a walkthrough of the process that I followed to install and configure the
+module. As always, I used Drush to download and enable the module, then visited
+the Image Caption Settings page (admin/settings/image_caption). Here, I select
+which node types should be included in image captioning. In my case, I only
+wanted this to apply to Blog posts.
+
+As I use the [FileField](http://drupal.org/project/filefield),
+[ImageField](http://drupal.org/project/imagefield) and
+[Insert](http://drupal.org/project/insert) modules to add images to my posts, as
+opposed to via a WYSIWYG editor, I'm able to add the CSS class of 'caption' to
+my images.
+
+Now, all images inserted this way will have the CSS class of 'caption'.
+
+As the Image Caption module uses the image's title tag to create the displayed
+caption, I enabled the custom title text for my Image field so that when I
+upload an image, I'm prompted to enter text for the caption.
+
+This results in a span called `image-caption-container` around the inserted
+image, and a caption below it called `image-caption` containing the text.
+
+All that's left is to style these classes within your CSS stylesheet.
diff --git a/sculpin/source/_posts/review-teleport-module.md b/sculpin/source/_posts/review-teleport-module.md
new file mode 100644
index 000000000..3aa995c7e
--- /dev/null
+++ b/sculpin/source/_posts/review-teleport-module.md
@@ -0,0 +1,39 @@
+---
+title: Review of the Teleport Module
+date: 2010-07-12
+excerpt: My review of Drupal’s Teleport module.
+tags:
+ - drupal-planet
+ - drupal-6
+ - drupal-modules
+ - teleport
+---
+
+As a heavily-reliant
+[Quicksilver](http://en.wikipedia.org/wiki/Quicksilver_%28software%29) user on
+my MacBook Pro, I was glad when I found the
+[Teleport](http://drupal.org/project/teleport) module for
+[Drupal](http://drupal.org) _(due to Elliott Rothman's
+[tweet](http://twitter.com/elliotttt/status/18044234238))_.
+
+When you press a configurable hot-key, a jQuery dialog box appears where you can
+search for nodes by title or path, or directly enter the path that you want to
+navigate to. This will greatly reduce the number of clicks that I need to
+perform to get to my desired page - even compared to the
+[Admin](http://drupal.org/project/admin) and
+[Administration Menu](http://drupal.org/project/admin_menu) modules.
+
+Although it's not a new module (the first commits were 2 years ago), I hope that
+they are still planning on achieving the list of future directions listed on
+their Drupal.org project page:
+
+- Make interface act more like Quicksilver (i.e. you should only have to press
+ Enter once to launch)
+- 'Actions' like Quicksilver: if you select a node, a second input should appear
+ with options to go to the View page, Edit page, (un)publish, etc. Same with
+ users.
+- Hook into more non-node content, like taxonomy terms and functions in the API
+ module.
+
+Personally, this will make navigation around both the front-end and
+administration area of my Drupal sites so much easier.
diff --git a/sculpin/source/_posts/running-drupal-88-symfony-local-server.md b/sculpin/source/_posts/running-drupal-88-symfony-local-server.md
new file mode 100644
index 000000000..33de2ccc2
--- /dev/null
+++ b/sculpin/source/_posts/running-drupal-88-symfony-local-server.md
@@ -0,0 +1,323 @@
+---
+title: Running Drupal 8.8 with the Symfony Local Server
+excerpt: How to use Symfony's local web server to run a Drupal 8.8 website.
+date: 2020-03-09
+tags:
+ - drupal
+ - drupal-8
+ - symfony
+---
+
+
+
+
+
+## Installation
+
+
+
+The Symfony server is bundled as part of the `symfony` binary that is available
+to download from .
+
+To install it, run this command:
+
+```bash
+curl -sS https://get.symfony.com/cli/installer | bash
+```
+
+Even though it’s by Symfony, the local webserver works with any type of
+project - including Drupal 8 (and 9) and Drupal 7.
+
+## Getting started
+
+Here are the basic commands to start and stop the server:
+
+```bash
+# Alias for server:start, starts the server
+symfony serve
+
+# Run the server in daemon mode (in the background)
+symfony serve -d
+
+# Display the status of the server
+symfony server:status
+
+# Stop the server
+symfony server:stop
+```
+
+If your Drupal files are within a `web` or `docroot` directory, it will
+automatically be used as the document root for the server, so files are served
+from there if you run the serve command.
+
+If you use a different subdirectory name - one that isn't loaded automatically -
+you can use the `--document-root` option:
+
+```bash
+symfony serve --document-root www
+```
+
+## Different PHP Versions
+
+One of the most useful features of the Symfony server is that it
+[supports multiple versions of PHP](https://symfony.com/doc/current/setup/symfony_server.html#different-php-settings-per-project)
+if you have them installed, and a different version can be selected per
+directory.
+
+This is done by adding a `.php-version` file to the root of the project that
+contains the PHP version to use. For example:
+
+```bash
+echo "7.3" > .php-version
+```
+
+Next time the server is started, this file will be read and the correct version
+of PHP will be used.
+
+If you’re using macOS and want to install another version of PHP, you can do it
+using Homebrew:
+
+```bash
+# Install PHP 7.3
+brew install php@7.3
+```
+
+[Further PHP customisations can be made per project](https://symfony.com/doc/current/setup/symfony_server.html#overriding-php-config-options-per-project)
+by adding a `php.ini` file.
+
+## Securing Sites Locally
+
+The Symfony server allows for serving sites via HTTPS locally by installing its
+own local certificate authority.
+
+If it’s not installed automatically, run this command to install it:
+
+```
+symfony server:ca:install
+```
+
+Now any site will be served via HTTPS by default, and any HTTP requests will be
+automatically redirected.
+
+If you need to run a site with just HTTP, add the `--no-tls` option to the
+`serve` command.
+
+## Adding Databases (and other services) with Docker
+
+The Symfony server has an integration with Docker for providing extra services -
+such as databases that we’ll need to install Drupal.
+
+This is my `docker-compose.yaml` file which defines a `database` service for
+MySQL:
+
+```yaml
+version: '2.1'
+
+services:
+ database:
+ image: mysql:5.7
+ ports: [3306]
+ environment:
+ MYSQL_ROOT_PASSWORD: secret
+ volumes:
+ - mysql-data:/var/lib/mysql
+
+volumes:
+ mysql-data:
+```
+
+Because port 3306 is exposed, the server recognises it as a database service and
+automatically creates environment variables prefixed with `DATABASE_`.
+
+A list of all the environment variables can be seen by running
+`symfony var:export` (add `| tr " " "\n"` if you want to view each one on a new
+line, and `| sort` if you want to list them alphabetically):
+
+```
+DATABASE_DATABASE=main
+DATABASE_DRIVER=mysql
+DATABASE_HOST=127.0.0.1
+DATABASE_NAME=main
+DATABASE_PASSWORD=secret
+DATABASE_PORT=32776
+DATABASE_SERVER=mysql://127.0.0.1:32776
+DATABASE_URL=mysql://root:secret@127.0.0.1:32776/main?sslmode=disable&charset=utf8mb4
+DATABASE_USER=root
+DATABASE_USERNAME=root
+SYMFONY_DOCKER_ENV=1
+SYMFONY_TUNNEL=
+SYMFONY_TUNNEL_ENV=
+```
+
+Now these environment variables can be used within `settings.php` file to allow
+configure Drupal’s database connection settings:
+
+```php
+// web/sites/default/settings.php
+
+if ($_SERVER['SYMFONY_DEFAULT_ROUTE_URL']) {
+ $databases['default']['default'] = [
+ 'driver' => $_SERVER['DATABASE_DRIVER'],
+ 'host' => $_SERVER['DATABASE_HOST'],
+ 'database' => $_SERVER['DATABASE_NAME'],
+ 'username' => $_SERVER['DATABASE_USER'],
+ 'password' => $_SERVER['DATABASE_PASSWORD'],
+ 'port' => $_SERVER['DATABASE_PORT'],
+ 'prefix' => '',
+ 'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',
+ 'collation' => 'utf8mb4_general_ci',
+ ];
+}
+```
+
+To keep things organised, I usually like to split these settings into their own
+file and include it:
+
+```php
+if ($_SERVER['SYMFONY_DEFAULT_ROUTE_URL'] && file_exists(__DIR__ . '/settings.symfony.php')) {
+ require_once __DIR__ . '/settings.symfony.php';
+}
+```
+
+## Installing Drupal
+
+Now that Drupal can connect to the (empty) database, we can install the site. I
+usually do this using Drush, which is added as a dependency via Composer.
+
+The command that I’d usually run is:
+
+```bash
+cd web
+
+../vendor/bin/drush site-install
+```
+
+However, this will cause an error like this because Drupal cannot connect to the
+database when Drush is run in this way.
+
+> Error: Class 'Drush\Sql\Sql' not found in Drush\Sql\SqlBase::getInstance()
+
+To fix this, ensure that the command is prefixed with `symfony php`. This will
+ensure that the correct PHP version and configuration is used, and that the
+appropriate environment variables are available.
+
+```bash
+symfony php ../vendor/bin/drush site-install
+```
+
+This also applies to all other Drush commands.
+
+## Custom Domain Names
+
+Currently we can only access the site via the localhost URL with a specific
+port. The port is determined automatically when the server is started so it can
+change if you have multiple projects running.
+
+Symfony server also allows for
+[adding local domain names through a proxy](https://symfony.com/doc/current/setup/symfony_server.html#local-domain-names).
+This is useful if you always want to access the site from the same URL, or if
+the site relies on using a specific URL such as a multisite setup (multiple
+domains served from the same codebase).
+
+{% include 'figure' with {
+ image: {
+ src: '/images/blog/running-drupal-with-symfony-local-server/proxy.png',
+ alt: 'A screenshot of the proxy overview screen, showing three local projects with their local domains, ports and directories.',
+ },
+ caption: 'The proxy overview screen'
+} only %}
+
+### Setting up a multisite
+
+Here’s an example of how I would use local domains to configure a multisite
+Drupal installation (taken from
+).
+
+The first thing is to add the subdomain to the proxy. In this example, I’m going
+to set up a version of the Umami demo installation profile at
+`https://umami.wip`.
+
+```bash
+# Add umami.wip to the proxy and attach it to this directory
+symfony proxy:domain:attach umami
+```
+
+Now we can add it to Drupal’s `sites.php` file to route requests to the correct
+site directory:
+
+```php
+// web/sites/sites.php
+
+// This maps https://umami.wip to the sites/umami directory
+$sites['umami.wip'] = 'umami';
+```
+
+To create the directory, we can duplicate the `default` site directory and its
+contents.
+
+```
+cp -R web/sites/default web/sites/umami
+```
+
+To create a separate database, we add a new service to the `docker-compose.yaml`
+file and a new MySQL volume to store the data. We can use labels to generate
+site specific environment variables.
+
+```diff
+ version: '2.1'
+
+ services:
+ database:
+ image: mysql:5.7
+ ports: [3306]
+ environment:
+ MYSQL_ROOT_PASSWORD: secret
+ volumes:
+ - mysql-data:/var/lib/mysql
+
++ database_umami:
++ image: mysql:5.7
++ ports: [3306]
++ environment:
++ MYSQL_ROOT_PASSWORD: secret
++ volumes:
++ - mysql-data-umami:/var/lib/mysql
++ labels:
++ com.symfony.server.service-prefix: 'UMAMI_DATABASE'
+
+ volumes:
+ mysql-data:
++ mysql-data-umami:
+```
+
+These can then be added to `sites/umami/settings.php`:
+
+```php
+$databases['default']['default'] = [
+ 'driver' => $_SERVER['UMAMI_DATABASE_DRIVER'],
+ 'host' => $_SERVER['UMAMI_DATABASE_HOST'],
+ 'database' => $_SERVER['UMAMI_DATABASE_NAME'],
+ 'username' => $_SERVER['UMAMI_DATABASE_USER'],
+ 'password' => $_SERVER['UMAMI_DATABASE_PASSWORD'],
+ 'port' => $_SERVER['UMAMI_DATABASE_PORT'],
+ 'prefix' => '',
+ 'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',
+ 'collation' => 'utf8mb4_general_ci',
+];
+```
+
+Now that the Umami site is able to connect to its own database, we can install
+Drupal - specifying the installation profile to use and also the site directory
+to target.
+
+```bash
+symfony php ../vendor/bin/drush site-install \
+ demo_umami \
+ -l umami \
+ --no-interaction
+```
diff --git a/sculpin/source/_posts/sculpin-browsersync.md b/sculpin/source/_posts/sculpin-browsersync.md
new file mode 100644
index 000000000..e4369d92d
--- /dev/null
+++ b/sculpin/source/_posts/sculpin-browsersync.md
@@ -0,0 +1,49 @@
+---
+date: 2025-08-20
+title: Automatic Sculpin reloading with Browsersync
+tags:
+ - php
+ - sculpin
+ - nix
+---
+
+I'm a long-time user of the [Sculpin static site generator][0].
+
+I've used it for this website for years, and have [given presentations about it][1] at meetups and conferences.
+
+If you're not familiar, Sculpin takes Markdown, HTML and Twig files from a `source` directory and generates a fully static HTML website.
+
+This is done by running the `sculpin generate` command.
+
+To have Sculpin watch for changes and automatically rebuild the website when a file is updated, you can run `sculpin generate --watch` and have a local web server running by including `--server`.
+
+## Enter BrowserSync
+
+Whilst `sculpin generate --watch` will automatically re-generate the website, it doesn't automatically refresh your browser for you to see the changes.
+
+This is common in other projects like Fractal (a component library) and Astro (another static site generator).
+
+To fix this, I introduced [BrowserSync][2] into my project.
+
+It's a node package that runs a webserver and can watch for changes to files - just like Sculpin - but it can automatically refresh the browser so I can see changes immediately.
+
+## Using BrowserSync and Sculpin together
+
+The simplest way I've found to do this is to run `browser-sync --watch` in the `output_dev` directory that Sculpin generates, and accessing the site via BrowserSync's server (usually ).
+
+Then, because Browsersync is serving the files, I don't need Sculpin to do it and I can simplify my command to `sculpin generate --watch`.
+
+It still generates the website and watches for changes, but I'm offloading the serving of the pages to BrowserSync.
+
+## How it's going
+
+It's a new approach for me, but is working well.
+
+I install Browsersync into the appropriate projects using Nix, so I don't need to add `package.json` or `node` for this to work.
+
+For an example, see [the code repository for this website][3].
+
+[0]: https://sculpin.io
+[1]: /presentations/sculpin
+[2]: https://browsersync.io
+[3]: https://code.oliverdavies.uk/opdavies/oliverdavies.uk
diff --git a/sculpin/source/_posts/sculpin-twig-resources.md b/sculpin/source/_posts/sculpin-twig-resources.md
new file mode 100644
index 000000000..0aeb79157
--- /dev/null
+++ b/sculpin/source/_posts/sculpin-twig-resources.md
@@ -0,0 +1,51 @@
+---
+title: Sculpin and Twig Resources
+date: 2015-07-19
+excerpt: A list of resources that I compiled whilst preparing for my Sculpin and Twig talk at DrupalCamp North.
+tags:
+ - drupalcamp
+ - drupalcamp-north
+ - sculpin
+ - twig
+---
+
+Here’s a list of resources that I compiled whilst preparing for my
+[Sculpin and Twig talk](http://drupalcampnorth.org/session/test-drive-twig-sculpin)
+at [DrupalCamp North](http://drupalcampnorth.org).
+
+## General Information
+
+-
+-
+
+## Where to Get Sculpin
+
+-
+-
+-
+-
+
+## Source Code Examples
+
+-
+- - the source repository for this
+ site.
+-
+-
+-
+-
+-
+-
+- Google for "`sculpin_site.yml site:github.com`" for more examples.
+
+## Videos
+
+- - a YouTube playlist of Sculpin videos.
+
+## Twig
+
+-
+- - variables, filters,
+ functions, template inheritance, expressions etc.
+- Go to http://twig.sensiolabs.org/{foo} to search for a tag, filter, test or
+ function.
diff --git a/sculpin/source/_posts/simplifying-drupal-migrations-xautoload.md b/sculpin/source/_posts/simplifying-drupal-migrations-xautoload.md
new file mode 100644
index 000000000..d5792ed32
--- /dev/null
+++ b/sculpin/source/_posts/simplifying-drupal-migrations-xautoload.md
@@ -0,0 +1,135 @@
+---
+title: Simplifying Drupal Migrations with xautoload
+date: 2016-05-03
+excerpt: How to use the xautoload module to autoload migration classes within your Drupal 7 migration modules.
+tags:
+ - autoloading
+ - drupal
+ - drupal-7
+ - drupal-planet
+ - php
+---
+
+How to use the [xautoload][1] module to autoload migration classes within your
+Drupal 7 migration modules.
+
+## What is xautoload?
+
+[xautoload][1] is a Drupal module that enables the autoloading of PHP classes,
+in the same way that you would do so in a [Composer][2] based project such as
+Drupal 8 or Symfony.
+
+It supports both the [PSR-0][3] and [PSR-4][4] standards, as well as providing a
+wildcard syntax for Drupal’s `file[]` syntax in .info files.
+
+To use it, download and enable it from Drupal.org as you would for any other
+module, and then add it as a dependency within your module. The xautoload
+project page suggests including a minimum version in this format:
+
+```ini
+dependencies[] = xautoload (>= 7.x-5.0)
+```
+
+This will ensure that the version of xautoload is 7.x-5.0 or newer.
+
+## How to use it
+
+### Wildcard syntax for .info files
+
+Here is an example .info file for a migrate module.
+
+```ini
+; foo_migrate.info
+
+name = Foo Migration
+core = 7.x
+package = Foo
+
+files[] = includes/user.inc
+files[] = includes/nodes/article.inc
+files[] = includes/nodes/page.inc
+```
+
+In this example, each custom migration class is stored in it’s own file within
+the `includes` directory, and each class needs to be loaded separately using the
+`files[] = filename` syntax.
+
+One thing that the xautoload module does to enable for the use of wildcards
+within this syntax. By using wildcards, the module file can be simplified as
+follows:
+
+```ini
+files[] = includes/**/*.inc
+```
+
+This will load any .inc files within the `includes` directory as well as any
+sub-directories, like 'node' in the original example.
+
+This means that any new migration classes that are added will be automatically
+loaded, so you don’t need to declare each include separately within
+foo_migrate.info again. The great thing about this approach is that it works
+with the existing directory and file structure.
+
+### Use the PSR-4 structure
+
+If you want to use the [PSR-4][4] approach, you can do that too.
+
+In order to do so, you’ll need to complete the following steps:
+
+1. Rename the `includes` directory to `src`.
+2. Ensure that there is one PHP class per file, and that the file extension is
+ `.php` rather than `.inc`.
+3. Ensure that the name of the file matches the name of the class -
+ `FooArticleNodeMigration` would be in a file called
+ `FooArticleNodeMigration.php`.
+4. Add a namespace to each PHP file. This uses the same format as Drupal 8,
+ including the machine name of the module. For example, `Drupal\foo_migrate`.
+ - If the class is within a sub-directory, then this will also need to be
+ included within the namespace - e.g. `Drupal\foo_migrate\Node`.
+ - You’ll also need to import any class names that you are referencing,
+ including class names that are you extending, by adding `use` statements at
+ the top of the file. You may be able to prefix it with `\` instead (e.g.
+ `\DrupalNode6Migration`), but I prefer to use imports.
+
+Now your class may look something like this:
+
+```php
+ 'Drupal\foo_migrate\Node\FooArticleNodeMigration',
+ 'source_type' => 'story',
+ 'destination_type' => 'article',
+);
+```
+
+## Resources
+
+- [xautoload module][1]
+- [migrate module][5]
+- [migrate_d2d module][6]
+- [PSR-0][3]
+- [PSR-4][4]
+
+[1]: https://www.drupal.org/project/xautoload
+[2]: http://getcomposer.org
+[3]: http://www.php-fig.org/psr/psr-0/
+[4]: http://www.php-fig.org/psr/psr-4/
+[5]: https://www.drupal.org/project/migrate
+[6]: https://www.drupal.org/project/migrate_d2d
diff --git a/sculpin/source/_posts/site-upgraded-drupal-7.md b/sculpin/source/_posts/site-upgraded-drupal-7.md
new file mode 100644
index 000000000..de4ea3878
--- /dev/null
+++ b/sculpin/source/_posts/site-upgraded-drupal-7.md
@@ -0,0 +1,22 @@
+---
+title: Site Upgraded to Drupal 7
+date: 2012-01-04
+excerpt: As the vast majority of the Drupal websites that I currently work on are built on Drupal 7, I thought that it was time that I upgraded this site.
+tags:
+ - drupal
+---
+
+As the vast majority of the Drupal websites that I currently work on are built
+on Drupal 7, I thought that it was time that I upgraded this site. Following the
+[core upgrade process](http://drupal.org/node/570162) and the
+[CCK migration process](http://drupal.org/node/1144136), everything was upgraded
+smoothly without any issues.
+
+I've upgraded a handful of essential contrib modules to the latest stable
+version, [Administration Menu](http://drupal.org/project/admin_menu),
+[Views](http://drupal.org/project/views) etc., and will continue upgrading the
+other modules on the site as time allows.
+
+I also prefer [Bartik](http://drupal.org/project/bartik) to
+[Garland](http://drupal.org/project/garland) - but I will be creating a new
+custom theme when I get a chance.
diff --git a/sculpin/source/_posts/some-useful-git-aliases.md b/sculpin/source/_posts/some-useful-git-aliases.md
new file mode 100644
index 000000000..339b67d3c
--- /dev/null
+++ b/sculpin/source/_posts/some-useful-git-aliases.md
@@ -0,0 +1,34 @@
+---
+title: Some Useful Git Aliases
+date: 2014-01-15
+excerpt: Here are some bash aliases that I use and find helpful for quickly writing Git and Git Flow commands.
+tags:
+ - git
+---
+
+Here are some bash aliases that I use and find helpful for quickly writing Git
+and Git Flow commands.
+
+These should be placed within your `~/.bashrc` or `~/.bash_profile` file:
+
+```bash
+alias gi="git init"
+alias gcl="git clone"
+alias gco="git checkout"
+alias gs="git status"
+alias ga="git add"
+alias gaa="git add --all"
+alias gc="git commit"
+alias gcm="git commit -m"
+alias gca="git commit -am"
+alias gm="git merge"
+alias gr="git rebase"
+alias gps="git push"
+alias gpl="git pull"
+alias gd="git diff"
+alias gl="git log"
+alias gfi="git flow init"
+alias gff="git flow feature"
+alias gfr="git flow release"
+alias gfh="git flow hotfix"
+```
diff --git a/sculpin/source/_posts/some-useful-links-using-simpletest-drupal.md b/sculpin/source/_posts/some-useful-links-using-simpletest-drupal.md
new file mode 100644
index 000000000..9f6f5a231
--- /dev/null
+++ b/sculpin/source/_posts/some-useful-links-using-simpletest-drupal.md
@@ -0,0 +1,19 @@
+---
+title: Some useful links for using SimpleTest in Drupal
+date: 2013-06-13
+excerpt: Here are some useful links that I've found when researching about unit testing in Drupal using SimpleTest.
+tags:
+ - drupal
+ - drupal-planet
+ - simpletest
+ - tdd
+ - test-driven-development
+ - testing
+---
+
+- [An Introduction to Unit Testing in Drupal](http://www.lullabot.com/blog/articles/introduction-unit-testing-drupal 'An Introduction to Unit Testing in Drupal')
+- [Module Developer's Guide to SimpleTest](http://www.lullabot.com/blog/articles/drupal-module-developers-guide-simpletest "Module Developer's Guide to SimpleTest")
+- [SimpleTest Tutorial (Drupal 6)](https://drupal.org/simpletest-tutorial 'SimpleTest Tutorial (Drupal 6)')
+- [SimpleTest Tutorial (Drupal 7)](https://drupal.org/simpletest-tutorial-drupal7 'SimpleTest Tutorial (Drupal 7)')
+- [SimpleTest Reference](https://drupal.org/node/278126 'SimpleTest Reference')
+- [Testing with SimpleTest](https://drupal.org/node/1128366 'Testing with SimpleTest')
diff --git a/sculpin/source/_posts/south-wales-drupal-user-group.md b/sculpin/source/_posts/south-wales-drupal-user-group.md
new file mode 100644
index 000000000..30fedffa7
--- /dev/null
+++ b/sculpin/source/_posts/south-wales-drupal-user-group.md
@@ -0,0 +1,20 @@
+---
+title: The Inaugural Meetup for the South Wales Drupal User Group
+date: 2010-09-26
+excerpt: If you do Drupal and you're in the area, come and join us for the first SWDUG meetup!
+tags:
+ - drupal
+ - drupal-planet
+ - meetups
+ - swdug
+---
+
+If you do Drupal and you're in the area, come and join us for the first SWDUG
+meetup!
+
+We'll be meeting in the communal area just outside of the
+[SubHub](http://www.subhub.com) HQ, at:
+
+4, The Studios 3 Burt Street Cardiff CF10 5FZ
+
+For more information and to signup, visit .
diff --git a/sculpin/source/_posts/speaking-drupalcon-amsterdam.md b/sculpin/source/_posts/speaking-drupalcon-amsterdam.md
new file mode 100644
index 000000000..8bf235942
--- /dev/null
+++ b/sculpin/source/_posts/speaking-drupalcon-amsterdam.md
@@ -0,0 +1,39 @@
+---
+title: Speaking at DrupalCon Amsterdam
+date: 2019-07-25
+excerpt: I’m going to be attending DrupalCon Europe again this year, but for the first time as a speaker.
+tags:
+ - drupalcon
+ - personal
+ - speaking
+has_tweets: true
+---
+
+
I’ve attended numerous DrupalCons since my first in Prague in 2013, as a delegate, as a [{{site.companies.drupal_association.name}}]({{site.companies.drupal_association.url}}) staff member, and also as a contribution sprint mentor. I’m excited to be attending DrupalCon Amsterdam again this year - but as my first time as a DrupalCon speaker.
— Oliver Davies (@opdavies) July 16, 2019'
+} %}
+
+The session that I’m going to be presenting is a twenty minute version of my
+[Deploying PHP applications with Ansible, Ansible Vault and Ansistrano](/talks/deploying-php-ansible-ansistrano)
+talk.
+
+{% include 'figure' with {
+ image: {
+ src: '/images/blog/speaking-drupalcon-amsterdam/drupalcon-schedule.jpg',
+ alt: 'My session on the DrupalCon Amsterdam schedule.',
+ },
+ caption: 'My session on the DrupalCon Amsterdam schedule.',
+} %}
+
+I’ve been working with Drupal since 2007 (or maybe 2008), and it was the subject
+of my [first meetup talk](/talks/so-what-is-this-drupal-thing) in 2012. Since
+then I’ve given 48 more talks
+(including one workshop) at various user groups and conferences on a range of
+development and systems administration topics. So it’s a nice conincedence that
+this will be my fiftieth (50th) talk.
+
+Thanks also to my employer,
+[{{site.companies.inviqa.name}}]({{site.companies.inviqa.url}}), who are giving
+me the time and covering my costs to attend the conference.
diff --git a/sculpin/source/_posts/speaking-drupalcon-europe-2020.md b/sculpin/source/_posts/speaking-drupalcon-europe-2020.md
new file mode 100644
index 000000000..8be4cab71
--- /dev/null
+++ b/sculpin/source/_posts/speaking-drupalcon-europe-2020.md
@@ -0,0 +1,17 @@
+---
+title: Speaking at DrupalCon Europe 2020
+excerpt: I'm excited to be speaking again at DrupalCon, this time online at DrupalCon Europe.
+tags:
+ - drupal
+ - conferences
+ - speaking
+date: 2020-07-30
+---
+
+After giving my [Ansible and Ansistrano talk](/talks/deploying-php-ansible-ansistrano) in Amsterdam, I'm excited that another of my talks has been accepted for DrupalCon!
+
+This year, my [TDD - Test-Driven Drupal](/talks/tdd-test-driven-drupal) talk has been accepted for DrupalCon Europe, will is being held online from December 8-11th.
+
+I first gave this talk at DrupalCamp London 2017 and again a number of times over the last few years including at Drupal Developer Days in Lisbon and most recently for the North West (UK) Drupal user group in May. I've always had good feedback from it, and enjoy teaching others about testing and hopefully continue to inspire people to start writing tests themselves.
+
+This is definitely one of my favourite topics. I've enjoyed updating and improving this talk over the years, and I'm looking forward to giving it at DrupalCon this year.
diff --git a/sculpin/source/_posts/speaking-remotely-during-covid-19.md b/sculpin/source/_posts/speaking-remotely-during-covid-19.md
new file mode 100644
index 000000000..c72af93c1
--- /dev/null
+++ b/sculpin/source/_posts/speaking-remotely-during-covid-19.md
@@ -0,0 +1,84 @@
+---
+title: Speaking remotely during COVID-19
+excerpt: I've been quite busy during this lockdown, giving talks remotely at conferences and user groups.
+date: 2020-07-07
+tags:
+ - speaking
+---
+
+I've been quite busy during COVID-19 and various lockdowns, giving talks remotely at conferences and user groups.
+
+In mid-April, I send a tweet with an open offer to any user groups that needed a speaker, with some suggestions for talks that I'd given recently that I could present remotely.
+
+
+
+As well as this, I also applied to some open calls for papers for remote conferences, such as [CMS Philly](https://cmsphilly.org "The CMS Philly conference") (formerly Drupaldelphia) that was taking place online this year.
+
+At the time of writing, these are the talks that I've given remotely or are already planned for future dates. I'll be updating this list going forward as new talks are added, as well as my [talks page](/talks "My upcoming and past talks").
+
+If you'd like me to speak at your online conference or user group and be added to this list, please contact me and we can see if we can find a suitable date.
+
+## Test-Driven Drupal
+
+An overview of automated testing in Drupal, and a demo of building a new Drupal 8 (or 9) module using test driven development.
+
+- [NWDUG](http://nwdrupal.org.uk) - 11th May
+- [BADCamp 2020](https://2020.badcamp.org/session/tdd-test-driven-drupal) - 16th October
+- [DrupalCon Europe 2020](https://events.drupal.org/europe2020/sessions/tdd-test-driven-drupal) - 8th December
+
+## Deploying PHP with Ansible and Ansistrano
+
+How to use Ansible, Ansible Vault and Ansistrano to deploy PHP applications, using a Drupal 8 application for a demo.
+
+- [Drupal Edinburgh](https://www.meetup.com/Drupal-Edinburgh/events/267905594) - 11th March
+- [CMS Philly](https://cmsphilly.org) - 30th April
+- [Drupal Yorkshire](https://www.meetup.com/DrupalYorkshire/events/zwzsfpybchbcc) - 20th May
+- [PHP London](https://www.meetup.com/phplondon/events/270930524) - 4th June
+- [PHP North East](https://www.meetup.com/phpnortheast) - 16th June
+- [PHP Sussex](https://www.meetup.com/PHP-Sussex) - 1st July
+- [Midwest PHP 2021](https://midwestphp.org/talks/1q5XUF2tTdXXLYOoujMkpF/Deploying_PHP_applications_with_Ansible,_Ansible_Vault_and_Ansistrano) - 23rd April 2021
+
+## Taking Flight with Tailwind CSS
+
+An introduction to utility-based CSS and how to use Tailwind CSS in PHP projects using tools such as Webpack Encore and Laravel Mix.
+
+- [CMS Philly](https://cmsphilly.org) - 30th April
+- [PHP Hampshire](https://www.meetup.com/meetup-group-yzpbvTYv) - 8th July
+- [Drupal Yorkshire](https://www.meetup.com/DrupalYorkshire/events/zwzsfpybclbbc) - 20th August
+- [DigitalCamp Atlanta](https://www.drupalcampatlanta.com/2020/sessions/taking-flight-tailwind-css) - 11th September
+- [Bristol JS](https://techtalks.io/events/f8e26038-2561-484e-8a74-7a1e3a0369b8) - 30th September
+- [Drupal Virtual Cafe](https://groups.drupal.org/node/536142) (Drupal Kyiv) - 15th October
+- [PHP Cambridge](https://www.meetup.com/phpcambridge/events/273686561) - 19th January 2021
+- [Nashville PHP](https://www.meetup.com/nashvillephp/events/kzkdwryccdbmb) - 9th February 2021
+
+## Updating to Drupal 9
+
+How to update your site to Drupal 9, and why it's much different to any major Drupal version upgrade before!
+
+- [Drupal NYC](https://ti.to/drupalnyc/meetup-2020-08-05) - 2nd September
+- [Leeds PHP](https://www.meetup.com/leedsphp/events/272504993) - 23rd September
+- [Midwest PHP 2021](https://midwestphp.org/talks/7C0m4I87vq72cDoXvsHFRp/Upgrading_your_site_to_Drupal_9) - 22nd April 2021
+
+## Working with Workspace
+
+- [NWDUG](https://www.meetup.com/nwdrupal/events/272098270) - 11th August (lightning talk)
+- [PHP South West](https://www.meetup.com/php-sw/events/272787346) - 9th September (lightning talk)
+- PHP North West - 2nd February 2021
+
+## Automated Testing and Test-Driven Development in Drupal 8 (workshop)
+
+- [DrupalCamp London](https://drupalcamp.london/training/Automated-Testing-and-Test-Driven-Development-in-Drupal-8) - 13th March (in-person, just before UK lockdown)
+- [DrupalCamp NYC](https://2020.drupalcamp.nyc/training/automated-testing-and-test-driven-development-drupal-8) - 14th November
+
+## Building Slides and Presenting with rst2pdf
+
+- [PHP South Wales](https://www.meetup.com/PHP-South-Wales/events/275625320) - January 28th 2021
+
+## Soaring with Utility CSS and Tailwind (workshop)
+
+- [DrupalCamp Florida 2021](https://www.fldrupal.camp/training/soaring-utility-css-and-tailwind) - Feburary 18th 2021
+
+## Building Static Websites with Sculpin
+
+- [Drupal Yorkshire](https://www.meetup.com/DrupalYorkshire/events/280100968) - 19th August 2021
+- PHP North West - 7th September
diff --git a/sculpin/source/_posts/speaking-unified-diff-again.md b/sculpin/source/_posts/speaking-unified-diff-again.md
new file mode 100644
index 000000000..94d7fdd15
--- /dev/null
+++ b/sculpin/source/_posts/speaking-unified-diff-again.md
@@ -0,0 +1,25 @@
+---
+title: Speaking again at unified.diff
+date: 2025-09-10
+excerpt: |
+ I'm going to be speaking again at the unified.diff meetup, 13 years and 106
+ presentations since I last presented there.
+tags:
+ - speaking
+---
+
+I'm very happy to be speaking later this month at the [unified.diff meetup][0]
+in Cardiff, where I'll be giving the [Nix for PHP Developers presentation][1] I
+gave last month at PHP Thames Valley.
+
+It's focussed on PHP situations and examples, but I hope to be able to explain
+the same concepts for attendees who use different languages.
+
+unified.diff was the first meetup I spoke at, when I presented "[So, what is
+this Drupal thing?][2]" on the 5th of September 2012.
+
+13 years later, this will be my 107th meetup or conference presentation.
+
+[0]: https://www.meetup.com/unified-diff/events/310886970
+[1]: /presentations/nix-for-php-developers
+[2]: /presentations/so-what-is-this-drupal-thing
diff --git a/sculpin/source/_posts/splitting-new-drupal-project-from-repo.md b/sculpin/source/_posts/splitting-new-drupal-project-from-repo.md
new file mode 100644
index 000000000..4805617d5
--- /dev/null
+++ b/sculpin/source/_posts/splitting-new-drupal-project-from-repo.md
@@ -0,0 +1,163 @@
+---
+title: How to split a new Drupal contrib project from within another repository
+date: 2018-03-10
+excerpt: How to use Git to split a directory from within an existing repository into it’s own.
+tags:
+ - drupal
+ - drupal-7
+ - drupal-8
+ - drupal-planet
+ - git
+ - open-source
+---
+
+Yay! You’ve written a new Drupal module, theme or installation profile as part
+of your site, and now you’ve decided to open source it and upload it to
+Drupal.org as a new contrib project. But how do you split it from the main site
+repository into it’s own?
+
+Well, there are a couple of options.
+
+## Does it need to be part of the site repository?
+
+An interesting thing to consider is, does it _need_ to be a part of the site
+repository in the first place?
+
+If from the beginning you intend to contribute the module, theme or distribution
+and it’s written as generic and re-usable from the start, then it _could_ be
+created as a separate project on Drupal.org or as a private repository on your
+Git server from the beginning, and added as a dependency of the main project
+rather than part of it. It could already have the correct branch name and adhere
+to the Drupal.org release conventions and be managed as a separate project, then
+there is no later need to "clean it up" or split it from the main repo at all.
+
+This is how I worked at the [Drupal Association][2] - with all of the modules
+needed for Drupal.org hosted on Drupal.org itself, and managed as a dependency
+of the site repository with Drush Make.
+
+Whether this is a viable option or not will depend on your processes. For
+example, if your code needs to go through a peer review process before releasing
+it, then pushing it straight to Drupal.org would either complicate that process
+or bypass it completely. Pushing it to a separate private repository may depend
+on your team's level of familiarity with [Composer][3], for example.
+
+It does though avoid the “we’ll clean it up and contribute it later” scenario
+which probably happens less than people intend.
+
+## Create a new, empty repository
+
+If the project is already in the site repo, this is probably the most common
+method - to create a new, empty repository for the new project, add everything
+to it and push it.
+
+For example:
+
+```bash
+cd web/modules/custom/my_new_module
+
+# Create a new Git repository.
+git init
+
+# Add everything and make a new commit.
+git add -A .
+git commit -m 'Initial commit'
+
+# Rename the branch.
+git branch -m 8.x-1.x
+
+# Add the new remote and push everything.
+git remote add origin username@git.drupal.org:project/my_new_module.git
+git push origin 8.x-1.x
+```
+
+There is a huge issue with this approach though - **you now have only one single
+commit, and you’ve lost the commmit history!**
+
+This means that you lose the story and context of how the project was developed,
+and what decisions and changes were made during the lifetime of the project so
+far. Also, if multiple people developed it, now there is only one person being
+attributed - the one who made the single new commit.
+
+Also, if I’m considering adding your module to my project, personally I’m less
+likely to do so if I only see one "initial commit". I’d like to see the activity
+from the days, weeks or months prior to it being released.
+
+What this does allow though is to easily remove references to client names etc
+before pushing the code.
+
+## Use a subtree split
+
+An alternative method is to use [git-subtree][0], a Git command that "merges
+subtrees together and split repository into subtrees". In this scenario, we can
+use `split` to take a directory from within the site repo and split it into it’s
+own separate repository, keeping the commit history intact.
+
+Here is the description for the `split` command from the Git project itself:
+
+> Extract a new, synthetic project history from the history of the
+> subtree. The new history includes only the commits (including merges) that
+> affected , and each of those commits now has the contents of
+> at the root of the project instead of in a subdirectory. Thus, the newly
+> created history is suitable for export as a separate git repository.
+
+
+__Note__: This command needs to be run at the top level of the repository. Otherwise you will see an error like "You need to run this command from the toplevel of the working tree.".
+
+To find the path to the top level, run `git rev-parse --show-toplevel`.
+
+
+
+In order to do this, you need specify the prefix for the subtree (i.e. the
+directory that contains the project you’re splitting) as well as a name of a new
+branch that you want to split onto.
+
+```
+git subtree split --prefix web/modules/custom/my_new_module -b split_my_new_module
+```
+
+When complete, you should see a confirmation message showing the branch name and
+the commit SHA of the branch.
+
+```
+Created branch 'split_my_new_module'
+7edcb4b1f4dc34fc3b636b498f4284c7d98c8e4a
+```
+
+If you run `git branch`, you should now be able to see the new branch, and if
+you run `git log --oneline split_my_new_module`, you should only see commits for
+that module.
+
+If you do need to tidy up a particular commit to remove client references etc,
+change a commit message or squash some commits together, then you can do that by
+checking out the new branch, running an interactive rebase and making the
+required amends.
+
+```
+git checkout split_my_new_module
+git rebase -i --root
+```
+
+Once everything is in the desired state, you can use `git push` to push to the
+remote repo - specifying the repo URL, the local branch name and the remote
+branch name:
+
+```
+git push username@git.drupal.org:project/my_new_module.git split_my_new_module:8.x-1.x
+```
+
+In this case, the new branch will be `8.x-1.x`.
+
+Here is a screenshot of example module that I’ve split and pushed to GitLab.
+Notice that there are multiple commits in the history, and each still attributed
+to it’s original author.
+
+
+
+Also, as this is standard Git functionality, you can follow the same process to
+extract PHP libraries, Symfony bundles, WordPress plugins or anything else.
+
+[0]: https://github.com/git/git/blob/master/contrib/subtree/git-subtree.txt
+[1]:
+ https://github.com/git/git/blob/master/contrib/subtree/git-subtree.txt#L101-L108
+[2]: {{site.companies.drupal_association.url}}
+[3]: https://getcomposer.org
diff --git a/sculpin/source/_posts/streaming-spabby-gary-hockin-about-drupal.md b/sculpin/source/_posts/streaming-spabby-gary-hockin-about-drupal.md
new file mode 100644
index 000000000..d536b76d0
--- /dev/null
+++ b/sculpin/source/_posts/streaming-spabby-gary-hockin-about-drupal.md
@@ -0,0 +1,22 @@
+---
+title: Streaming with Spabby (Gary Hockin) about Drupal
+excerpt: I recently joined my friend Gary on his stream to discuss Drupal.
+tags:
+ - drupal
+ - php
+ - drupal-9
+ - streaming
+date: 2020-07-30
+---
+
+I recently joined my friend and fellow [PHP South Wales](https://phpsouthwales.uk) regular [Gary Hockin](https://twitter.com/GeeH "Gary on Twitter") (aka GeeH, aka Spabby) on his stream to discuss one of my favourite topics, Drupal.
+
+I've noticed that a lot of Developers within the wider PHP community have maybe used or looked at an earlier version of Drupal, like 4, 5 or 6, but not a more recent version, so this seemed like a good opportunity to discuss and demo some of the modern features and improvements in Drupal to Gary's mostly PHP focussed audience.
+
+You can currently [view the video on Gary's Twitch page](https://www.twitch.tv/videos/689269586), or I've embedded it below.
+
+We touched on the topic of decoupled Drupal, and we're planning a follow-up stream where we pair program and set up Drupal together with a front-end React application, which would be great fun!
+
+
+
+
diff --git a/sculpin/source/_posts/style-drupal-6s-taxonomy-lists-php-css-and-jquery.md b/sculpin/source/_posts/style-drupal-6s-taxonomy-lists-php-css-and-jquery.md
new file mode 100644
index 000000000..783df0016
--- /dev/null
+++ b/sculpin/source/_posts/style-drupal-6s-taxonomy-lists-php-css-and-jquery.md
@@ -0,0 +1,75 @@
+---
+title: Style Drupal 6's Taxonomy Lists with PHP, CSS and jQuery
+date: 2010-04-05
+excerpt: Getting started with Drupal theming by styling Drupal’s taxonomy lists.
+tags:
+ - drupal-6
+ - drupal-planet
+ - drupal-theming
+ - taxonomy
+---
+
+Whilst developing this, and other Drupal websites for clients, I decided that I
+wanted to categorise content using the taxonomy system. However, I wasn't happy
+with the way that Drupal displayed the terms lists by default, and I started
+comparing this to other websites that I look at.
+
+To start with, I wanted to have something that described what the list was
+displaying - like in the second example above. I wanted to have the words
+'Posted in' displayed before the list of terms. To do this, I had to edit the
+node template file that exists within my theme folder (sites/all/themes). As I
+only wanted this change to affect my Blog posts, the file that I needed to
+change is **node-blog.tpl.php**
+
+I scrolled down until I found the piece of code that displayed the terms list:
+
+```php
+
+
+
+
+
+```
+
+Adding `print t(' Posted in ')` will print the words 'Posted in' before
+outputing the terms.
+
+I then added some CSS to re-size the spacing between the items, and then add the
+commas between them to seperate them:
+
+```css
+.terms ul.links li {
+ margin-right: 1px;
+ padding: 0;
+}
+
+.terms ul.links li:after {
+ content: ",";
+}
+
+.terms ul.links li.last:after {
+ content: ".";
+}
+```
+
+I created a file named **script.js** in my theme folder with the following code
+in it. After clearing Drupal's caches, this file is automatically recognised by
+Drupal 6.
+
+```js
+if (Drupal.jsEnabled) {
+ $(document).ready(function() {
+ $('.terms ul.links li.last').prev().addClass('test');
+ })
+}
+```
+
+This code finds the last item in the list, uses **.prev** to select the one
+before it, and then uses **.addClass** to assign it the HTML class of "test". We
+can then use this class to target it with specific CSS.
+
+```css
+.terms ul.links li.test:after {
+ content: " and";
+}
+```
diff --git a/sculpin/source/_posts/survey-results-my-drupalcon-europe-session-test-driven-drupal.md b/sculpin/source/_posts/survey-results-my-drupalcon-europe-session-test-driven-drupal.md
new file mode 100644
index 000000000..5f4e7ebaf
--- /dev/null
+++ b/sculpin/source/_posts/survey-results-my-drupalcon-europe-session-test-driven-drupal.md
@@ -0,0 +1,96 @@
+---
+title: Survey results from my DrupalCon Europe session (Test-Driven Drupal)
+excerpt: Here are the results from the session survey for my DrupalCon session (Test-Driven Drupal) on Drupal automated testing and test-driven development.
+tags:
+ - drupalcon
+ - speaking
+date: 2021-01-22
+---
+
+In December [I gave a talk at DrupalCon Europe](/blog/test-driven-drupal-presentation-drupalcon-europe) on automated testing and test-driven development in Drupal. At the end of each session, the attendees were shown a survey to complete and the results have just been released to the speakers.
+
+Here are the results from my session, and I've included the screenshot of the graphs at the bottom of this post. I'd like to thank everyone who attended the session live, and those who left valuable feedback afterward.
+
+If you want to see the slides and video for that session, click the link above to see the embedded slides and video.
+
+## Survey results
+
+Attendance: 134
+
+### Did the session description accurately represent the content presented?
+
+* Yes - 96.8%
+* No - 3.2%
+
+Total survey responses: 62
+
+### Overall, how would you rate this session?
+
+* 5 - 44.8%
+* 4 - 29.3%
+* 3 - 19%
+* 2 - 6.9%
+* 1 - 0%
+
+Total survey responses: 58
+
+### How would you rate the speaker(s)'s mastery of this topic?
+
+* Excellent - 64.4%
+* Very good - 24.4%
+* Good - 11.1%
+* Fair - 0%
+* Poor - 0%
+
+Total survey responses: 45
+
+### How would you rate the speaker(s)'s presentation skills?
+
+* Excellent - 46.8%
+* Very good - 31.9%
+* Good - 10.6%
+* Fair - 8.5%
+* Poor - 2.1%
+
+Total survey responses: 47
+
+### How would you rate the speaker(s)’s slides and other session materials?
+
+* Excellent - 47.8%
+* Very good - 30.4%
+* Good - 15.2%
+* Fair - 6.5%
+* Poor - 0%
+
+Total survey responses: 46
+
+### What changes could the speaker(s) have made for you to give it a higher rating?
+
+* A little more insights (e.g. on test environment setup)
+* Everything was perfect! Thank you :)
+* He ist to fast ;)
+* Just felt the end examples were rushed through slightly compared to the introduction at the start.
+* Less examples to more focus.
+* Nothing, was excellent
+* Not to shy and interact afterwards
+* Slower - too much content in too little time
+* Speak a little slower, especially when going through the example code
+* Yes
+
+### What did the speaker(s) do really well?
+
+* Broken down the steps into real clear examples
+* Clear, concise slides, minimal code (Which can be hard to read)
+* CodeBlocks
+* Covers all relevant topics.
+* End to end examples without just being a high level overview.
+* Examples
+* Good examples
+* Good preparation, Good speed
+* Good structured walkthrough
+* Great knowledge
+* Introduce the idea of TDD
+* Yes
+* You explained your workflow very clearly. You have a skill for making tricky concepts very clear. Thanks for a great talk.
+
+
diff --git a/sculpin/source/_posts/test-driven-ansible-role-development-molecule.md b/sculpin/source/_posts/test-driven-ansible-role-development-molecule.md
new file mode 100644
index 000000000..9c78b5d3e
--- /dev/null
+++ b/sculpin/source/_posts/test-driven-ansible-role-development-molecule.md
@@ -0,0 +1,39 @@
+---
+title: Test-Driven Ansible Role Development with Molecule
+date: 2019-06-02
+excerpt: Some resources that I found for testing Ansible roles with a tool called Molecule.
+tags:
+ - ansible
+ - molecule
+ - testing
+ - video
+---
+
+I used to maintain a number of [Ansible roles][roles], and I recently wrote one
+for automatically generating `settings.php` files for Drupal projects that I use
+for some client projects as part of the [Ansible and Ansistrano deployment
+process][talk], as it can populate these files with credentials stored in
+Ansible Vault.
+
+I uploaded an initial version of the role [onto GitHub][github], but haven’t yet
+released it onto Ansible Galaxy.
+
+I’d seen in other people’s roles and read elsewhere about writing automated
+tests for Ansible roles using a tool called [Molecule][molecule], and wanted to
+write some tests for this role before publishing it onto Galaxy.
+
+I looked around for resources about Molecule, and found a [blog post by Jeff
+Geerling][jeff-post], but also this YouTube video that I found very helpful.
+
+I’ve since been re-writing the role from scratch based on Molecule, and plan to
+release an official version of it soon.
+
+{% include 'youtube-video' with { id: DAnMyBZ8-Qs } %}
+
+[github]: https://github.com/opdavies/ansible-role-drupal-settings
+[jeff-post]:
+ https://www.jeffgeerling.com/blog/2018/testing-your-ansible-roles-molecule
+[molecule]: https://molecule.readthedocs.io
+[roles]:
+ https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html
+[talk]: /talks/deploying-php-ansible-ansistrano
diff --git a/sculpin/source/_posts/test-driven-drupal-on-gitstore-leanpub.md b/sculpin/source/_posts/test-driven-drupal-on-gitstore-leanpub.md
new file mode 100644
index 000000000..db670b81a
--- /dev/null
+++ b/sculpin/source/_posts/test-driven-drupal-on-gitstore-leanpub.md
@@ -0,0 +1,37 @@
+---
+title: Test-Driven Drupal on Gitstore and Leanpub
+excerpt: The work-in-progress codebase for the example application is now on Gitstore.
+date: 2020-04-22
+tags:
+ - drupal
+ - drupal-8
+ - drupal-association
+ - drupal-planet
+ - drupalcares
+ - testing
+ - test-driven-drupal
+has_tweets: true
+---
+
+Some time ago, I announced that I was planning on writing a book on automated testing and test driven development with Drupal. I [created a landing page][landing page] and set up a mailing list, but I wasn't sure at that point what I was going to cover or create as part of the book.
+
+
+{% include 'tweet' with {
+ class: 'my-6',
+ data_cards: true,
+ content: '
I'm going to write a book on automated testing in Drupal. Join the mailing list for updates, and I'm happy to take suggestions on what to cover. https://t.co/YXNpe6f8Ft#drupal
— Oliver Davies (@opdavies) May 15, 2018',
+} %}
+
+Being a meetup and DrupalCamp conference organiser, after some thought I decided to build a website for an example conference, and that some of this code would then be included in the book as example content. This seemed to cover most of what I originally wanted, through features like a call for papers for potential speakers to propose sessions, allowing organisers to administer and moderate those proposals, automatically sending notification emails to submitters and displaying the accepted sessions.
+
+I've started building it with Drupal 8.8 and it is [now available on GitStore][gitstore] to purchase access to, including all future updates as I continue building the application - adding new features and upgrading to Drupal 9 once it is released. There are some other interesting things there too, such as using feature flags to enable or disable functionality, and using GitHub Actions to run the tests automatically.
+
+The book itself I've [added a page for on Leanpub][leanpub], and I'll be continuing to add content to it in parallel to building the example codebase. Once there is enough content, I will release the first draft for purchase.
+
+Any purchases that are made via Gitstore or Leanpub, an amount will be donated to the [Drupal Association][] and the [#DrupalCares campaign][drupalcares] to help sustain the Association during COVID-19.
+
+[drupal association]: https://www.drupal.org/association
+[drupalcares]: https://www.drupal.org/association/drupal-cares-challenge
+[gitstore]: https://enjoy.gitstore.app/repositories/opdavies/test-driven-drupal-conference-app
+[landing page]: /test-driven-drupal
+[leanpub]: https://leanpub.com/test-driven-drupal
diff --git a/sculpin/source/_posts/test-driven-drupal-presentation-drupalcon-europe.md b/sculpin/source/_posts/test-driven-drupal-presentation-drupalcon-europe.md
new file mode 100644
index 000000000..41393eaa8
--- /dev/null
+++ b/sculpin/source/_posts/test-driven-drupal-presentation-drupalcon-europe.md
@@ -0,0 +1,22 @@
+---
+title: Test-Driven Drupal presentation from DrupalCon Europe
+excerpt: Links to the video and slides from my automated testing session from DrupalCon Europe.
+tags:
+ - drupal
+ - drupal-8
+ - drupalcon
+ - speaking
+date: 2021-01-12
+---
+
+Today, the sessions from DrupalCon Europe were posted on the [Drupal Association YouTube channel](https://www.youtube.com/playlist?list=PLpeDXSh4nHjTP7vRC6LCak9adK2yp1P5S), including my session on automated testing and test-driven development in Drupal 8 (and 9):
+
+Here is the video of my presentation:
+
+
+
+
+
+Here is an embedded version of the slides, which I've updated since the talk:
+
+
diff --git a/sculpin/source/_posts/testing-tailwind-css-plugins-jest.md b/sculpin/source/_posts/testing-tailwind-css-plugins-jest.md
new file mode 100644
index 000000000..d22568aff
--- /dev/null
+++ b/sculpin/source/_posts/testing-tailwind-css-plugins-jest.md
@@ -0,0 +1,284 @@
+---
+title: Testing Tailwind CSS plugins with Jest
+date: 2019-04-29
+excerpt: How to write tests for Tailwind CSS plugins using Jest.
+tags:
+ - javascript
+ - jest
+ - tailwind-css
+ - testing
+promoted: true
+---
+
+
+**Note:** The content of this post is based on tests seen in Adam Wathan’s ["Working on Tailwind 1.0" video][working-on-tailwind-video], the Jest documentation website, and existing tests for other Tailwind plugins that I’ve used such as [Tailwind CSS Interaction Variants][tailwindcss-interaction-variants].
+
+
+## Preface
+
+In Tailwind 0.x, there was a `list-reset` utility that reset the list style and
+padding on a HTML list, though it was removed prior to 1.0 and moved into
+Tailwind’s base styles and applied by default.
+
+However, on a few projects I use Tailwind in addition to either existing custom
+styling or another CSS framework, and don’t use `@tailwind base` (formerly
+`@tailwind preflight`) so don’t get the base styles.
+
+Whilst I could re-create this by replacing it with two other classes
+(`list-none` and `p-0`), I decided to write [my own Tailwind CSS plugin][repo]
+to re-add the `list-reset` class. This way I could keep backwards compatibility
+in my projects and only need to add one class in other future instances.
+
+In this post, I’ll use this as an example to show how to write tests for
+Tailwind CSS plugins with a JavaScript testing framework called [Jest][jest].
+
+More information about plugins for Tailwind CSS themselves can be found on the
+[Tailwind website][tailwind-docs-plugins].
+
+## Add dependencies
+
+To start, we need to include `jest` as a dependency of the plugin, as well as
+`jest-matcher-css` to perform assertions against the CSS that the plugin
+generates.
+
+We also need to add `tailwindcss` and `postcss` so that we can use them within
+the tests.
+
+```
+yarn add -D jest jest-matcher-css postcss tailwindcss@next
+```
+
+This could be done with `yarn add` or `npm install`.
+
+## Writing the first test
+
+In this plugin, the tests are going to be added into a new file called
+`test.js`. This file is automatically loaded by Jest based on it’s [testRegex
+setting][jest-testregex-setting].
+
+This is the format for writing test methods:
+
+```js
+test('a description of the test', () => {
+ // Perform tasks and write assertions
+});
+```
+
+The first test is to ensure that the correct CSS is generated from the plugin
+using no options.
+
+We do this by generating the plugin’s CSS, and asserting that it matches the
+expected CSS within the test.
+
+```js
+test('it generates the list reset class', () => {
+ generatePluginCss().then(css => {
+ expect(css).toMatchCss(`
+ .list-reset {
+ list-style: none;
+ padding: 0
+ }
+ `);
+ });
+});
+```
+
+However, there are some additional steps needed to get this working.
+
+### Generating the plugin’s CSS
+
+Firstly, we need to import the plugin’s main `index.js` file, as well as PostCSS
+and Tailwind. This is done at the beginning of the `test.js` file.
+
+```js
+const plugin = require('./index.js');
+const postcss = require('postcss');
+const tailwindcss = require('tailwindcss');
+```
+
+Now we need a way to generate the CSS so assertions can be written against it.
+
+In this case, I’ve created a function called `generatePluginCss` that accepts
+some optional options, processes PostCSS and Tailwind, and returns the CSS.
+
+```js
+const generatePluginCss = (options = {}) => {
+ return postcss(tailwindcss())
+ .process('@tailwind utilities;', {
+ from: undefined,
+ })
+ .then(result => result.css);
+};
+```
+
+Alternatively, to test the output of a component, `@tailwind utilities;` would
+be replaced with `@tailwind components`.
+
+```js
+.process('@tailwind components;', {
+ from: undefined
+})
+```
+
+Whilst `from: undefined` isn’t required, if it’s not included you will get this
+message:
+
+> Without `from` option PostCSS could generate wrong source map and will not
+> find Browserslist config. Set it to CSS file path or to `undefined` to prevent
+> this warning.
+
+### Configuring Tailwind
+
+In order for the plugin to generate CSS, it needs to be enabled within the test,
+and Tailwind’s core plugins need to be disabled so that we can assert against
+just the output from the plugin.
+
+As of Tailwind 1.0.0-beta5, this can be done as follows:
+
+```
+tailwindcss({
+ corePlugins: false,
+ plugins: [plugin(options)]
+})
+```
+
+In prior versions, each plugin in `corePlugins` needed to be set to `false`
+separately.
+
+I did that using a `disableCorePlugins()` function and [lodash][lodash], using
+the keys from `variants`:
+
+```
+const _ = require('lodash')
+
+// ...
+
+const disableCorePlugins = () => {
+ return _.mapValues(defaultConfig.variants, () => false)
+}
+```
+
+### Enabling CSS matching
+
+In order to compare the generated and expected CSS, [the CSS matcher for
+Jest][jest-css-matcher] needs to be required and added using
+[expect.extend][jest-expect-extend].
+
+```js
+const cssMatcher = require('jest-matcher-css')
+
+...
+
+expect.extend({
+ toMatchCss: cssMatcher
+})
+```
+
+Without it, you’ll get an error message like _"TypeError: expect(...).toMatchCss
+is not a function"_ when running the tests.
+
+## The next test: testing variants
+
+To test variants we can specify the required variant names within as options to
+`generatePluginCss`.
+
+For example, this is how to enable `hover` and `focus` variants.
+
+```js
+generatePluginCss({ variants: ['hover', 'focus'] });
+```
+
+Now we can add another test that generates the variant classes too, to ensure
+that also works as expected.
+
+```js
+test('it generates the list reset class with variants', () => {
+ generatePluginCss({ variants: ['hover', 'focus'] }).then(css => {
+ expect(css).toMatchCss(`
+ .list-reset {
+ list-style: none;
+ padding: 0
+ }
+
+ .hover\\:list-reset:hover {
+ list-style: none;
+ padding: 0
+ }
+
+ .focus\\:list-reset:focus {
+ list-style: none;
+ padding: 0
+ }
+ `);
+ });
+});
+```
+
+## Running tests locally
+
+Now that we have tests, we need to be able to run them.
+
+With Jest included as a dependency, we can update the `test` script within
+`package.json` to execute it rather than returning a stub message.
+
+```diff
+- "test": "echo \"Error: no test specified\" && exit 1"
++ "test": "jest"
+```
+
+This means that as well as running the `jest` command directly to run the tests,
+we can also run `npm test` or `yarn test`.
+
+After running the tests, Jest will display a summary of the results:
+
+
+
+## Running tests automatically with Travis CI
+
+As well as running the tests locally, they can also be run automatically via
+services like [Travis CI][travis] when a new pull request is submitted or each
+time new commits are pushed.
+
+This is done by adding a `.travis-ci.yml` file to the repository, like this one
+which is based on the [JavaScript and Node.js example][travis-nodejs-example]:
+
+```yaml
+language: node_js
+
+node_js:
+ - '8'
+
+cache:
+ directories:
+ - node_modules
+
+before_install:
+ - npm update
+
+install:
+ - npm install
+
+script:
+ - npm test
+```
+
+With this in place, the project can now be enabled on the Travis website, and
+the tests will be run automatically.
+
+For this plugin, you can see the results at
+.
+
+[jest-css-matcher]: https://www.npmjs.com/package/jest-matcher-css
+[jest-expect-extend]: https://jestjs.io/docs/en/expect#expectextendmatchers
+[jest-testregex-setting]:
+ https://jestjs.io/docs/en/configuration#testregex-string-array-string
+[jest]: https://jestjs.io
+[lodash]: https://lodash.com
+[repo]: https://github.com/opdavies/tailwindcss-list-reset
+[tailwind-docs-plugins]: https://tailwindcss.com/docs/plugins
+[tailwindcss-interaction-variants]:
+ https://www.npmjs.com/package/tailwindcss-interaction-variants
+[travis-nodejs-example]:
+ https://docs.travis-ci.com/user/languages/javascript-with-nodejs
+[travis]: https://travis-ci.org
+[working-on-tailwind-video]: https://www.youtube.com/watch?v=SkTKN38wSEM
diff --git a/sculpin/source/_posts/thanks.md b/sculpin/source/_posts/thanks.md
new file mode 100644
index 000000000..ac7ce5fe8
--- /dev/null
+++ b/sculpin/source/_posts/thanks.md
@@ -0,0 +1,14 @@
+---
+title: Thanks
+date: 2014-05-06
+excerpt: Thanks everyone or their comments about my move to the Drupal Association.
+tags:
+ - drupal
+ - drupal-association
+ - personal
+---
+
+This is just a quick post to thank everyone for their comments and
+congratulations after my previous post about
+[joining the Drupal Association](/blog/drupal-association/). I’m looking forward
+to my first day in the job tomorrow.
diff --git a/sculpin/source/_posts/turning-drupal-module-into-feature.md b/sculpin/source/_posts/turning-drupal-module-into-feature.md
new file mode 100644
index 000000000..b34da9b41
--- /dev/null
+++ b/sculpin/source/_posts/turning-drupal-module-into-feature.md
@@ -0,0 +1,35 @@
+---
+title: Turning Your Custom Drupal Module into a Feature
+date: 2017-05-20
+excerpt: How to turn a custom Drupal module into a Feature.
+tags:
+ - drupal
+ - drupal-7
+ - drupal-planet
+ - features
+---
+
+Yesterday I was fixing a bug in an inherited Drupal 7 custom module, and I
+decided that I was going to add some tests to ensure that the bug was fixed and
+doesn’t get accidentially re-introduced in the future. The test though required
+me to have a particular content type and fields which are specific to this site,
+so weren’t present within the standard installation profile used to run tests.
+
+I decided to convert the custom module into a [Feature][0] so that the content
+type and it’s fields could be added to it, and therefore present on the testing
+site once the module is installed.
+
+To do this, I needed to expose the module to the Features API.
+
+All that’s needed is to add this line to the `mymodule.info` file:
+
+```ini
+features[features_api][] = api:2
+```
+
+After clearing the cache, the module is now visible in the Features list - and
+ready to have the appropriate configuration added to it.
+
+
+
+[0]: https://www.drupal.org/project/features
diff --git a/sculpin/source/_posts/tweets-drupalcamp-london.md b/sculpin/source/_posts/tweets-drupalcamp-london.md
new file mode 100644
index 000000000..13506a2bb
--- /dev/null
+++ b/sculpin/source/_posts/tweets-drupalcamp-london.md
@@ -0,0 +1,69 @@
+---
+title: Tweets from DrupalCamp London
+date: 2018-03-04
+excerpt: I wasn’t able to make it to DrupalCamp London, but here are some of the tweets that I saw.
+tags:
+ - drupal
+ - drupalcamp
+ - drupalcamp-london
+has_tweets: true
+---
+
+In the end, I wasn’t able to make it to DrupalCamp London because of the heavy
+snow that’s hit the UK over the last few days. I did though keep a close eye on
+Twitter and still had good conversations with some of the attendees, so it did
+feel that in some ways I was still part of the conference.
+
+Thanks to [@ChandeepKhosa](https://twitter.com/ChandeepKhosa),
+[@OrangePunchUK](https://twitter.com/OrangePunchUK),
+[@hussainweb](https://twitter.com/hussainweb),
+[@littlepixiez](https://twitter.com/littlepixiez),
+[@cferthorney](https://twitter.com/cferthorney) and others for taking the time
+to tweet whilst enjoying the event.
+
+Here are some of my favourites that I saw, and no snow next year, please!
+
+
This weekend at @DrupalCampLDN will be 5 years since my 1st #Drupal conference. It was life-changing, leading to travel across Europe learning, being inspired & speaking at 25 others. Thanks everyone who helped shape my journey, see you soon! #DCLondonpic.twitter.com/BeulE0XqET
A great weekend. Thanks to the organisers for putting on a great show. Thanks to @ReasonDigital for sending me. Lots of new stuff to try out on Monday! https://t.co/Xxvl7ZYcqj
A huge huge huge thank you to all our sponsors, speakers, volunteers, attendees and everyone else involved in that Camp. It was a fantastic weekend! Look out for the post Camp Survey and more! #DCLondon
diff --git a/sculpin/source/_posts/uis-ive-rebuilt-tailwind-css.md b/sculpin/source/_posts/uis-ive-rebuilt-tailwind-css.md
new file mode 100644
index 000000000..044ab8d4f
--- /dev/null
+++ b/sculpin/source/_posts/uis-ive-rebuilt-tailwind-css.md
@@ -0,0 +1,22 @@
+---
+title: UIs that I've Rebuilt with Tailwind CSS
+excerpt: A collection of all of the UIs that I've rebuilt using Tailwind CSS for talk demos etc.
+tags:
+ - css
+ - tailwind-css
+date: 2020-11-02
+---
+
+Like Adam Wathan and other Tailwind CSS users, I've rebuilt a number of existing websites and user interfaces with [Tailwind CSS](https://tailwindcss.com), either for practice, to try out some new features, or for a demo for a talk where I like to have a relevant example for that audience.
+
+I thought that I'd list them all here, and keep the list up to date for future reference. Most of them are [tagged on GitHub](/tailwind-repos), though I have been using [Tailwind Play](https://play.tailwindcss.com) for the newest ones.
+
+Here is the list (last updated in November 2020):
+
+- [Bartik](https://rebuilding-bartik.oliverdavies.uk) (Drupal's default theme), built with Vue.js. I've also made a version using Alpine JS.
+- [Acquia's hosting dashboard](https://rebuilding-acquia.oliverdavies.uk), built with Vue.js.
+- [The WordPress 2019 theme](https://wp-tailwind.oliverdavies.uk), built for WordCamp Bristol 2019.
+- [Symfony.com website](http://rebuilding-symfony.oliverdavies.uk), built for Leeds PHP meetup.
+- [Bristol JS website](https://rebuilding-bristol-js.oliverdavies.uk), built with Vue.js for Bristol JS meetup.
+- [Platform.sh's hosting dashboard](https://rebuilding-platformsh.oliverdavies.uk) - in progress.
+- [Pantheon's hosting dashboard](/rebuilding-pantheon) - in progress.
diff --git a/sculpin/source/_posts/updating-features-adding-components-using-drush.md b/sculpin/source/_posts/updating-features-adding-components-using-drush.md
new file mode 100644
index 000000000..144da7e78
--- /dev/null
+++ b/sculpin/source/_posts/updating-features-adding-components-using-drush.md
@@ -0,0 +1,74 @@
+---
+title: Updating Features and Adding Components Using Drush
+date: 2014-10-21
+excerpt: How to update features on the command line using Drush.
+tags:
+ - drupal
+ - drupal-planet
+ - drush
+ - features
+---
+
+If you use the [Features module](http://drupal.org/project/features) to manage
+your Drupal configuration, it can be time consuming to update features through
+the UI, especially if you are working on a remote server and need to keep
+downloading and uploading files.
+
+If you re-create a feature through the UI, you'll be prompted to download a new
+archive of the feature in its entirety onto your local computer. You could
+either commit this into a local repository and then pull it remotely, or use a
+tool such as SCP to upload the archive onto the server and commit it from there.
+You can simplify this process by using [Drush](http://drush.org).
+
+## Finding Components
+
+To search for a component, use the `drush features-components` command. This
+will display a list of all components on the site. As we're only interested in
+components that haven't been exported yet, add the `--not-exported` option to
+filter the results.
+
+To filter further, you can also use the `grep` command to filter the results.
+For example, `drush features-components --not-exported field_base | grep foo`,
+would only return non-exported field bases containing the word "foo".
+
+The result is a source and a component, separated by a colon. For example,
+`field_base:field_foo`.
+
+## Exporting the Feature
+
+Once you have a list of the components that you need to add, you can export the
+feature. This is done using the `drush features-export` command, along with the
+feature name and the component names.
+
+For example:
+
+```bash
+$ drush features-export -y myfeature field_base:field_foo field_instance:user-field_foo
+```
+
+In this example, the base for field_boo and it's instance on the user object is
+being added to the "myfeature" feature.
+
+If you are updating an existing feature, you'll get a message informing you that
+the module already exists and asking if you want to continue. This is fine, and
+is automatically accepted by including `-y` within the command. If a feature
+with the specified name doesn't exist, it will be created.
+
+If you're creating a new feature, you can define where the feature will be
+created using the `--destination` option.
+
+Once complete, you will see a confirmation message.
+
+> Created module: my feature in sites/default/modules/custom/features/myfeature
+
+## The Result
+
+Once finished, the feature is updated in it's original location, so there's no
+download of the feature and then needing to re-upload it. You can add and commit
+your changes into Git or continue with your standard workflow straight away.
+
+## Useful Links
+
+- [The Features project page on Drupal.org](http://www.drupal.org/project/features)
+- [The "drush features-components" command](http://www.drushcommands.com/drush-6x/features/features-components)
+- [The "drush features-export" command](http://www.drushcommands.com/drush-6x/features/features-export)
diff --git a/sculpin/source/_posts/updating-forked-github-repos.md b/sculpin/source/_posts/updating-forked-github-repos.md
new file mode 100644
index 000000000..e4602b036
--- /dev/null
+++ b/sculpin/source/_posts/updating-forked-github-repos.md
@@ -0,0 +1,121 @@
+---
+title: Updating Forked Repositories on GitHub
+date: 2015-06-18
+excerpt: I just had to update a repo that I forked on GitHub. This is how I did it. Did I do it the correct way?
+tags:
+ - git
+ - github
+ - phpstorm
+ - sculpin
+---
+
+I just had to update a repo that I forked on GitHub. This is how I did it. Did I
+do it the correct way?
+
+## Sculpin
+
+People may or may not know, but this site runs on
+[Sculpin](https://sculpin.io/), a PHP based static site generator (this may be
+the first time that I've mentioned it on this site). The source code is hosted
+on [GitHub](https://github.com/opdavies/oliverdavies.uk), and I've listed the
+site on the [Community page](https://sculpin.io/community/) on the Sculpin
+website.
+
+To get it there, I forked the
+[main sculpin.io repository](https://github.com/sculpin/sculpin.io) so that I
+had [my own copy](https://github.com/opdavies/sculpin.io), created a branch,
+made my additions and submitted a pull request. Easy enough!
+
+## New Domain
+
+In the last week or so, I've changed this site URL from .co.uk to just .uk, and
+also updated the GitHub repo URL to match, so I wanted to update the Community
+page to use the correct URL.
+
+There had been commits to the main repo since my pull request was merged, I
+didn't want to delete my repo and fork again, and making any changes against and
+old codebase isn't best practice, so I wanted to merge the latest changes into
+my forked repo before I did anything else - just to check that I didn't break
+anything!
+
+## Updating my Local Repo
+
+I had a quick look for a _Update my fork_ button or something, but couldn't see
+one to I added the main repository as an additional remote called `upstream` and
+fetched the changes.
+
+```bash
+$ git remote add upstream https://github.com/sculpin/sculpin.io.git
+
+$ git fetch upstream
+remote: Counting objects: 33, done.
+remote: Total 33 (delta 6), reused 6 (delta 6), pack-reused 27
+Unpacking objects: 100% (33/33), done.
+From https://github.com/sculpin/sculpin.io
+* [new branch] master -> upstream/master
+* [new branch] pr/4 -> upstream/pr/4
+```
+
+Now my local site knows about the upstream repo, and I could rebase the changes
+(`git pull upstream master` should have worked too) and push them back to
+origin.
+
+```bash
+$ git rebase upstream/master
+First, rewinding head to replay your work on top of it...
+...
+Fast-forwarded master to upstream/master.
+
+$ git push origin master
+```
+
+This seems to have worked OK - the commits are still authored by the correct
+people and at the correct date and time - and I went ahead and created a new
+feature branch and pull request based on that master branch.
+
+{% include 'figure' with {
+ image: {
+ src: '/images/blog/forked-github-repo-commits.png',
+ alt: 'The commits on my master branch after rebasing',
+ },
+ caption: 'The commits on my forked master branch after rebasing and pushing. All good!',
+} %}
+
+{% include 'figure' with {
+ image: {
+ src: '/images/blog/my-commit-to-the-rebased-branch.png',
+ alt: 'The new feature branch with my additional commit',
+ },
+ caption: 'The new feature branch with the new commit.',
+} %}
+
+## Is There a Better Way?
+
+Did I miss something? Is there a recommended and/or better way to update your
+forked repos, maybe through the UI? Please
+send
+me a tweet with any comments.
+
+## Up
+
+**December 2015:** I’ve found that PhpStorm has an option available to rebase a
+fork from within the IDE. This is within the _VCS_ > _Git_ menu.
+
+I believe that it will use an existing "upstream" remote if it exists, otherwise
+it will add one automatically for you, linking to the repository that you forked
+from.
+
+Once you’ve completed the rebase, you can then push your updated branch either
+from the terminal, or using the _Push_ command from the same menu.
+
+
+
+It would be great to see something similar added to
+[hub](https://hub.github.com) too (I’ve created
+[an issue](https://github.com/github/hub/issues/1047))!
+
+## Resources
+
+- [PhpStorm - Advanced GitHub Integration: Rebase My GitHub Fork (blog post)](http://blog.jetbrains.com/idea/2011/02/advanced-github-integration-rebase-my-github-fork/)
+- [Rebasing a GitHub fork inside PhpStorm (video)](https://www.youtube.com/watch?v=Twy-dhVgN4k)
+- [hub](https://hub.github.com) - makes Git better with GitHub
diff --git a/sculpin/source/_posts/upgrading-dransible-project-drupal-9.md b/sculpin/source/_posts/upgrading-dransible-project-drupal-9.md
new file mode 100644
index 000000000..33e7d3527
--- /dev/null
+++ b/sculpin/source/_posts/upgrading-dransible-project-drupal-9.md
@@ -0,0 +1,51 @@
+---
+title: Upgrading the Dransible project to Drupal 9
+excerpt: How I recently upgraded the Dransible example project from Drupal 8.8 to 9.0.
+tags:
+ - composer
+ - dransible
+ - drupal
+ - drupal-9
+ - drupal-planet
+ - php
+date: 2020-09-05
+---
+
+This week I gave [a new talk on upgrading to Drupal 9](/talks/upgrading-your-site-drupal-9) for the Drupal NYC meetup. Whilst preparing for that, I decided to upgrade my [Dransible example project](https://github.com/opdavies/dransible) that I use for my [Ansible and Ansistrano talk](/talks/deploying-php-ansible-ansistrano) to Drupal 9 and document the process.
+
+Whilst the steps taken are in the slides for that talk, here is the full list of steps that I took including the Composer commands.
+
+## Updating from Drupal 8.8 to 8.9 { #updating-from-drupal-88-to-89 }
+
+To begin with, let's update to the latest version of Drupal 8 so that we can do some testing and see all of the latest deprecation notices before moving to Drupal 9.
+
+1. Remove Drush temporarily using `composer remove drush/drush` as it will cause us being stuck on Drupal 8.9.0-beta2 rather than a newer, stable 8.9 version.
+1. Update `^8.8` to `^8.9` in composer.json for `drupal/core-recommended`, `drupal/core-dev` and `drupal/core-composer-scaffold`, and run `composer update drupal/core-* --with-dependencies` to update core to 8.9.5.
+1. Re-add Drush so that it's present for the deployment by running `composer require drush/drush:^9`.
+
+## Preparing for Drupal 9 {#preparing-drupal-9}
+
+1. Add the [Upgrade Status module](https://www.drupal.org/project/upgrade_status) by running `composer require drupal/upgrade_status`.
+1. Upgrade to Drush 10 by running `composer require drush/drush:^10`.
+1. Remove the [Config Installer module](https://www.drupal.org/project/config_installer) by running `composer remove drupal/config_installer`. This is no longer needed since Drupal 8.6, and there will be no Drupal 9 version.
+1. Update the [Admin Toolbar module](https://www.drupal.org/project/admin_toolbar) to 2.3, a Drupal 9 compatible version, by running `composer update drupal/admin_toolbar`.
+
+As I'd previously updated the Simple Message custom module to be Drupal 9 compatible (adding the `core_version_requirement` key to the info.yml file, and removing usages of deprecated code), no changes needed to be made to that.
+
+## Upgrading to Drupal 9 {#upgrading-drupal-9}
+
+1. Update `^8.9` to `^9.0` for the core packages in composer.json, and run `composer update drupal/core-* --with-dependencies` to update to 9.0.5.
+1. Re-add Drush by running `composer require drush/drush`. This will install Drush 10 by default.
+
+## Post upgrade {#post-upgrade}
+
+Although everything seemed to have updated OK locally, there were some errors when running a deployment to the Vagrant virtual machine that needed to be addressed, as well as some post-upgrade housekeeping steps to perform.
+
+1. Fix the deployment error by adding the [Symfony Configuration component](https://symfony.com/components/Config) as a dependency by running `composer require symfony/config:^4`.
+1. Alias `Drupal\Core\Messenger\MessengerInterface` to `messenger` in `simple_message.services.yml` to fix the autowiring error.
+1. Add `settings["config_sync_directory"]` to settings file variables (this will be added automatically in the next version of the [Drupal settings Ansible role](https://github.com/opdavies/ansible-role-drupal-settings)).
+1. Remove the Upgrade Status module by running `composer remove drupal/upgrade_status`, as it's no longer needed.
+
+And that's it! The Dransible demo project is upgraded, and if you see my Ansible deployments talk in the future, the demo site will be running on Drupal 9.
+
+If you want to see the original pull request, it's at .
diff --git a/sculpin/source/_posts/use-authorized-keys-create-passwordless-ssh-connection.md b/sculpin/source/_posts/use-authorized-keys-create-passwordless-ssh-connection.md
new file mode 100644
index 000000000..eda31b022
--- /dev/null
+++ b/sculpin/source/_posts/use-authorized-keys-create-passwordless-ssh-connection.md
@@ -0,0 +1,33 @@
+---
+title: How to use Authorized Keys to Create a Passwordless SSH Connection
+date: 2012-02-01
+excerpt: How to generate a SSH key, and how to use to log in to a server using SSH without entering a password.
+tags:
+ - linux
+ - ssh
+---
+
+If you're accessing Linux servers or automating tasks between servers, rather
+than having to enter your user password every time, you can also use SSH public
+key authentication. This is a simple process that involves creating a local key
+and storing it within the _authorized_keys_ file on the remote server.
+
+1. Check if you already have a SSH key. `$ ssh-add -L`
+1. If you don't have one, create one. `$ ssh-keygen`
+1. Upload the key onto the server. Replace _myserver_ with the hostname or IP
+ address of your remote server. `$ ssh-copy-id myserver`
+
+If you're using Mac OS X and you don't have ssh-copy-id installed, download and
+install [Homebrew](http://mxcl.github.com/homebrew 'Homebrew') and run the
+`brew install ssh-copy-id` command.
+
+If successful, you should now see a message like:
+
+> Now try logging into the machine, with "ssh 'myserver'", and check in:
+>
+> ~/.ssh/authorized_keys
+>
+> to make sure we haven't added extra keys that you weren't expecting.
+
+Now the next time that you SSH onto the server, it should log you in without
+prompting you for your password.
diff --git a/sculpin/source/_posts/use-regular-expressions-search-replace-coda-or-textmate.md b/sculpin/source/_posts/use-regular-expressions-search-replace-coda-or-textmate.md
new file mode 100644
index 000000000..eaacc8f33
--- /dev/null
+++ b/sculpin/source/_posts/use-regular-expressions-search-replace-coda-or-textmate.md
@@ -0,0 +1,57 @@
+---
+title: Use Regular Expressions to Search and Replace in Coda or TextMate
+date: 2010-11-04
+excerpt: How to perform searches using regular expressions.
+tags:
+ - coda
+ - database
+ - regular-expression
+ - sequel-pro
+ - taxonomy
+ - textmate
+---
+
+As in
+[the original post](/blog/add-taxonomy-term-multiple-nodes-using-sql/ 'Quickly adding a taxonomy term to multiple nodes using SQL'),
+I'd generated a list of node ID values, and needed to add structure the SQL
+update statment formatted in a certain way. However, I changed my inital query
+slightly to out put the same nid value twice.
+
+```sql
+SELECT nid, nid FROM node WHERE TYPE = 'blog' ORDER BY nid ASC;
+```
+
+Then, I could select all of the returned rows, copy the values, and paste them
+into Coda:
+
+As before, I needed my SQL update statement to be in the following format:
+
+```sql
+INSERT INTO term_node VALUE (nid, vid, tid), (nid2, vid2, tid);
+```
+
+As I mentioned previously, the nid and vid values are the same for each node,
+and the tid will remain constant. In this case, the tid value that I needed to
+use was '63'.
+
+So, using the 'Find and Replace' function within Coda, combined with
+[regular expressions](http://en.wikipedia.org/wiki/Regular_expression) (regex),
+I can easily format the values as needed. To begin with, I need to ensure that
+the RegEx search option is enabled, and that I'm using the correct escape
+character.
+
+The first thing that I wanted to do was add the seperating comma between the two
+values. To do this, I perform a search for `\s*\t`. This searches for everything
+that is whitespace AND is a tab value. I can then add the comma as the
+replacement for each result.
+
+All 31 lines have been changed.
+
+Next, I can use `\n` to target the lines between the rows. I'll replace it with
+the next comma, the number 63 (the tid value), the closing bracket, another
+comma, re-add the line and add the opening bracket.
+
+The only two lines that aren't changed are the first and last, as they don't
+have any line breaks following them. I can complete these lines manually. Now
+all I need to do is add the beginning of the SQL update statement, then copy and
+paste it into Sequel Pro.
diff --git a/sculpin/source/_posts/use-sass-and-compass-drupal-7-using-sassy.md b/sculpin/source/_posts/use-sass-and-compass-drupal-7-using-sassy.md
new file mode 100644
index 000000000..2ebae84fc
--- /dev/null
+++ b/sculpin/source/_posts/use-sass-and-compass-drupal-7-using-sassy.md
@@ -0,0 +1,79 @@
+---
+title: How to use SASS and Compass in Drupal 7 using Sassy
+date: 2012-12-06
+excerpt: Use PHPSass and the Sassy module to use Sass and Compass in your Drupal theme.
+tags:
+ - compass
+ - css
+ - drupal
+ - drupal-7
+ - drupal-planet
+ - less
+ - preprocessing
+ - sass
+---
+
+I've recently started using [SASS](http://sass-lang.com) rather than LESS to do
+my CSS preprocessing - namely due to its integration with
+[Compass](http://compass-style.org) and it's built-in CSS3 mixins. Here are
+three modules that provide the ability to use SASS within Drupal.
+
+- [Sassy](http://drupal.org/project/sassy 'Sassy module on drupal.org')
+- [Prepro](http://drupal.org/project/prepro 'Prepro module on drupal.org')
+- [Libraries API](http://drupal.org/project/libraries 'Libraries API module on drupal.org')
+
+Alternatively, you could use a base theme like
+[Sasson](http://drupal.org/project/sasson 'Sasson theme on drupal.org') that
+includes a SASS compiler.
+
+## Download the PHPSass Library
+
+The first thing to do is download the PHPSass library from
+[GitHub](https://github.com/richthegeek/phpsass 'PHPSass on GitHub'), as this is
+a requirement of the Sassy module and we can't enable it without the library.
+So, in a Terminal window:
+
+```bash
+$ mkdir -p sites/all/libraries;
+$ cd sites/all/libraries;
+$ wget https://github.com/richthegeek/phpsass/archive/master.tar.gz;
+$ tar zxf master.tar.gz;
+$ rm master.tar.gz;
+$ mv phpsass-master/ phpsass
+```
+
+Or, if you're using Drush Make files:
+
+```ini
+libraries[phpsass][download][type] = "get"
+libraries[phpsass][download][url] = "https://github.com/richthegeek/phpsass/archive/master.tar.gz"
+```
+
+The PHPSass library should now be located at `sites/all/libraries/phpsass`.
+
+## Download and enable the Drupal modules
+
+This is easy if you use [Drush](http://drupal.org/project/drush):
+
+```bash
+$ drush dl libraries prepro sassy
+$ drush en -y libraries prepro sassy sassy_compass
+```
+
+Otherwise, download the each module from it's respective project page and place
+it within your `sites/all/modules` or `sites/all/modules/contrib` directory.
+
+## Configuring the Prepro module
+
+The Prepro module provides various settings that can be changed for each
+preprocessor. Go to `admin/config/media/prepro` to configure the module as
+required.
+
+Personally, in development, I'd set caching to 'uncached' and the error
+reporting method to 'show on page'. In production, I'd change these to "cached"
+and "watchdog" respectively. I'd also set the output style to "compressed",
+
+## Adding SASS files into your theme
+
+With this done, you can now add SASS and SCSS files by adding a line like
+`stylesheets[all][] = css/base.scss` in your theme's .info file.
diff --git a/sculpin/source/_posts/useful-drupal-6-modules.md b/sculpin/source/_posts/useful-drupal-6-modules.md
new file mode 100644
index 000000000..1b482bd72
--- /dev/null
+++ b/sculpin/source/_posts/useful-drupal-6-modules.md
@@ -0,0 +1,57 @@
+---
+title: 10 Useful Drupal 6 Modules
+date: 2010-06-25
+excerpt: A list of 10 contributed modules that I currently use on each Drupal project.
+tags:
+ - drupal
+ - drupal-6
+ - drupal-modules
+ - drupal-planet
+---
+
+Aside from the obvious candidates such as Views, CCK etc, here are a list of 10
+contributed modules that I currently use on each Drupal project.
+
+So, in no particular order:
+
+- **[Admin](http://drupal.org/project/admin):** The admin module provides UI
+ improvements to the standard Drupal admin interface. I've just upgraded to the
+ new [6.x-2.0-beta4](http://drupal.org/node/835870) version, and installed the
+ newly-required
+ [Rubik](http://code.developmentseed.org/rubik)/[Tao](http://code.developmentseed.org/tao)
+ themes. So far, so good!
+- **[Better Permissions](http://drupal.org/project/better_perms)/[Filter Permissions](http://drupal.org/project/filter_perms):
+ **Basic permissions is a basic module which enhances the Drupal
+ Permissions page to support collapsing and expanding permission rows. Filter
+ permissions provides filters at the top of the Permissions page for easier
+ management when your site has a large amount of roles and/or permissions.
+- **[Better Formats](http://drupal.org/project/better_formats): **Better
+ formats is a module to add more flexibility to Drupal's core input format
+ system.
+- **[Clone module](http://drupal.org/project/node_clone):** Allows users to
+ make a copy of an existing item of site content (a node) and then edit that
+ copy.
+- **[Vertical Tabs](http://drupal.org/project/vertical_tabs): **Integrated
+ into Drupal 7 core, this module adds vertical tabs to the node add and edit
+ forms.
+- **[Context](http://drupal.org/project/context): **Context allows you to
+ manage contextual conditions and reactions for different portions of your
+ site. You can think of each context as representing a "section" of your site.
+ For each context, you can choose the conditions that trigger this context to
+ be active and choose different aspects of Drupal that should react to this
+ active context.
+- **[Node Picker](http://drupal.org/project/nodepicker):** A rewrite of the
+ module [TinyMCE Node Picker](http://drupal.org/project/tinymce_node_picker).
+ Allows you to easily create links to internal nodes.
+- **[Module Filter](http://drupal.org/project/module_filter):** What this
+ module aims to accomplish is the ability to quickly find the module you are
+ looking for without having to rely on the browsers search feature which more
+ times than not shows you the module name in the 'Required by' or 'Depends on'
+ sections of the various modules or even some other location on the page like a
+ menu item.
+- **[Zenophile](http://drupal.org/project/zenophile):** Quickly create Zen
+ subthemes.
+- **[Add Another](http://drupal.org/project/addanother):** Add another
+ displays a message after a user creates a node, and/or displays an "Add
+ another" tab on nodes allowing them to make another node of the same type. You
+ can control what roles and node types see this feature.
diff --git a/sculpin/source/_posts/useful-vagrant-commands.md b/sculpin/source/_posts/useful-vagrant-commands.md
new file mode 100644
index 000000000..0e63a7e70
--- /dev/null
+++ b/sculpin/source/_posts/useful-vagrant-commands.md
@@ -0,0 +1,24 @@
+---
+title: Useful Vagrant Commands
+date: 2013-11-27
+excerpt: Here are the basic commands that you need to adminster a virtual machine using Vagrant.
+tags:
+ - vagrant
+---
+
+[Vagrant](http://www.vagrantup.com 'About Vagrant') is a tool for managing
+virtual machines within [VirtualBox](https://www.virtualbox.org) from the
+command line. Here are some useful commands to know when using Vagrant.
+
+| Command | Description |
+| :--------------------------- | :----------------------------------------------------------------------------------------------------------- |
+| vagrant init {box} | Initialise a new VM in the current working directory. Specify a box name, or "base" will be used by default. |
+| vagrant status | Shows the status of the Vagrant box(es) within the current working directory tree. |
+| vagrant up (--provision) | Boots the Vagrant box. Including "–provision" also runs the "vagrant provision" command. |
+| vagrant reload (--provision) | Reloads the Vagrant box. Including "--provision" also runs the "vagrant provision" command. |
+| vagrant provision | Provision the Vagrant box using Puppet. |
+| vagrant suspend | Suspend the Vagrant box. Use "vagrant up" to start the box again. |
+| vagrant halt (-f) | Halt the Vagrant box. Use -f to forcefully shut down the box without prompting for confirmation. |
+| vagrant destroy (-f) | Destroys a Vagrant box. Use -f to forcefully shut down the box without prompting for confirmation. |
+
+The full Vagrant documentation can be found at .
diff --git a/sculpin/source/_posts/using-imagecache-and-imagecrop-my-portfolio.md b/sculpin/source/_posts/using-imagecache-and-imagecrop-my-portfolio.md
new file mode 100644
index 000000000..422901aa4
--- /dev/null
+++ b/sculpin/source/_posts/using-imagecache-and-imagecrop-my-portfolio.md
@@ -0,0 +1,44 @@
+---
+title: Using ImageCache and ImageCrop for my Portfolio
+date: 2010-04-28
+excerpt: How to create thumbnail images using the ImageCache and ImageCrop modules.
+tags:
+ - drupal
+ - drupal-6
+ - cck
+ - imagecache
+ - imagecrop
+ - imagefield
+---
+
+Whilst working on my own portfolio/testimonial website, I decided to have a
+portfolio page displaying the name of each site and a thumbnail image. For this
+Blog post, I'll be using a site called
+[Popcorn Strips](http://popcornstrips.com) which I built for a friend earlier
+this year as an example.
+
+I created a content type called 'Project' with a CCK ImageField called
+'Screenshot'. I created a project called
+[Popcorn Strips](http://popcornstrips.com), used the
+[ScreenGrab](https://addons.mozilla.org/addon/1146) add-on for Mozilla Firefox
+to take a screenshot of the website, and uploaded it to the project node.
+
+I created a View to display the published projects, and an ImageCache preset to
+create the thumbnail image by scaling and cropping the image to a size of
+200x100 pixels.
+
+Although, this automatically focused the crop on the centre of the image,
+whereas I wanted to crop from the top and left of the image - showing the site's
+logo and header.
+
+I installed the [ImageCrop](http://drupal.org/project/imagecrop) module, which
+add a jQuery crop function to the standard ImageCache presents. I removed the
+original Scale and Crop action and replaced it with a Scale action with a width
+of 200px.
+
+I then added a new 'Javascript crop' action with the following settings:
+
+- Width: 200px
+- Height: 100px
+- xoffset: Left
+- yoffset: Top
diff --git a/sculpin/source/_posts/using-laravel-collections-drupal.md b/sculpin/source/_posts/using-laravel-collections-drupal.md
new file mode 100644
index 000000000..e3345206d
--- /dev/null
+++ b/sculpin/source/_posts/using-laravel-collections-drupal.md
@@ -0,0 +1,71 @@
+---
+title: Examples of using Laravel Collections in Drupal
+date: 2018-08-23
+excerpt: Some examples of using Laravel’s Illuminate Collections within Drupal projects.
+tags:
+ - drupal
+ - drupal-7
+ - drupal-8
+ - drupal-planet
+ - laravel
+ - laravel-collections
+ - php
+has_tweets: true
+---
+
+Since starting to work with Laravel as well as Drupal and Symfony, watching Adam
+Wathan’s [Refactoring to Collections][0] course as well as [lessons on
+Laracasts][6], I’ve become a fan of [Laravel’s Illuminate Collections][1] and
+the object-orientated pipeline approach for interacting with PHP arrays.
+
+In fact I’ve given a talk on [using Collections outside Laravel][2] and have
+written a [Collection class module][3] for Drupal 7.
+
+I’ve also tweeted several examples of code that I’ve written within Drupal that
+use Collections, and I thought it would be good to collate them all here for
+reference.
+
+Thanks again to [Tighten][4] for releasing and maintaining the
+[tightenco/collect library][5] that makes it possible to pull in Collections via
+Composer.
+
+
+ {% include 'tweet' with {
+ class: 'block mb-4 lg:w-1/2 lg:px-2 lg:mb-0',
+ data_cards: true,
+ content: '
— Oliver Davies (@opdavies) February 14, 2018',
+ } %}
+
+ {% include 'tweet' with {
+ class: 'block mb-4 lg:w-1/2 lg:px-2 lg:mb-0',
+ data_cards: true,
+ content: '
I knew that you could specify a property like 'price' in Twig and it would also look for methods like 'getPrice()', but I didn't know (or had maybe forgotten) that @laravelphp Collections does it too.
This means that these two Collections return the same result.
— Oliver Davies (@opdavies) August 10, 2018',
+ } %}
+
+ {% include 'tweet' with {
+ class: 'block mb-4 lg:w-1/2 lg:px-2 lg:mb-0',
+ data_cards: true,
+ content: '
Some more #Drupal 8 fun with Laravel Collections. Loading the tags for a post and generating a formatted string of tweetable hashtags. pic.twitter.com/GbyiRPzIRo
+
+[0]: https://adamwathan.me/refactoring-to-collections
+[1]: https://laravel.com/docs/collections
+[2]: /talks/using-laravel-collections-outside-laravel
+[3]: https://www.drupal.org/project/collection_class
+[4]: https://tighten.co
+[5]: https://packagist.org/packages/tightenco/collect
+[6]: https://laracasts.com/series/how-do-i/episodes/18
diff --git a/sculpin/source/_posts/using-nixbook.md b/sculpin/source/_posts/using-nixbook.md
new file mode 100644
index 000000000..90100ff66
--- /dev/null
+++ b/sculpin/source/_posts/using-nixbook.md
@@ -0,0 +1,5 @@
+---
+title: Using NixBook
+date: ~
+draft: true
+---
diff --git a/sculpin/source/_posts/using-pcss-extension-postcss-webpack-encore.md b/sculpin/source/_posts/using-pcss-extension-postcss-webpack-encore.md
new file mode 100644
index 000000000..9033e5dc3
--- /dev/null
+++ b/sculpin/source/_posts/using-pcss-extension-postcss-webpack-encore.md
@@ -0,0 +1,94 @@
+---
+title: Using the pcss extension for PostCSS with Webpack Encore
+excerpt: How to use the .pcss file extension for PostCSS files with Webpack Encore.
+date: 2020-04-01
+tags:
+ - encore
+ - postcss
+ - symfony
+ - webpack
+---
+
+I’ve been watching Christopher Pitt ([assertchris][assertchris-twitter])’s [streams on Twitch][assertchris-twitch] over the last few months, in one of which he was doing some work with Tailwind CSS and using a `.pcss` file extension for his PostCSS files.
+
+I couldn’t remember seeing this extension before, but this made a lot of sense to me compared to the standard `.css` extension - both to make it clear that it’s a PostCSS file and features like nesting can be used, and also for better integration and highlighting with IDEs and text editors such as PhpStorm.
+
+It’s also shorter that the `.postcss` extension, and has been suggested by [@PostCSS on Twitter](https://twitter.com/PostCSS/status/661645290622083073) previously.
+
+Some of my projects use [Laravel Mix][] which support this extension by default, but some of them use Symfony’s [Webpack Encore][] which didn’t seem to, so I decided to look into it. (Note that both are agnostic and not coupled to their respective frameworks, so can be used with other projects too including Drupal and Sculpin).
+
+## Updating Webpack Encore’s configuration
+
+I was able to review the existing configuration and confirm this by using `console.log()` to output Encore’s generated webpack configuration - specifically the module rules.
+
+```js
+console.log(Encore.getWebpackConfig().module.rules)
+```
+
+There I can see the the test for PostCSS supports the `.css` and `.postcss` file extensions, but not `.pcss`.
+
+```
+test: /\.(css|postcss)$/,
+```
+
+There is documentation on the Symfony website for [adding custom webpack loaders and plugins](https://symfony.com/doc/current/frontend/encore/custom-loaders-plugins.html) but this wasn’t quite what I needed, as I needed to edit the existing `css` loader rather than add a new one.
+
+The page that I needed was [Advanced Webpack Config](https://symfony.com/doc/current/frontend/encore/advanced-config.html#having-the-full-control-on-loaders-rules) - specifically the section on 'Having the full control on Loaders Rules'.
+
+This suggests using `.configureLoaderRule()` and using that to override `test` directly.
+
+It does though come with a warning:
+
+> This is a low-level method. All your modifications will be applied just before pushing the loaders rules to Webpack. It means that you can override the default configuration provided by Encore, which may break things. Be careful when using it.
+
+My first pass was to add the full `.pcss` extension, but as this is a regular expression, I did a second pass that adds an second capturing group that would cover both PostCSS extensions.
+
+```
+// First pass
+loaderRule.test = /\.(css|pcss|postcss)$/
+
+// Second pass
+loaderRule.test = /\.(css|p(ost)?css)$/
+```
+
+To see this running, go to .
+
+## The final configuration
+
+This is my full `webpack.config.js` file for this site, including the `.pcss` extension support:
+
+```js
+Encore
+ .disableSingleRuntimeChunk()
+ .cleanupOutputBeforeBuild()
+ .setOutputPath('source/build/')
+ .setPublicPath('/build')
+ .addEntry('app', './assets/js/app.js')
+ .enablePostCssLoader()
+ .configureLoaderRule('css', loaderRule => {
+ loaderRule.test = /\.(css|p(ost)?css)$/
+ })
+ .enableSourceMaps(!Encore.isProduction())
+
+if (Encore.isProduction()) {
+ Encore
+ .enableVersioning()
+ .addPlugin(new PurgecssPlugin(purgecssConfig))
+} else {
+ Encore.enableSourceMaps()
+}
+
+module.exports = Encore.getWebpackConfig()
+```
+
+Alternatively, you can view it in the [codebase on GitHub](https://github.com/opdavies/oliverdavies.uk/blob/796578d7f0f3332724cb8335982c69b36bc11e53/webpack.config.js).
+
+## Contributing back to Encore
+
+I’ve also submitted a pull request to Encore to add support for the `.pcss` extension by default: . If accepted, then these changes in `webpack.config.js` would no longer be needed.
+
+[assertchris-twitch]: https://www.twitch.tv/assertchris "assertchris on Twitch"
+[assertchris-twitter]: https://twitter.com/assertchris "assertchris on Twitter"
+[gitstore]: https://gitstore.app
+[laravel mix]: https://laravel-mix.com
+[webpack encore]: https://github.com/symfony/webpack-encore
diff --git a/sculpin/source/_posts/using-remote-files-when-developing-locally-stage-file-proxy-module.md b/sculpin/source/_posts/using-remote-files-when-developing-locally-stage-file-proxy-module.md
new file mode 100644
index 000000000..084212cfb
--- /dev/null
+++ b/sculpin/source/_posts/using-remote-files-when-developing-locally-stage-file-proxy-module.md
@@ -0,0 +1,37 @@
+---
+title: Using Remote Files when Developing Locally with Stage File Proxy Module
+date: 2014-11-20
+excerpt: How to install and configure the Stage File Proxy module to serve remote images on your local Drupal site.
+tags:
+ - drupal
+ - drupal-planet
+ - servers
+---
+
+How to install and configure the
+[Stage File Proxy](https://www.drupal.org/project/stage_file_proxy) module to
+serve remote images on your local Drupal site.
+
+As this module is only going to be needed on pre-production sites, it would be
+better to configure this within your settings.php or settings.local.php file. We
+do this using the `$conf` array which removes the need to configure the module
+through the UI and store the values in the database.
+
+```php
+// File proxy to the live site.
+$conf['stage_file_proxy_origin'] = 'http://www.example.com';
+
+// Don't copy the files, just link to them.
+$conf['stage_file_proxy_hotlink'] = TRUE;
+
+// Image style images are the wrong size otherwise.
+$conf['stage_file_proxy_use_imagecache_root'] = FALSE;
+```
+
+If the origin site is not publicly accessible yet, maybe it's a pre-live or
+staging site, and protected with a basic access authentication, you can include
+the username and password within the origin URL.
+
+```php
+$conf['stage_file_proxy_origin'] = 'http://user:password@prelive.example.com';
+```
diff --git a/sculpin/source/_posts/using-tailwind-css-your-drupal-theme.md b/sculpin/source/_posts/using-tailwind-css-your-drupal-theme.md
new file mode 100644
index 000000000..2a81d89c1
--- /dev/null
+++ b/sculpin/source/_posts/using-tailwind-css-your-drupal-theme.md
@@ -0,0 +1,112 @@
+---
+title: Using Tailwind CSS in your Drupal Theme
+date: 2018-02-05
+excerpt: What is Tailwind CSS, and how do I use it in Drupal theme?
+tags:
+ - drupal
+ - drupal-planet
+ - drupal-theming
+ - tailwind-css
+---
+
+## What is Tailwind?
+
+> Tailwind is a utility-first CSS framework for rapidly building custom user
+> interfaces.
+
+It generates a number of utility classes that you can add to your theme's markup
+to apply different styling, as well as the ability to apply classes to other
+markup and create components comprised of utility classes using a custom
+`@apply` PostCSS directive.
+
+## Initial Configuration
+
+The installation and configuration steps are essentially the same as those
+outlined within the [Tailwind documentation][1], and should be performed within
+your custom theme's directory (e.g. `sites/all/themes/custom/mytheme` for Drupal
+7 or `themes/custom/mytheme` for Drupal 8:
+
+1. Require PostCSS and Tailwind via `npm` or `yarn`.
+1. Generate a configuration file using `./node_modules/.bin/tailwind init`.
+1. Tweak the settings as needed.
+1. Add a `postcss.config.js` file.
+1. Configure your build tool (Gulp, Grunt, Webpack).
+1. Generate the CSS.
+1. Include a path to the generated CSS in your `MYTHEME.info`,
+ `MYTHEME.info.yml` or `MYTHEME.libraries.yml` file.
+
+## PostCSS Configuration
+
+Create a `postcss.config.js` file and add `tailwindcss` as a plugin, passing the
+path to the config file:
+
+```js
+module.exports = {
+ plugins: [
+ require('tailwindcss')('./tailwind.js'),
+ ]
+}
+```
+
+## Configuration for Drupal
+
+There are some configuration settings within `tailwind.js` that you’ll need to
+change to make things work nicely with Drupal. These are within the `options`
+section:
+
+```js
+options: {
+ prefix: 'tw-',
+ important: true,
+ ...
+}
+```
+
+### Prefix
+
+By adding a prefix like `tw-`, we can ensure that the Tailwind classes don’t
+conflict with core HTML classes like `block`. We can also ensure that they won't
+conflict with any other existing HTML or CSS.
+
+No prefix:
+
+{.with-border}
+
+With prefix:
+
+{.with-border}
+
+### Important
+
+We can also set the `!important` rule on all Tailwind’s generated classes. We
+need to do this if we want to override core styles which have more specific
+rules.
+
+For example: if I had this core markup then the left margin added by `tw-ml-4`
+would be overridden by core’s `.item-list ul` styling.
+
+```html
+
+
+ ...
+
+
+```
+
+{.with-border}
+
+With the `!important` rule enabled though, the Tailwind’s class takes precedence
+and is applied.
+
+{.with-border}
+
+## Example
+
+For an example of Tailwind within a Drupal 8 theme, see the custom theme for the
+[Drupal Bristol website][0] on GitHub.
+
+[0]:
+ https://github.com/drupalbristol/drupal-bristol-website/tree/master/web/themes/custom/drupalbristol
+[1]: https://tailwindcss.com/docs/installation
+[2]: https://www.npmjs.com/get-npm
+[3]: https://yarnpkg.com/lang/en/docs/install
diff --git a/sculpin/source/_posts/using-transition-props-vuejs.md b/sculpin/source/_posts/using-transition-props-vuejs.md
new file mode 100644
index 000000000..5d6d8d4cf
--- /dev/null
+++ b/sculpin/source/_posts/using-transition-props-vuejs.md
@@ -0,0 +1,13 @@
+---
+title: Using Transition Class Props in Vue.js
+date: 2019-06-06
+type: tweet
+excerpt: Adam Wathan shows a more Tailwind-esque approach to writing Vue.js transitions.
+tags:
+ - vuejs
+external_url: https://twitter.com/adamwathan/status/1118670393030537217
+---
+
+{% include 'tweet' with {
+ content: '🔥 Using the transition class props instead of the `name` prop for @vuejs transitions makes it really easy to compose transitions on the fly using utility classes.
— Adam Wathan (@adamwathan) April 18, 2019'
+} %}
diff --git a/sculpin/source/_posts/weeknotes-2021-06-05.md b/sculpin/source/_posts/weeknotes-2021-06-05.md
new file mode 100644
index 000000000..b9719faee
--- /dev/null
+++ b/sculpin/source/_posts/weeknotes-2021-06-05.md
@@ -0,0 +1,26 @@
+---
+title: 'Weeknotes: June 5th'
+excerpt: Starting at Transport for Wales.
+date: ~
+tags:
+ - personal
+ - week-notes
+---
+
+After the Bank Holiday weekend, I started working this week as a Lead Developer at [Transport for Wales](https://trc.cymru). I really enjoyed working at Inviqa, but felt that moving again to an in-house team would offer some new types of challenges whilst also getting to lead and manage a team.
+
+It's been an interesting first week, and I've met a lot of new colleagues whilst also going through the regular TfW induction processes and getting to know the current team.
+
+
+
+
+
+
Image
+
+
+
+
+
+
+
+I'm looking forward to helping build and lead the development team at TfW, and plan on publishing regular weeknotes on the work that we're doing at Transport for Wales and TfW Rail.
diff --git a/sculpin/source/_posts/weeknotes-2021-06-12.md b/sculpin/source/_posts/weeknotes-2021-06-12.md
new file mode 100644
index 000000000..2c68a4bb2
--- /dev/null
+++ b/sculpin/source/_posts/weeknotes-2021-06-12.md
@@ -0,0 +1,36 @@
+---
+title: 'Weeknotes: June 12th'
+excerpt: Developing on Windows, organising dotfiles, and helping organise DrupalCon.
+date: 2021-06-12
+tags:
+ - personal
+ - week-notes
+---
+
+## Local development with Windows and WSL 2
+
+As a long-time Linux and macOS user, the last couple of weeks have been my first experience of using the Windows operating system for some time. After some research, I've been using the WSL 2 (Windows Subsystem for Linux) functionality built into Windows 10, with Ubuntu 20.04 installed within it. The codebase that I'm currently working on is using Lando, and that seems to be running fine within this setup after following some [instructions on the Lando documentation](https://docs.lando.dev/guides/setup-lando-on-windows-with-wsl-2.html) and a [blog post by Cal Evans](https://blog.calevans.com/2020/06/18/making-lando-work-inside-wsl2).
+
+I spend most of the day working within the WSL 2 environment which is a lot more familiar for me for development, but also for more simple tasks like generating SSH keys and cloning and configuring [my dotfiles](https://github.com/opdavies/dotfiles).
+
+## Re-organised dotfiles
+
+It was easy to clone my Dotfiles repository into the WSL 2 container but they still required to be symlinked into the correct place for them to be used. I'd previously used [rcm](https://github.com/thoughtbot/rcm), a tool from Thoughtbot, to do this but I wanted to review other approaches.
+
+I decided to try an approach of [using a local bare Git repository](https://www.atlassian.com/git/tutorials/dotfiles) and using Git's worktree functionality to clone the files into my home directory. This means no more symlinks, and no additional tool to manage the files. The structure of my dotfiles repo is now a lot simpler, though I do miss the grouping of files by 'tag' so I might look to re-implement this somehow in the future.
+
+## DrupalCon Europe kick-off meeting
+
+This week was the kick-off meeting for the DrupalCon Program Track Chair team, which I'm a part of this year.
+
+I've been proud to speak at the last two DrupalCon Europe conferences (2019 in Amsterdam, and online in 2020) and this year I wanted to contribute in a different way.
+
+I'm part of the Open Web & Community track and I'm looking forward to reviewing all of the sessions and experiencing DrupalCon in a new way again this year.
+
+## Inviqa blog post published
+
+My final task before leaving Inviqa a few weeks ago was to upgrade the inviqa.com and inviqa.de sites, which I co-developed, to Drupal 9.
+
+I wrote an article for the Inviqa blog, [Drupal 9 upgrade from Drupal 8](https://inviqa.com/blog/drupal-9-upgrade-from-drupal-8), which was published this week.
+
+I wrote my own post, [Upgrading the Dransible project to Drupal 9](/blog/upgrading-dransible-project-drupal-9) last year where I reviewed the commands and steps that I ran to upgrade one of my personal projects, whereas this post covers more about the process that we took, and the differences between this upgrade and previous Drupal upgrades that I've done.
diff --git a/sculpin/source/_posts/weeknotes-2021-07-24.md b/sculpin/source/_posts/weeknotes-2021-07-24.md
new file mode 100644
index 000000000..de5dfd10d
--- /dev/null
+++ b/sculpin/source/_posts/weeknotes-2021-07-24.md
@@ -0,0 +1,44 @@
+---
+title: 'Weeknotes: July 24th'
+excerpt: Windows utilities, continuous integration and delivery, and writing tests.
+tags:
+ - personal
+ - week-notes
+date: ~
+---
+
+## Using PowerToys and FancyZones
+
+I've been missing some of the features from Pop!_OS whilst working on my Windows laptop, so this week, I've been experimenting with [Microsoft PowerToys](https://docs.microsoft.com/en-us/windows/powertoys) which adds additional utilities to Windows 10 - similar to Gnome Tweak Tools.
+
+The main features that I'm trying are [FancyZones](https://docs.microsoft.com/en-us/windows/powertoys/fancyzones), which is similar to Pop!_OS's tiling window manager, and [Keyboard Manager](https://docs.microsoft.com/en-us/windows/powertoys/keyboard-manager](https://docs.microsoft.com/en-us/windows/powertoys/keyboard-manager)) so that I can remap some keys to match my personal laptop.
+
+## Continuous integration vs. feature branching
+
+I've been researching more about the continuous integration, or trunk-based development, approach to version control. Dave Farley's blog and [Continuous Delivery YouTube channel](https://www.youtube.com/channel/UCCfqyGl3nq_V0bo64CjZh8g) have been a great resource for this. I've also purchased his and Jez Humble's "Continuous Delivery" book, and have moved it to the top of my "To read" list.
+
+My current team at Transport for Wales is following a CI workflow, and it's been interesting to see code being pushed more often and moving from local to staging compared to waiting for async code reviews, so I'm looking forward to learning more about this approach and how to integrate it more with pair programming and test-driven development.
+
+I gave my first conference talk at DrupalCamp London 2014 on [Git Flow](/talks/git-flow), so I've been using the feature branch workflow for some time. As always, I'm willing to try and evaluate new approaches with an open mind.
+
+## Growing an automated test suite
+
+I [posted a tweet yesterday](https://twitter.com/opdavies/status/1418500778428338177) with a screenshot of the output from running some of the tests that I've added to my current work codebase. These are Drupal-based PHPUnit tests and are a combination of browser/functional and unit tests.
+
+I'm very keen on adding tests where possible for new functionality or when fixing bugs, which will make it much easier and less risky to refactor that code and ensure that the same bugs aren't re-added again.
+
+## Git hooks
+
+I've been using Git hooks for a few months to run checks locally, such as code linting and static analysis prior to pushing to a CI pipeline, to shorten the feedback loop and more quickly fix any failures.
+
+I've added some initial pre-push hooks to the current work codebase, to run some non-intrusive tasks such as code linting, with a view to adding to it over time.
+
+Sebastian Feldmann gave a talk in May for PHP South Wales, which included Git hooks. [The video is available on YouTube](https://www.youtube.com/watch?v=b85MoYmwUYs).
+
+## Deploying (and reverting) with CI and feature toggles
+
+Continuing with the CI/CD topic, I pushed a new, incomplete feature to production for one of my freelance clients' projects alongside some other changes.
+
+It was hidden behind a feature flag and not enabled, but it was merged and pushed to production.
+
+We decided to remove that implementation and use a different approach, so the code and the feature flag were removed, but for a time, that code was on production.
diff --git a/sculpin/source/_posts/what-git-flow.md b/sculpin/source/_posts/what-git-flow.md
new file mode 100644
index 000000000..d7b2b2878
--- /dev/null
+++ b/sculpin/source/_posts/what-git-flow.md
@@ -0,0 +1,59 @@
+---
+title: 'DrupalCamp London: What is Git Flow?'
+date: 2014-03-03
+excerpt: Here are my slides from my "What is Git Flow?" session at DrupalCamp London.
+tags:
+ - git
+ - git-flow
+ - drupalcamp-london
+ - talks
+tweets: true
+---
+
+Here are my slides from my "What is Git Flow?" session at
+[DrupalCamp London](http://2014.drupalcamplondon.co.uk).
+
+{% include 'presentation/slides' with { speakerdeck: {
+ data_id: '201559e0f103013198dd5a5f6f23ab67' }
+} only %}
+
+## Take aways
+
+The main take aways are:
+
+- Git Flow adds various commands into Git to enhance its native functionality,
+ which creates a branching model to separate your stable production code from
+ your unstable development code.
+- Never commit directly to the master branch - this is for production code only!
+- You can commit directly to the develop branch, but this should be done
+ sparingly.
+- Use feature branches as much as possible - one per feature, user story or bug.
+- Commit early and often, and push to a remote often to encourage collaboration
+ as well as to provide a backup of your code.
+- You can use settings within services like GitHub and Bitbucket to only allow
+ certain users to push to the master and develop branches, and restrict other
+ Developers to only commit and push to feature branches. Changes can then be
+ committed and pushed, then reviewed as part of a peer code review, and merged
+ back into the develop branch.
+
+## Feedback
+
+If you've got any questions, please feel free to
+tweet at me
+or fill in the
+session
+evaluation form that you can complete on the DrupalCamp London website.
+
+I've had some great feedback via Twitter:
+
+{% include 'tweet' with {
+ content: '
@opdavies@DrupalCampLDN always had trouble with git. Your talk + Git flow has made it all very easy.
— James Tombs (@jtombs) March 2, 2014'
+} %}
+
+{% include 'tweet' with {
+ content: '
— Curve Agency (@CurveAgency) March 2, 2014'
+} %}
diff --git a/sculpin/source/_posts/writing-article-linux-journal.md b/sculpin/source/_posts/writing-article-linux-journal.md
new file mode 100644
index 000000000..13b5dc7be
--- /dev/null
+++ b/sculpin/source/_posts/writing-article-linux-journal.md
@@ -0,0 +1,22 @@
+---
+title: Writing an Article for Linux Journal
+date: 2012-07-27
+excerpt: I'm absolutely delighted to announce that I'm going to be writing an article for Linux Journal magazine's upcoming Drupal special.
+tags:
+ - distributions
+ - drupal
+ - installation-profiles
+ - linux-journal
+ - writing
+---
+
+I'm absolutely delighted to announce that I'm going to be writing an article for
+[Linux Journal](http://www.linuxjournal.com) magazine's upcoming Drupal special.
+
+The article is going to be entitled "Speeding Up Your Drupal Development Using
+Installation Profiles and Distributions" and will be mentioning existing
+distributions available on Drupal.org, but mainly focussing on the steps needed
+to create your own custom distribution. Needless to say, I'm quite excited about
+it!
+
+The article is expected to be published in October.
diff --git a/sculpin/source/_posts/writing-info-file-drupal-7-theme.md b/sculpin/source/_posts/writing-info-file-drupal-7-theme.md
new file mode 100644
index 000000000..e3d623c18
--- /dev/null
+++ b/sculpin/source/_posts/writing-info-file-drupal-7-theme.md
@@ -0,0 +1,40 @@
+---
+title: Writing a .info file for a Drupal 7 theme
+date: 2012-05-23
+excerpt: An example .info file for a Drupal 7 theme.
+tags:
+ - code
+ - drupal
+ - drupal-theming
+ - theming
+---
+
+```ini
+name = My Theme
+description = A description of my theme
+core = 7.x
+
+# Add a base theme, if you want to use one.
+base = mybasetheme
+
+# Define regions, otherwise the default regions will be used.
+regions[header] = Header
+regions[navigation] = Navigation
+regions[content] = Content
+regions[sidebar] = Sidebar
+regions[footer] = Footer
+
+# Define which features are available. If none are specified, all the default
+# features will be available.
+features[] = logo
+features[] = name
+features[] = favicon
+
+# Add stylesheets
+stylesheets[all][] = css/reset.css
+stylesheets[all][] = css/mytheme.css
+stylesheets[print][] = css/print.css
+
+# Add javascript files
+styles[] = js/mytheme.js
+```
diff --git a/sculpin/source/_posts/writing-new-drupal-8-module-using-test-driven-development-tdd.md b/sculpin/source/_posts/writing-new-drupal-8-module-using-test-driven-development-tdd.md
new file mode 100644
index 000000000..d39909751
--- /dev/null
+++ b/sculpin/source/_posts/writing-new-drupal-8-module-using-test-driven-development-tdd.md
@@ -0,0 +1,661 @@
+---
+title: Writing a new Drupal 8 Module using Test-Driven Development (TDD)
+date: 2017-11-07
+tags:
+ - drupal
+ - phpunit
+ - simpletest
+ - tdd
+ - testing
+excerpt: How to write automated tests and follow test driven development for Drupal modules.
+meta:
+ image:
+ url: /images/talks/test-driven-drupal-development.png
+ width: 2560
+ height: 1440
+ type: image/png
+promoted: true
+---
+
+

+
+I recently gave a [talk on automated testing in Drupal][0] talk at [DrupalCamp
+Dublin][1] and as a lunch and learn session for my colleagues at Microserve. As
+part of the talk, I gave an example of how to build a Drupal 8 module using a
+test driven approach. I’ve released the [module code on GitHub][2], and this
+post outlines the steps of the process.
+
+## Prerequisites
+
+You have created a `core/phpunit.xml` file based on `core/phpunit.xml.dist`, and
+populated it with your database credentials so that PHPUnit can bootstrap the
+Drupal database as part of the tests. [Here is an example][5].
+
+## Acceptance Criteria
+
+For the module, we are going to satisfy this example acceptance criteria:
+
+> As a site visitor, I want to see all published pages at /pages Ordered
+> alphabetically by title
+
+## Initial Setup
+
+Let’s start by writing the minimal code needed in order for the new module to be
+enabled. In Drupal 8, this is the `.info.yml` file.
+
+```yaml
+# tdd_dublin.info.yml
+
+name: 'TDD Dublin'
+excerpt: 'A demo module for DrupalCamp Dublin to show test driven module development.'
+core: 8.x
+type: module
+```
+
+We can also add the test file structure at this point too. We’ll call it
+`PageTestTest.php` and put it within a `tests/src/Functional` directory. As this
+is a functional test, it extends the `BrowserTestBase` class, and we need to
+ensure that the tdd_dublin module is enabled by adding it to the `$modules`
+array.
+
+```php
+// tests/src/Functional/PageListTest.php
+
+namespace Drupal\Tests\tdd_dublin\Functional;
+
+use Drupal\Tests\BrowserTestBase\BrowserTestBase;
+
+class PageListTest extends BrowserTestBase {
+
+ protected static $modules = ['tdd_dublin'];
+
+}
+```
+
+With this in place, we can now start adding test methods.
+
+## Ensure that the Listing page Exists
+
+### Writing the First Test
+
+Let’s start by testing that the listing page exists at /pages. We can do this by
+loading the page and checking the status code. If the page exists, the code will
+be 200, otherwise it will be 404.
+
+I usually like to write comments first within the test method, just to outline
+the steps that I'm going to take and then replace it with code.
+
+```php
+public function testListingPageExists() {
+ // Go to /pages and check that it is accessible by checking the status
+ // code.
+}
+```
+
+We can use the `drupalGet()` method to browse to the required path, i.e.
+`/pages`, and then write an assertion for the response code value.
+
+```php
+public function testListingPageExists() {
+ $this->drupalGet('pages');
+
+ $this->assertSession()->statusCodeEquals(200);
+}
+```
+
+### Running the Test
+
+In order to run the tests, you either need to include `-c core` or be inside the
+`core` directory when running the command, to ensure that the test classes are
+autoloaded so can be found, though the path to the `vendor` directory may be
+different depending on your project structure. You can also specify a path
+within which to run the tests - e.g. within the module’s `test` directory.
+
+```
+$ vendor/bin/phpunit -c core modules/custom/tdd_dublin/tests
+```
+
+
+Note: I’m using Docksal, and I’ve noticed that I need to run the tests from within the CLI container. You can do this by running the `fin bash` command.
+
+
+```
+1) Drupal\Tests\tdd_dublin\Functional\PageListTest::testListingPageExists
+Behat\Mink\Exception\ExpectationException: Current response status code is 404, but 200 expected.
+
+FAILURES!
+Tests: 1, Assertions: 1, Errors: 1.
+```
+
+Because the route does not yet exist, the response code returned is 404, so the
+test fails.
+
+Now we can make it pass by adding the page. For this, I will use the Views
+module, though you could achieve the same result with a custom route and a
+Controller.
+
+### Building the View
+
+To begin with, I will create a view showing all types of content with a default
+sort order of newest first. We will use further tests to ensure that only the
+correct content is returned and that it is ordered correctly.
+
+ { .with-border }
+
+The only addition I will make to the view is to add a path at `pages`, as per
+the acceptance criteria.
+
+ { .with-border }
+
+### Exporting the View
+
+With the first version of the view built, it needs to be incldued within the
+module so that it can be enabled when the test is run. To do this, we need to
+export the configuration for the view, and place it within the module’s
+`config/install` directory. This can be done using the `drush config-export`
+command or from within the Drupal UI. In either case, the `uid` line at the top
+of the file needs to be removed so the configuration can be installed.
+
+Here is the exported view configuration:
+
+```yaml
+langcode: en
+status: true
+dependencies:
+ module:
+ - node
+ - user
+id: pages
+label: pages
+module: views
+excerpt: ''
+tag: ''
+base_table: node_field_data
+base_field: nid
+core: 8.x
+display:
+ default:
+ display_plugin: default
+ id: default
+ display_title: Master
+ position: 0
+ display_options:
+ access:
+ type: perm
+ options:
+ perm: 'access content'
+ cache:
+ type: tag
+ options: { }
+ query:
+ type: views_query
+ options:
+ disable_sql_rewrite: false
+ distinct: false
+ replica: false
+ query_comment: ''
+ query_tags: { }
+ exposed_form:
+ type: basic
+ options:
+ submit_button: Apply
+ reset_button: false
+ reset_button_label: Reset
+ exposed_sorts_label: 'Sort by'
+ expose_sort_order: true
+ sort_asc_label: Asc
+ sort_desc_label: Desc
+ pager:
+ type: mini
+ options:
+ items_per_page: 10
+ offset: 0
+ id: 0
+ total_pages: null
+ expose:
+ items_per_page: false
+ items_per_page_label: 'Items per page'
+ items_per_page_options: '5, 10, 25, 50'
+ items_per_page_options_all: false
+ items_per_page_options_all_label: '- All -'
+ offset: false
+ offset_label: Offset
+ tags:
+ previous: ‹‹
+ next: ››
+ style:
+ type: default
+ options:
+ grouping: { }
+ row_class: ''
+ default_row_class: true
+ uses_fields: false
+ row:
+ type: fields
+ options:
+ inline: { }
+ separator: ''
+ hide_empty: false
+ default_field_elements: true
+ fields:
+ title:
+ id: title
+ table: node_field_data
+ field: title
+ entity_type: node
+ entity_field: title
+ label: ''
+ alter:
+ alter_text: false
+ make_link: false
+ absolute: false
+ trim: false
+ word_boundary: false
+ ellipsis: false
+ strip_tags: false
+ html: false
+ hide_empty: false
+ empty_zero: false
+ settings:
+ link_to_entity: true
+ plugin_id: field
+ relationship: none
+ group_type: group
+ admin_label: ''
+ exclude: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_alter_empty: true
+ click_sort_column: value
+ type: string
+ group_column: value
+ group_columns: { }
+ group_rows: true
+ delta_limit: 0
+ delta_offset: 0
+ delta_reversed: false
+ delta_first_last: false
+ multi_type: separator
+ separator: ', '
+ field_api_classes: false
+ filters:
+ status:
+ value: '1'
+ table: node_field_data
+ field: status
+ plugin_id: boolean
+ entity_type: node
+ entity_field: status
+ id: status
+ expose:
+ operator: ''
+ group: 1
+ sorts:
+ created:
+ id: created
+ table: node_field_data
+ field: created
+ order: DESC
+ entity_type: node
+ entity_field: created
+ plugin_id: date
+ relationship: none
+ group_type: group
+ admin_label: ''
+ exposed: false
+ expose:
+ label: ''
+ granularity: second
+ header: { }
+ footer: { }
+ empty: { }
+ relationships: { }
+ arguments: { }
+ display_extenders: { }
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - 'languages:language_content'
+ - 'languages:language_interface'
+ - url.query_args
+ - 'user.node_grants:view'
+ - user.permissions
+ tags: { }
+ page_1:
+ display_plugin: page
+ id: page_1
+ display_title: Page
+ position: 1
+ display_options:
+ display_extenders: { }
+ path: pages
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - 'languages:language_content'
+ - 'languages:language_interface'
+ - url.query_args
+ - 'user.node_grants:view'
+ - user.permissions
+ tags: { }
+```
+
+When the test is run again, we see a different error that leads us to the next
+step.
+
+```
+1) Drupal\Tests\tdd_dublin\Functional\PageListTest::testListingPageExists
+Drupal\Core\Config\UnmetDependenciesException: Configuration objects provided by tdd_dublin have unmet dependencies: node.type.page (node), views.view.pages (node, views)
+
+FAILURES!
+Tests: 1, Assertions: 0, Errors: 1.
+```
+
+This error is identifying unmet dependencies within the module’s configuration.
+In this case, the view that we’ve added depends on the node and views modules,
+but these aren’t enabled. To fix this, we can add the extra modules as
+dependencies of tdd_dublin so they will be enabled too.
+
+```yaml
+# tdd_dublin.info.yml
+
+dependencies:
+ - drupal:node
+ - drupal:views
+```
+
+```
+1) Drupal\Tests\tdd_dublin\Functional\PageListTest::testListingPageExists
+Drupal\Core\Config\UnmetDependenciesException: Configuration objects provided by tdd_dublin have unmet dependencies: views.view.pages (node.type.page)
+
+FAILURES!
+Tests: 1, Assertions: 0, Errors: 1.
+```
+
+With the modules enabled, we can see one more unmet dependency for
+`node.type.page`. This means that we need a page content type to be able to
+install the view. We can fix this in the same way as before, by exporting the
+configuration and copying it into the `config/install` directory.
+
+With this in place, the test should now pass - and it does.
+
+```
+Time: 26.04 seconds, Memory: 6.00MB
+
+OK (1 test, 1 assertion)
+```
+
+We now have a test to ensure that the listing page exists.
+
+## Ensure that only Published Pages are Shown
+
+### Writing the Test
+
+Now that we have a working page, we can now move on to checking that the correct
+content is returned. Again, I’ll start by writing comments and then translate
+that into code.
+
+The objectives of this test are:
+
+- To ensure that only page nodes are returned.
+- To ensure that only published nodes are returned.
+
+```php
+public function testOnlyPublishedPagesAreShown() {
+ // Given that a have a mixture of published and unpublished pages, as well
+ // as other types of content.
+
+ // When I view the page.
+
+ // Then I should only see the published pages.
+}
+```
+
+In order to test the different scenarios, I will create an additional "article"
+content type, create a node of this type as well as one published and one
+unpublished page. From this combination, I only expect one node to be visible.
+
+```php
+public function testOnlyPublishedPagesAreShown() {
+ $this->drupalCreateContentType(['type' => 'article']);
+
+ $this->drupalCreateNode(['type' => 'page', 'status' => TRUE]);
+ $this->drupalCreateNode(['type' => 'article']);
+ $this->drupalCreateNode(['type' => 'page', 'status' => FALSE]);
+
+ // When I view the page.
+
+ // Then I should only see the published pages.
+}
+```
+
+We could use `drupalGet()` again to browse to the page and write assertions
+based on the rendered HTML, though I’d rather do this against the data returned
+from the view itself. This is so that the test isn’t too tightly coupled to the
+presentation logic, and we won’t be in a situation where at a later date the
+test fails because of changes made to how the data is displayed.
+
+Rather, I’m going to use `views_get_view_result()` to programmatically get the
+result of the view. This returns an array of `Drupal\views\ResultRow` objects,
+which contain the nodes. I can use `array_column` to extract the node IDs from
+the view result into an array.
+
+```php
+public function testOnlyPublishedPagesAreShown() {
+ $this->drupalCreateContentType(['type' => 'article']);
+
+ $this->drupalCreateNode(['type' => 'page', 'status' => TRUE]);
+ $this->drupalCreateNode(['type' => 'article']);
+ $this->drupalCreateNode(['type' => 'page', 'status' => FALSE]);
+
+ $result = views_get_view_result('pages');
+ $nids = array_column($result, 'nid');
+
+ // Then I should only see the published pages.
+}
+```
+
+From the generated nodes, I can use `assertEquals()` to compare the returned
+node IDs from the view against an array of expected node IDs - in this case, I
+expect only node 1 to be returned.
+
+```php
+public function testOnlyPublishedPagesAreShown() {
+ $this->drupalCreateContentType(['type' => 'article']);
+
+ $this->drupalCreateNode(['type' => 'page', 'status' => TRUE]);
+ $this->drupalCreateNode(['type' => 'article']);
+ $this->drupalCreateNode(['type' => 'page', 'status' => FALSE]);
+
+ $result = views_get_view_result('pages');
+ $nids = array_column($result, 'nid');
+
+ $this->assertEquals([1], $nids);
+}
+```
+
+### Running the Test
+
+The test fails as no extra conditions have been added to the view, though the
+default "Content: Published" filter is already excluding one of the page nodes.
+We can see from the output from the test that node 1 (a page) and node 2 (the
+article) are both being returned.
+
+```
+1) Drupal\Tests\tdd_dublin\Functional\PageListTest::testOnlyPublishedPagesAreShown
+Failed asserting that two arrays are equal.
+--- Expected
++++ Actual
+@@ @@
+ Array (
+- 0 => 1
++ 0 => '2'
++ 1 => '1'
+ )
+
+FAILURES!
+Tests: 1, Assertions: 3, Failures: 1.
+```
+
+### Updating the Test
+
+We can fix this by adding another condition to the view, to only show content
+based on the node type - i.e. only return page nodes.
+
+ { .with-border }
+
+Once the view is updated and the configuration is updated within the module, the
+test should then pass - and it does.
+
+```
+Time: 24.76 seconds, Memory: 6.00MB
+
+OK (1 test, 3 assertions)
+```
+
+## Ensure that the Pages are in the Correct Order
+
+### Writing the Test
+
+As we know that the correct content is being returned, we can now focus on
+displaying it in the correct order. We’ll start again by adding a new test
+method and filling out the comments.
+
+```php
+public function testResultsAreOrderedAlphabetically() {
+ // Given I have multiple nodes with different titles.
+
+ // When I view the pages list.
+
+ // Then I should see pages in the correct order.
+}
+```
+
+To begin with this time, I’ll create a number of different nodes and specify the
+title for each. These are intentionally in the incorrect order alphabetically so
+that we can see the test fail initially and then see it pass after making a
+change so we know that the change worked.
+
+```php
+public function testResultsAreOrderedAlphabetically() {
+ $this->drupalCreateNode(['title' => 'Page A']);
+ $this->drupalCreateNode(['title' => 'Page D']);
+ $this->drupalCreateNode(['title' => 'Page C']);
+ $this->drupalCreateNode(['title' => 'Page B']);
+
+ // When I view the pages list.
+
+ // Then I should see pages in the correct order.
+}
+```
+
+We can use the same method as the previous test to get the returned IDs, using
+`views_get_view_result()` and `array_column()`, and assert that the returned
+node IDs match the expected node IDs in the specified order. Based on the
+defined titles, the order should be 1, 4, 3, 2.
+
+```php
+public function testResultsAreOrderedAlphabetically() {
+ $this->drupalCreateNode(['title' => 'Page A']);
+ $this->drupalCreateNode(['title' => 'Page D']);
+ $this->drupalCreateNode(['title' => 'Page C']);
+ $this->drupalCreateNode(['title' => 'Page B']);
+
+ $nids = array_column(views_get_view_result('pages'), 'nid');
+
+ $this->assertEquals([1, 4, 3, 2], $nids);
+}
+```
+
+### Running the Test
+
+As expected the test fails, as the default sort criteria in the view orders the
+results by their created date.
+
+In the test output, we can see the returned results are in sequential order so
+the results array does not match the expected one.
+
+This would be particularly more complicated to test if I was using `drupalGet()`
+and having to parse the HTML, compared to getting the results as an array from
+the view programmatically.
+
+```
+1) Drupal\Tests\tdd_dublin\Functional\PageListTest::testResultsAreOrderedAlphabetically
+Failed asserting that two arrays are equal.
+--- Expected
++++ Actual
+@@ @@
+ Array (
+- 0 => 1
+- 1 => 4
+- 2 => 3
+- 3 => 2
++ 0 => '1'
++ 1 => '2'
++ 2 => '3'
++ 3 => '4'
+ )
+
+FAILURES!
+Tests: 1, Assertions: 2, Failures: 1.
+```
+
+### Updating the Test
+
+This can be fixed by removing the default sort criteria and adding a new one
+based on "Content: Title".
+
+ { .with-border }
+
+Again, once the view has been updated and exported, the test should pass - and
+it does.
+
+```
+Time: 27.55 seconds, Memory: 6.00MB
+
+OK (1 test, 2 assertions)
+```
+
+## Ensure all Tests Still Pass
+
+Now we know that all the tests pass individually, all of the module tests should
+now be run to ensure that they all still pass and that there have been no
+regressions due to any of the changes.
+
+```
+docker@cli:/var/www$ vendor/bin/phpunit -c core modules/custom/tdd_dublin/tests
+
+Testing modules/custom/tdd_dublin/tests
+...
+
+Time: 1.27 minutes, Memory: 6.00MB
+
+OK (3 tests, 6 assertions)
+```
+
+They all pass, so we be confident that the code works as expected, we can
+continue to refactor if needed, and if any changes are made to this module at a
+later date, we have the tests to ensure that any regressions are caught and
+fixed before deployment.
+
+## Next Steps
+
+I’ve started looking into whether some of the tests can be rewritten as kernel
+tests, which should result in quicker test execution. I will post any updated
+code to the [GitHub repository][3], and will also do another blog post
+highlighting the differences between functional and kernel tests and the steps
+taken to do the conversion.
+
+[0]: {{site.url}}/talks/tdd-test-driven-drupal
+[1]: http://2017.drupal.ie
+[2]: https://github.com/opdavies/tdd_dublin
+[3]: https://packagist.org/packages/tightenco/collect
+[4]: http://php.net/manual/en/function.array-column.php
+[5]: https://gist.github.com/opdavies/dc5f0cea46ccd349b34a9f3a463c14bb
diff --git a/sculpin/source/_posts/zenophile.md b/sculpin/source/_posts/zenophile.md
new file mode 100644
index 000000000..b18e3114e
--- /dev/null
+++ b/sculpin/source/_posts/zenophile.md
@@ -0,0 +1,24 @@
+---
+title: Quickly Create Zen Subthemes Using Zenophile
+date: 2010-05-10
+excerpt: How to use the Zenophile module to create a Zen subtheme.
+tags:
+ - drupal-planet
+ - drupal-6
+ - drupal-modules
+ - drupal-theming
+ - zen
+ - zenophile
+---
+
+If you use the [Zen](http://drupal.org/project/zen) theme, then you should also
+be using the [Zenophile](http://drupal.org/project/zenophile) module!
+
+The Zenophile module allows you to very quickly create Zen subthemes from within
+your web browser, as well as editing options such as the site directory where it
+should be placed, the layout type (fixed or fluid), page wrapper and sidebar
+widths, and the placement of the sidebars.
+
+For more information about the Zenophile module, check out
+[this video](http://blip.tv/file/2427703) by
+[Elliott Rothman](http://elliottrothman.com).
diff --git a/sculpin/source/_presentations/about-drupal-association.md b/sculpin/source/_presentations/about-drupal-association.md
new file mode 100644
index 000000000..5703da3f3
--- /dev/null
+++ b/sculpin/source/_presentations/about-drupal-association.md
@@ -0,0 +1,10 @@
+---
+title: About the Drupal Association
+description: An impromptu talk about what the Drupal Association is, and what work I’ve been doing since I joined the Association staff.
+events:
+ - name: South Wales Drupal user group (SWDUG)
+ location: Cardiff, UK
+ date: 2014-08-19
+---
+
+An impromptu talk about what the Drupal Association is, and what work I’ve been doing since I joined the Association staff.
diff --git a/sculpin/source/_presentations/automated-testing-lightning-talk.md b/sculpin/source/_presentations/automated-testing-lightning-talk.md
new file mode 100644
index 000000000..1919aa2f6
--- /dev/null
+++ b/sculpin/source/_presentations/automated-testing-lightning-talk.md
@@ -0,0 +1,14 @@
+---
+title: Automated testing crash course
+description: A crash course in automated testing with Drupal and test-driven development (lightning talk).
+events:
+ - name: NWDUG
+ location: Manchester, UK
+ date: 2023-08-08
+ url: https://www.meetup.com/nwdrupal/events/293429104
+ online: true
+---
+
+A short talk introducing automated testing with Drupal and test-driven development.
+
+Example repository:
diff --git a/sculpin/source/_presentations/automated-testing-test-driven-development-drupal-8.md b/sculpin/source/_presentations/automated-testing-test-driven-development-drupal-8.md
new file mode 100644
index 000000000..6d1305761
--- /dev/null
+++ b/sculpin/source/_presentations/automated-testing-test-driven-development-drupal-8.md
@@ -0,0 +1,17 @@
+---
+title: Automated testing and Test-Driven Development in Drupal 8
+description: A workshop that I gave about automated testing and test driven development in Drupal 8.
+events:
+ - name: Drupal Bristol
+ location: Bristol, UK
+ date: 2018-06-27
+ - name: DrupalCamp London 2020
+ location: London, UK
+ url: ~
+ date: 2020-03-13
+ - name: DrupalCamp NYC
+ location: New York, USA
+ url: https://2020.drupalcamp.nyc/training/automated-testing-and-test-driven-development-drupal-8
+ date: 2020-11-14
+ online: true
+---
diff --git a/sculpin/source/_presentations/building-build-configs.md b/sculpin/source/_presentations/building-build-configs.md
new file mode 100644
index 000000000..8fa5f789e
--- /dev/null
+++ b/sculpin/source/_presentations/building-build-configs.md
@@ -0,0 +1,29 @@
+---
+title: Building "Build Configs"
+description: In this lightning talk, I discuss the "Build Configs" tool I've built to create configuration files.
+speakerdeck:
+ id: e1403f0dfd8245e5a71df9761dc6bc7b
+ ratio: "1.78343949044586"
+ url: https://speakerdeck.com/opdavies/building-build-configs
+video: ~
+image: ~
+events:
+ - name: PHP South West (PHPSW)
+ location: Bristol, UK
+ date: 2023-11-08
+ url: https://www.meetup.com/php-sw/events/296917370
+ - name: PHP UserGroup Munich
+ date: 2024-01-23
+ url: https://www.meetup.com/phpugmunich/events/298411540
+ online: true
+ - name: PHP Oxford
+ location: Oxford, UK
+ date: 2024-01-25
+ url: https://www.meetup.com/php-oxford/events/297556113
+---
+
+{% block abstract %}
+[Build Configs](/build-configs) is a tool I've created for creating and managing customised per-project configuration files, such as Docker, Docker Compose, Nix Flakes, and PHPUnit and PHPStan configuration.
+
+In this lightning talk, I'll discuss why I created it, what problem it solves, more about what it does, and how it works.
+{% endblock %}
diff --git a/sculpin/source/_presentations/communities-contribution.md b/sculpin/source/_presentations/communities-contribution.md
new file mode 100644
index 000000000..60c07c815
--- /dev/null
+++ b/sculpin/source/_presentations/communities-contribution.md
@@ -0,0 +1,21 @@
+---
+title: Communities and contribution
+description: Why I mentor, why I've organised meetups and conferences, why I give talks, and why I maintain and contribute to open-source software.
+speakerdeck:
+ id: 63c2fcc2294641688f433d062282a968
+ ratio: "1.78343949044586"
+ url: https://speakerdeck.com/opdavies/building-and-presenting-slide-decks-with-rst2pdf
+events:
+ - name: TechConnect London
+ location: London, UK
+ date: 2023-09-28
+ url: https://www.eventbrite.co.uk/e/techconnect-london-tickets-698416712187
+---
+
+{% block abstract %}
+I'm a self-taught Developer who's been writing code since 2007, working with and contributing to open-source technologies. I've been a mentor at events like DrupalCon and for bootcamps, including School of Code.
+
+I'm part of different open-source and tech communities, organised meetups and conferences, and have given nearly 100 presentations.
+
+In this talk, I'll speak more about the communities I'm part of, my contributions, how I started, why I do it, and who benefits and how.
+{% endblock %}
diff --git a/sculpin/source/_presentations/configuring-all-the-things-drupal-8.md b/sculpin/source/_presentations/configuring-all-the-things-drupal-8.md
new file mode 100644
index 000000000..6f85a4646
--- /dev/null
+++ b/sculpin/source/_presentations/configuring-all-the-things-drupal-8.md
@@ -0,0 +1,18 @@
+---
+title: "Configuring all the Things (in Drupal 8)"
+description: A short notice talk on configuration management in Drupal 8, and things I’ve learned working on my current Drupal 8 project.
+speakerdeck:
+ id: 6de3fe8947a34727b79eb9d9dcc66bf2
+ ratio: "1.37081659973226"
+ url: https://speakerdeck.com/opdavies/configuring-all-the-things-in-drupal-8
+video: ~
+events:
+ - name: Drupal Bristol
+ location: Bristol, UK
+ url: https://www.drupalbristol.org.uk
+ date: 2018-07-25
+---
+
+{% block abstract %}
+A short notice talk on configuration management in Drupal 8, and things I’ve learned working on my current Drupal 8 project.
+{% endblock %}
diff --git a/sculpin/source/_presentations/dancing-for-drupal.md b/sculpin/source/_presentations/dancing-for-drupal.md
new file mode 100644
index 000000000..6b46f597b
--- /dev/null
+++ b/sculpin/source/_presentations/dancing-for-drupal.md
@@ -0,0 +1,31 @@
+---
+title: Dancing for Drupal
+description: A talk on Drupal, presented alongside others representing Umbraco, Sitecore and Episerver.
+speakerdeck:
+ id: ffa9b6dea6dc4a8eb207b9982ed6e1bd
+ ratio: "1.33333333333333"
+ url: https://speakerdeck.com/opdavies/umbristol-dancing-for-drupal
+events:
+ - name: umBristol
+ location: Bristol, UK
+ url: http://umbristol.co.uk
+ date: 2015-08-25
+---
+
+{% block abstract %}
+As part of their [CMS Dance-Off][1], I was selected to speak about Drupal alongside other speakers representing Umbraco, Sitecore and Episerver.
+
+The points to cover were:
+
+- How does your CMS store configuration data, 'structure' and content?
+- How do you manage source-control, versioning and deployment?
+- How do you manage multiple simultaneous editors and/or developers?
+- How do you manage upgrades?
+- What are the 3 best things about your CMS?
+- What are the 3 worst things about your CMS?
+- What does the future of your CMS look like?
+
+In each point, tried to cover the differences between Drupal core and developing a project using Drupal, as well as between Drupal 7 and Drupal 8, as things like source control and versioning would be approached differently.
+
+[1]: http://www.meetup.com/umBristol/events/223807592
+{% endblock %}
diff --git a/sculpin/source/_presentations/decoupling-drupal-vuejs.md b/sculpin/source/_presentations/decoupling-drupal-vuejs.md
new file mode 100644
index 000000000..986547ee4
--- /dev/null
+++ b/sculpin/source/_presentations/decoupling-drupal-vuejs.md
@@ -0,0 +1,20 @@
+---
+title: Decoupling Drupal with Vue.js
+description: Decoupling Drupal with Vue.js.
+code: https://github.com/opdavies/blue-conf-2019
+speakerdeck:
+ id: 60c8b7abdf194946b7a78a1bb74a0982
+ ratio: "1.77777777777778"
+ url: https://speakerdeck.com/opdavies/decoupling-drupal-with-vue-dot-js
+events:
+ - name: Blue Conf 2019
+ location: Cardiff, UK
+ url: https://blueconf.co.uk
+ date: 2019-06-07
+---
+
+{% block abstract %}
+Decoupled or headless Drupal has been a trend for a number of years, with modules like RESTful Web Services available for Drupal 7 to expose data, and Drupal 8 becoming more API-first with JSON:API module now included as part of core. This makes it easier for third party systems or alternative front-end applications to consume and work with the data provided by Drupal.
+
+In this talk, we’ll look at how to build progressively and fully decoupled applications - passing data from a Drupal 8 back-end to a Vue.js front-end.
+{% endblock %}
diff --git a/sculpin/source/_presentations/deploying-drupal-fabric.md b/sculpin/source/_presentations/deploying-drupal-fabric.md
new file mode 100644
index 000000000..b1f54b7a5
--- /dev/null
+++ b/sculpin/source/_presentations/deploying-drupal-fabric.md
@@ -0,0 +1,33 @@
+---
+title: "Deploying Drupal with Fabric"
+description: How to use Fabric, a Python command line based library, to deploy your Drupal applications.
+meta:
+ og:
+ title: Deploying Drupal with Fabric
+ description: "You've built your Drupal site, now learn how to deploy it with Fabric."
+ type: website
+speakerdeck:
+ id: 40d1eca4bd484afc86295924fff5dd41
+ ratio: "1.77777777777778"
+ url: "https://speakerdeck.com/opdavies/deploying-drupal-and-anything-else-with-fabric"
+ embed: ''
+events:
+ - name: DrupalCamp Dublin 2017
+ location: Dublin, Ireland
+ url: http://2017.drupal.ie
+ date: 2017-10-20
+ time: "15:00 - 15:40"
+ - name: Drupal Somerset
+ location: Glastonbury, UK
+ date: 2017-10-26
+---
+
+{% block abstract %}
+You’ve built your website, and now you just need to deploy it. There are various ways that this could be done - from (S)FTP, to SCP and rsync, to running commands like “git pull” and “composer install” directly on the server (not recommended).
+
+My favourite deployment tool of late is [Fabric][1] - a Python based command line tool for running commands locally as well as on remote servers. It’s language and framework agnostic, and unopinionated so you define the steps and workflow that you need - from a basic few-step deployment to a full Capistrano style zero-downtime deployment.
+
+This talk will cover some introduction to Fabric and how to write your own fabfiles, to then covering some examples and demos of different use case deployments for your Drupal project.
+
+[1]: http://www.fabfile.org
+{% endblock %}
diff --git a/sculpin/source/_presentations/deploying-php-ansible-ansistrano.md b/sculpin/source/_presentations/deploying-php-ansible-ansistrano.md
new file mode 100644
index 000000000..4933423da
--- /dev/null
+++ b/sculpin/source/_presentations/deploying-php-ansible-ansistrano.md
@@ -0,0 +1,86 @@
+---
+title: Deploying PHP applications with Ansible, Ansible Vault and Ansistrano
+description: |
+ How to use Ansible and Ansistrano to perform robust, secure deployments of your PHP applications.
+speakerdeck:
+ id: c11fe635ed8f4741b35bf3ebe53e8323
+ ratio: "1.77777777777778"
+ url: https://speakerdeck.com/opdavies/deploying-php-applications-with-ansible-ansible-vault-and-ansistrano
+video:
+ type: youtube
+ id: dQL-gOnxXCM
+events:
+ - name: Drupal Bristol
+ date: 2019-01-23
+ location: Bristol, UK
+ url: https://www.drupalbristol.org.uk
+ - name: PHP South Wales
+ date: 2019-07-23
+ location: Cardiff, UK
+ url: https://www.phpsouthwales.uk
+ - name: DrupalCon Europe 2019
+ date: 2019-10-30
+ location: Amsterdam, NL
+ url: https://events.drupal.org/amsterdam2019
+ - name: Bristol Cloud Native & DevOps
+ date: 2020-01-30
+ location: Bristol, UK
+ url: https://www.meetup.com/Bristol-Cloud-Native-DevOps/events/266609627
+ - name: Drupal Edinburgh
+ date: 2020-03-12
+ location: Edinburgh, UK
+ url: https://www.meetup.com/Drupal-Edinburgh/events/267905594
+ online: true
+ - name: CMS Philly
+ date: 2020-05-01
+ location: Philadelphia, USA
+ url: https://cmsphilly.org
+ online: true
+ - name: Drupal Yorkshire
+ date: 2020-05-21
+ location: Leeds, UK
+ url: https://www.meetup.com/DrupalYorkshire/events/zwzsfpybchbcc
+ online: true
+ - name: PHP London
+ date: 2020-06-04
+ location: London, UK
+ url: https://www.meetup.com/phplondon/events/270930524
+ online: true
+ - name: PHP North East
+ date: 2020-06-16
+ location: Newcastle Upon Tyne, UK
+ url: https://www.meetup.com/phpnortheast
+ online: true
+ - name: PHP Sussex
+ date: 2020-07-01
+ location: Brighton, UK
+ url: https://www.meetup.com/PHP-Sussex/events/271472628
+ online: true
+ - name: Midwest PHP
+ date: 2021-04-23
+ url: https://midwestphp.org/talks/1q5XUF2tTdXXLYOoujMkpF/Deploying_PHP_applications_with_Ansible_Ansible_Vault_and_Ansistrano
+ online: true
+ - name: PHP Oxford
+ date: 2021-04-28
+ location: Oxford, UK
+ url: https://www.meetup.com/PHP-Oxford/events/qmbkfsyccgblc
+ online: true
+ - name: Ansible London
+ date: 2021-05-25
+ location: London, UK
+ url: https://www.meetup.com/Ansible-London/events/278093392
+ online: true
+ - name: DrupalNYC
+ date: 2021-06-15
+ location: New York, USA
+ url: https://ti.to/drupalnyc/lunch-learn-2021-06-15
+ online: true
+---
+
+{% block abstract %}
+Great! You’ve built your website, and now you just need to deploy it. There are various ways that this could be done - from (S)FTP, to SCP and rsync, to running commands like `git pull` and `composer install` directly on the server which is not ideal.
+
+As well provisioning and maintaining your server configuration and running commands, you can also use [Ansible](https://www.ansible.com) to deploy your PHP application - leveraging relevant Ansible modules such as Git and Composer, custom Ansible roles, [Ansible Vault](https://docs.ansible.com/ansible/latest/user_guide/vault.html) for managing secrets, and features such as idempotency out of the box to build a simple deployment playbook. We can then extend that and make it more robust by adding [Ansistrano](https://ansistrano.com) - a port of [Capistrano](https://capistranorb.com) - which adds extra features such as storing multiple builds for each project and the ability to roll-back if needed, customising your build steps using built-in hooks, multi-stage environments and more.
+
+I've been using Ansible and Ansistrano to deploy a variety of PHP projects - including Drupal 7 & 8, Symfony, Laravel and Sculpin, as well as basic HTML websites, and found it to be very flexible and easy to install and use, and by the end of this talk we will have a fully working deployment playbook, deploying real code onto a real server.
+{% endblock %}
diff --git a/sculpin/source/_presentations/deploying-php-fabric.md b/sculpin/source/_presentations/deploying-php-fabric.md
new file mode 100644
index 000000000..182d82ae9
--- /dev/null
+++ b/sculpin/source/_presentations/deploying-php-fabric.md
@@ -0,0 +1,42 @@
+---
+title: Deploying PHP Applications with Fabric
+description: How to use Fabric, a Python command line based library, to deploy your PHP applications.
+speakerdeck:
+ id: c147618ce07546ca92f92983c52d6a41
+ ratio: "1.77777777777778"
+ url: https://speakerdeck.com/opdavies/deploying-php-applications-with-fabric
+meta:
+ og:
+ image:
+ url: '/assets/images/talks/deploying-php-fabric.png'
+ width: 2560
+ height: 1440
+ type: image/png
+events:
+ - name: Nomad PHP
+ date: 2017-04-20
+ time: "19:00 (CET)"
+ url: https://nomadphp.com
+ online: true
+ - name: PHP South West
+ location: Bristol, UK
+ url: https://phpsw.uk
+ date: 2017-09-13
+ joindin: https://joind.in/talk/a5ff3
+ - name: PHP North West 2017
+ location: Manchester, UK
+ date: 2017-10-01
+ time: "09:00 - 09:45"
+ url: http://conference.phpnw.org.uk/phpnw17
+ joindin: https://joind.in/talk/4e35d
+---
+
+{% block abstract %}
+You’ve built your application, and now you just need to deploy it. There are various ways that this could be done – from (S)FTP, to SCP and rsync, to running commands like “git pull” and “composer install” directly on the server (not recommended).
+
+My favourite deployment tool of late is [Fabric][1] – a Python based command line tool for running commands locally as well as on remote servers. It’s language and framework agnostic, and unopinionated so you define the steps and workflow that you need – from a basic few-step deployment to a full Capistrano style zero-downtime deployment.
+
+This talk will cover some introduction to Fabric and how to write your own fabfiles, and then look at some examples of different use case deployments for your PHP project.
+
+[1]: http://www.fabfile.org
+{% endblock %}
diff --git a/sculpin/source/_presentations/drupal-8-module-development.md b/sculpin/source/_presentations/drupal-8-module-development.md
new file mode 100644
index 000000000..c0a4d9a6c
--- /dev/null
+++ b/sculpin/source/_presentations/drupal-8-module-development.md
@@ -0,0 +1,40 @@
+---
+title: Getting Started with Drupal 8 Module Development
+description: How to build your first module for Drupal 8.
+code: https://github.com/opdavies/dclondon16-d8-module
+speakerdeck:
+ id: 0041804e52664d12a8e31cd118264813
+ ratio: "1.77777777777778"
+ url: https://speakerdeck.com/opdavies/getting-started-with-drupal-8-module-development
+video:
+ type: youtube
+ id: qO_Wh5WE3VA
+meta:
+ og:
+ image:
+ url: '/assets/images/talks/dclondon16.png'
+ type: "image/png"
+ height: 540
+ width: 960
+events:
+ - name: DrupalCamp London 2016
+ location: London, UK
+ date: 2016-03-05
+---
+
+{% block abstract %}
+New to object-orientated PHP, Symfony or YAML, and want to get started building modules in Drupal 8? This is the session for you!
+
+In this session, we’ll cover:
+
+- Where Drupal 8 modules are located, and how they are structured.
+- How to build a simple module, including our own permissions and routes.
+- How to add your own controller and service classes.
+- What is the service/dependency injection container, and how do we use it?
+- How we can use tools such as PhpStorm and Drupal Console to speed up the process.
+
+What we won’t be covering:
+
+- Automated testing in PHPUnit or Simpletest.
+- Adding third party libraries and external dependencies via Composer.
+{% endblock %}
diff --git a/sculpin/source/_presentations/drupal-8-php-libraries-drupalorg-api.md b/sculpin/source/_presentations/drupal-8-php-libraries-drupalorg-api.md
new file mode 100644
index 000000000..35e93e2b3
--- /dev/null
+++ b/sculpin/source/_presentations/drupal-8-php-libraries-drupalorg-api.md
@@ -0,0 +1,57 @@
+---
+title: Having Fun with Drupal 8, PHP libraries and the Drupal.org API
+description: A crash course in developing PHP packages and Drupal 8 modules, based on the Drupal.org API.
+speakerdeck:
+ id: 6e42ae9620bb4e91b3955f8c30d66934
+ ratio: "1.77777777777778"
+ url: https://speakerdeck.com/opdavies/having-fun-with-drupal-8-php-libraries-and-the-drupal-dot-org-api
+image:
+ url: '/assets/images/talks/having-fun-drupalorg-api.png'
+ width: 2000
+ height: 1125
+ type: image/png
+video:
+ type: youtube
+ id: JyDjC7gGDpU
+events:
+ - name: Drupal Bristol
+ location: Bristol, UK
+ url: https://www.drupalbristol.org.uk
+ date: 2018-04-18
+ joindin: https://joind.in/talk/14851
+ - name: DrupalCamp London 2019
+ location: London, UK
+ url: ~
+ date: 2019-03-03
+ time: "12:05 - 12:50"
+---
+
+{% block abstract %}
+A overview and demo of some of the open source projects that I’ve been working on lately that are based on information from the Drupal.org API, including a PHP library for the API itself as well as some Drupal 8 modules that use it.
+
+This session will cover various topics including:
+
+- Why would you want to separate your code into reusable packages
+- An overview of how to structure a PHP package (e.g. an API for interacting with Drupal.org)
+- How to add a PHP package as a dependency for a Drupal module using Composer
+- How to create routes and services, and use dependency injection in Drupal 8
+- How to use Drupal to configure the packages
+- How to increase performance and reliability by using Drupal's cache system to store API results
+
+## Links
+
+- [Drupal.org API library][2]
+- [Drupal.org API documentation][3]
+- [Laravel Collections][4]
+- Example module: [Drupal.org project statistics][5]
+- Example module: [Drupalversary][6]
+- Talk: [Using Laravel Collections outside Laravel][7]
+
+[0]: https://www.drupalbristol.org.uk
+[2]: https://github.com/opdavies/drupalorg-api-php
+[3]: https://www.drupal.org/drupalorg/docs/api
+[4]: https://laravel.com/docs/collections
+[5]: https://github.com/opdavies/drupal-module-drupalorg-project-statistics
+[6]: https://github.com/opdavies/drupal-module-drupalversary
+[7]: /presentations/using-laravel-collections-outside-laravel/
+{% endblock %}
diff --git a/sculpin/source/_presentations/drupal-8-rejoining-the-herd.md b/sculpin/source/_presentations/drupal-8-rejoining-the-herd.md
new file mode 100644
index 000000000..0a03f4d9a
--- /dev/null
+++ b/sculpin/source/_presentations/drupal-8-rejoining-the-herd.md
@@ -0,0 +1,22 @@
+---
+title: "Drupal 8: Rejoining the Herd"
+description: A talk highlighting some of the recent technical and non-technical changes in Drupal 8.
+speakerdeck:
+ id: 440fd6592f474741bc606c96bc32c104
+ ratio: "1.37081659973226"
+ url: https://speakerdeck.com/opdavies/drupal-rejoining-the-herd
+events:
+ - name: PHP South Coast 2016
+ location: Portsmouth, UK
+ url: http://2016.phpsouthcoast.co.uk
+ date: 2016-06-11
+ joindin: https://joind.in/talk/41d0f
+---
+
+{% block abstract %}
+[Drupal 8][0] was (finally) released on November 19th 2015, after almost 4 years of work and code commits by over 3,200 different contributors. Whilst it’s pretty much the same as the Drupal that we know and, hopefully, love, a lot has changed behind the scenes and under the hood!
+
+In this talk, I'll highlight some of the new features and improvements in Drupal 8, and discuss some of the benefits to Drupal site builders, themers, and module developers. I'll also talk about some of the non-technical changes and the cultural shift from "not invented here" to "proudly found elsewhere", and how we are rejoining the PHP herd.
+
+[0]: https://www.drupal.org/8
+{% endblock %}
diff --git a/sculpin/source/_presentations/drupal-8.md b/sculpin/source/_presentations/drupal-8.md
new file mode 100644
index 000000000..bde9b63e4
--- /dev/null
+++ b/sculpin/source/_presentations/drupal-8.md
@@ -0,0 +1,23 @@
+---
+title: Drupal 8
+description:
+ A lightning talk presented to the PHPSW user group, highlighting some of the relevant changes in Drupal 8.
+speakerdeck:
+ url: https://speakerdeck.com/opdavies/drupal-8
+ id: 46ba4ba577d94a32b7abdade610ceb69
+ ratio: "1.29456384323641"
+video:
+ type: youtube
+ id: 36zCxPrOOzM
+events:
+ - name: PHP South West
+ location: Bristol, UK
+ url: https://phpsw.uk
+ date: 2015-04-08
+---
+
+{% block abstract %}
+This was a ten minute lightning talk, designed to highlight the major changes coming in Drupal 8.
+
+I categorised the technical changes into groups for site builders, developers and themers, and also highlighted the cultural shift from "not invented here" to "proudly found elsewhere" and the benefits that brings.
+{% endblock %}
diff --git a/sculpin/source/_presentations/drupal-ldap-module.md b/sculpin/source/_presentations/drupal-ldap-module.md
new file mode 100644
index 000000000..39e6b4709
--- /dev/null
+++ b/sculpin/source/_presentations/drupal-ldap-module.md
@@ -0,0 +1,12 @@
+---
+title: Drupal and the LDAP module
+description: A review and demonstration of some of the recent single sign-on work that I did using Drupal’s LDAP module.
+events:
+ - name: South Wales Drupal user group (SWDUG)
+ location: Cardiff, UK
+ date: 2013-07-10
+---
+
+{% block abstract %}
+A review and demonstration of some of the recent single sign-on work that I did using Drupal’s LDAP module.
+{% endblock %}
diff --git a/sculpin/source/_presentations/drupal-vm-generator.md b/sculpin/source/_presentations/drupal-vm-generator.md
new file mode 100644
index 000000000..af7db1cd5
--- /dev/null
+++ b/sculpin/source/_presentations/drupal-vm-generator.md
@@ -0,0 +1,26 @@
+---
+title: Drupal VM Generator
+description: Announcing the Drupal VM Generator CLI tool.
+type: Lightning talk
+code: https://github.com/opdavies/drupal-vm-generator
+speakerdeck:
+ id: a27ee1d2bfed4a209dc395fa455acb41
+ ratio: "1.37081659973226"
+ url: https://speakerdeck.com/opdavies/bristol-dug-drupal-vm-generator
+video:
+ type: youtube
+ id: U1pbKAAO2Wo
+events:
+ - name: NWDUG
+ url: http://nwdrupal.org.uk
+ location: Manchester, UK
+ date: 2016-03-08
+ - name: Drupal Bristol
+ location: Bristol, UK
+ url: https://www.drupalbristol.org.uk
+ date: 2016-04-02
+---
+
+An short talk about the [Drupal VM Generator][1] project.
+
+[1]: https://github.com/opdavies/drupal-vm-generator
diff --git a/sculpin/source/_presentations/drupal-vm-meet-symfony-console.md b/sculpin/source/_presentations/drupal-vm-meet-symfony-console.md
new file mode 100644
index 000000000..0f6864a1e
--- /dev/null
+++ b/sculpin/source/_presentations/drupal-vm-meet-symfony-console.md
@@ -0,0 +1,26 @@
+---
+title: Drupal VM, Meet Symfony Console
+description: How to develop command line applications using Symfony Console, using the Drupal VM CLI as an example.
+speakerdeck:
+ id: 56c79770f73f4e47a542a30243437c49
+ ratio: "1.37081659973226"
+ url: https://speakerdeck.com/opdavies/drupal-vm-meet-symfony-console
+image: drupal-vm-meet-symfony-console.png
+events:
+ - name: DrupalCamp Bristol 2016
+ location: Bristol, UK
+ date: 2016-07-23
+---
+
+{% block abstract %}
+_TL;DR - Come and learn about Symfony Console, with examples from a real-world
+project._
+
+The [Drupal VM Generator][2] is a CLI application, built on [Symfony Console][0], that generates configuration files for [Drupal VM][1] based on personal settings and user interaction.
+
+After an introduction to Drupal VM itself and the Drupal VM Generator, we’ll jump into the code and see how Symfony Console applications are structured, how to write new commands, and how to integrate additional libraries like Guzzle, Twig and other Symfony components - whilst referencing code from the Drupal VM Generator project.
+
+[0]: http://symfony.com/doc/current/components/console/introduction.html
+[1]: https://www.drupalvm.com
+[2]: https://www.drupalvmgenerator.com
+{% endblock %}
diff --git a/sculpin/source/_presentations/drupalorg-2015.md b/sculpin/source/_presentations/drupalorg-2015.md
new file mode 100644
index 000000000..25f54c521
--- /dev/null
+++ b/sculpin/source/_presentations/drupalorg-2015.md
@@ -0,0 +1,17 @@
+---
+title: "Drupal.org in 2015: What's Coming Next"
+description: A retrospective of the Drupal Association’s work in 2014 and a look forward to what we’ll be working on in 2015.
+speakerdeck:
+ id: 0cf8d7b647c94ae289e9db2b46a9e8f2
+ ratio: "1.77777777777778"
+ url: https://speakerdeck.com/opdavies/drupal-dot-org-in-14
+events:
+ - name: DrupalCamp Brighton
+ location: Brighton, UK
+ date: 2015-01-18
+ - name: DrupalCamp London 2015
+ location: London, UK
+ date: 2015-02-28
+---
+
+A retrospective of the Drupal Association’s work in 2014 and a look forward to what we’ll be working on in 2015.
diff --git a/sculpin/source/_presentations/drush-make-drupalbristol.md b/sculpin/source/_presentations/drush-make-drupalbristol.md
new file mode 100644
index 000000000..4debe14d3
--- /dev/null
+++ b/sculpin/source/_presentations/drush-make-drupalbristol.md
@@ -0,0 +1,15 @@
+---
+title: drush make drupalbristol
+description: How to Drush Make to build your Drupal websites.
+speakerdeck:
+ id: 42605700f102013198de5a5f6f23ab67
+ ratio: "1.29456384323641"
+ url: https://speakerdeck.com/opdavies/drush-make-drupalbristol
+events:
+ - name: Drupal Bristol
+ location: Bristol, UK
+ url: https://www.drupalbristol.org.uk
+ date: 2014-08-19
+---
+
+An introduction to Drush Make and how to use it to build reusable custom installation profiles or entire websites.
diff --git a/sculpin/source/_presentations/getting-your-data-into-drupal-8.md b/sculpin/source/_presentations/getting-your-data-into-drupal-8.md
new file mode 100644
index 000000000..cef269b44
--- /dev/null
+++ b/sculpin/source/_presentations/getting-your-data-into-drupal-8.md
@@ -0,0 +1,35 @@
+---
+title: Getting (Your Data) Into Drupal 8
+description: An overview of Drupal’s Migrate functionality, and a look at how to write your own migrations.
+speakerdeck:
+ id: 63e5dfce996e46699e304d50e896477b
+ ratio: "1.77777777777778"
+ url: "https://speakerdeck.com/opdavies/getting-your-data-into-drupal-8-drupal_bristol"
+video:
+ type: youtube
+ id: jtmARTuYhp8
+meta:
+ og:
+ image:
+ url: '/assets/images/talks/getting-your-data-into-drupal-8.png'
+ width: 2560
+ height: 1440
+ type: image/png
+events:
+ - name: Drupal Bristol
+ date: 2017-01-18
+ location: Bristol, UK
+ url: https://www.drupalbristol.org.uk
+ - name: DrupalCamp London 2017
+ date: 2017-03-04
+ time: "12:05 - 12:50"
+ location: London, UK
+---
+
+{% block abstract %}
+If you’ve moved a site from Drupal 6 to 7, the chances are that you’ve either used the upgrade path to update your old site in-place, or you built a new site from scratch and used the Migrate module from contrib to migrate your data from the old database.
+
+In Drupal 8, things have changed as there’s no upgrade path from Drupal 7 and the Migrate module has been moved into core, though there are still migration related modules available in contrib.
+
+This talk will look at the core Migrate module and how it implements Drupal 8 features such as YAML and the plugin and configuration systems, and how to write your own migrations to get your data into Drupal 8.
+{% endblock %}
diff --git a/sculpin/source/_presentations/git-flow.md b/sculpin/source/_presentations/git-flow.md
new file mode 100644
index 000000000..5b5ddcf8b
--- /dev/null
+++ b/sculpin/source/_presentations/git-flow.md
@@ -0,0 +1,17 @@
+---
+title: Never Commit to Master - An Introduction to Git Flow
+description: An introduction to and demonstration of the Git Flow branching model.
+speakerdeck:
+ id: 201559e0f103013198dd5a5f6f23ab67
+ ratio: "1.29456384323641"
+ url: https://speakerdeck.com/opdavies/never-commit-to-master-an-introduction-to-git-flow
+video:
+ type: youtube
+ id: T-miCpHxfds
+events:
+ - name: DrupalCamp London 2014
+ location: London, UK
+ date: 2014-03-01
+---
+
+An introduction to the Git Flow branching model and the git-flow plugin, and how I’ve used them to manage a Drupal development project.
diff --git a/sculpin/source/_presentations/goodbye-drush-make-hello-composer.md b/sculpin/source/_presentations/goodbye-drush-make-hello-composer.md
new file mode 100644
index 000000000..08b566b60
--- /dev/null
+++ b/sculpin/source/_presentations/goodbye-drush-make-hello-composer.md
@@ -0,0 +1,32 @@
+---
+title: Goodbye Drush Make. Hello Composer!
+description: How to use Composer to manage your Drupal applications.
+speakerdeck:
+ id: 1c1e0e129ab34816bd4c4edb5f6642c2
+ ratio: "1.37081659973226"
+ url: https://speakerdeck.com/opdavies/goodbye-drush-make-hello-composer
+video:
+ type: youtube
+ id: ZL2FtRTX9Y8
+events:
+ - name: Drupal Bristol
+ location: Bristol, UK
+ url: https://www.drupalbristol.org.uk
+ date: 2016-11-17
+ - name: PHP UK Conference 2018
+ location: London, UK
+ date: 2018-02-16
+ time: "14:40 - 15:40"
+ url: https://www.phpconference.co.uk
+ joindin: https://joind.in/talk/650ab
+---
+
+{% block abstract %}
+One of the main outcomes of Drupal 8 was “getting off the island” with third-party code included in core and adopting modern best practices from the wider PHP ecosystem - including [Composer][1], PHP’s dependency manager.
+
+Included to manage core’s dependencies, it has also gained traction in the contrib space with the creation of the Drupal Composer project, and the Drupal Packagist and now native endpoints on Drupal.org exposing contrib project metadata.
+
+In this session, I'll show how to fully manage a Drupal 7 or Drupal 8 website including contributed modules and themes and external libraries with Composer.
+
+[1]: https://getcomposer.org
+{% endblock %}
diff --git a/sculpin/source/_presentations/introduction-to-mob-programming.md b/sculpin/source/_presentations/introduction-to-mob-programming.md
new file mode 100644
index 000000000..d018df641
--- /dev/null
+++ b/sculpin/source/_presentations/introduction-to-mob-programming.md
@@ -0,0 +1,20 @@
+---
+title: An introduction to mob programming
+description: |
+ What is mob programming, how does it work, and why should we do it?
+speakerdeck:
+ id: f37b16f915d64bc0b4a20f9f965e5353
+ ratio: '1.77725118483412'
+ url: https://speakerdeck.com/opdavies/an-introduction-to-mob-programming
+events:
+ - name: PHP South Wales
+ location: Cardiff, Wales
+ date: 2022-09-28
+ url: https://www.meetup.com/php-south-wales/events/288359737
+---
+
+{% block abstract %}
+Pair and mob programming are collaborative approaches to software development where tasks are completed in small groups instead of by individuals.
+
+In this talk, I’ll explain more about mob programming, its benefits and how it works, and then we’ll put it into practice with an interactive mob session where we add a feature to a codebase.
+{% endblock %}
diff --git a/sculpin/source/_presentations/it-all-started-with-a-patch.md b/sculpin/source/_presentations/it-all-started-with-a-patch.md
new file mode 100644
index 000000000..52e7cabc6
--- /dev/null
+++ b/sculpin/source/_presentations/it-all-started-with-a-patch.md
@@ -0,0 +1,18 @@
+---
+title: It All Started With A Patch
+description: A lightning talk on how and why to get involved with open source.
+speakerdeck:
+ id: 5862bdecb7a24cfaa5fc844696fafa0c
+ ratio: "1.37081659973226"
+ url: https://speakerdeck.com/opdavies/it-all-started-with-a-patch-phpsw
+video:
+ type: youtube
+ id: 5FYMRR61sdo
+events:
+ - name: PHP South West
+ location: Bristol, UK
+ url: https://phpsw.uk
+ date: 2017-02-08
+---
+
+A crash course of why and how to get involved with open source.
diff --git a/sculpin/source/_presentations/modern-drupal-development-with-composer.md b/sculpin/source/_presentations/modern-drupal-development-with-composer.md
new file mode 100644
index 000000000..8d546b01c
--- /dev/null
+++ b/sculpin/source/_presentations/modern-drupal-development-with-composer.md
@@ -0,0 +1,25 @@
+---
+title: Modern Drupal Development with Composer
+description: A lightning talk on how to use Composer to manage your Drupal projects.
+type: Lightning talk
+speakerdeck:
+ id: 7a1358502526425a9cfd288f85fb32f3
+ ratio: "1.37081659973226"
+ url: https://speakerdeck.com/opdavies/modern-drupal-development-with-composer
+video:
+ type: youtube
+ id: Yi_FPI3xHwc
+events:
+ - name: PHP South West
+ location: Bristol, UK
+ url: https://phpsw.uk
+ date: 2016-11-09
+---
+
+{% block abstract %}
+Building a Drupal application? You no longer need to download archives to add new modules or update core, or deal with Drupal specific tools to manage your codebase.
+
+With Drupal "getting off the island" there has been an increase in the adoption of common PHP tools within the Drupal ecosystem.
+
+In this lightning talk, I’ll show how to build a Drupal application using Composer for dependency management.
+{% endblock %}
diff --git a/sculpin/source/_presentations/nix-for-php-developers.md b/sculpin/source/_presentations/nix-for-php-developers.md
new file mode 100644
index 000000000..6d3b26ce8
--- /dev/null
+++ b/sculpin/source/_presentations/nix-for-php-developers.md
@@ -0,0 +1,27 @@
+---
+title: Nix for PHP Developers
+events:
+ - name: PHP Thames Valley
+ location: Reading, UK
+ date: 2025-08-20
+ urls:
+ slides: /files/presentations/nix-php-developers/php-thames-valley.pdf
+ website: https://www.eventbrite.co.uk/e/php-thames-valley-oxfordshire-and-berkshire-3-x-speakers-networking-tickets-1512713198899
+ - name: unified.diff
+ location: Cardiff, UK
+ date: 2025-09-25
+ urls:
+ slides: ~
+ website: https://www.meetup.com/unified-diff/events/310886970
+new_events: true
+---
+
+{% block description %}
+How PHP Developers can use Nix to create robust and reproducible environments and replace tools like Docker and Vagrant.
+{% endblock %}
+
+{% block abstract %}
+[Nix](https://nixos.org) is a package manager with more than 120,000 packages, an operating system with more than 20,000 options, and a build tool for creating reproducible and reliable software that works the same for everyone, every time.
+
+In this talk, Oliver will explain what Nix is, show how to create development shells for PHP that mean you no longer need to use containers or virtual machines, and how to package PHP applications that are easy to deploy and distribute.
+{% endblock %}
diff --git a/sculpin/source/_presentations/out-of-the-box-initiative-update.md b/sculpin/source/_presentations/out-of-the-box-initiative-update.md
new file mode 100644
index 000000000..545340fce
--- /dev/null
+++ b/sculpin/source/_presentations/out-of-the-box-initiative-update.md
@@ -0,0 +1,28 @@
+---
+title: Out of the Box Initiative Update
+description: An update on Drupal’s "out of the box" initiative, and core’s new Umami installation profile.
+speakerdeck:
+ id: 3f66c48653f44ed4867fc3cc05c1db06
+ ratio: "1.77777777777778"
+ url: https://speakerdeck.com/opdavies/out-of-the-box-initiative-update
+video: ~
+events:
+ - name: Drupal Bristol
+ location: Bristol, UK
+ url: https://www.drupalbristol.org.uk
+ date: 2019-03-27
+---
+
+{% block abstract %}
+From the [DrupalCamp London website](https://drupalcamp.london/session/out-box-initiative-update):
+
+> Since the last DrupalCamp London, the Umami Demo install profile is now one of the options shown to anyone installing Drupal. But this doesn't mean we've finished.
+
+> There are many existing Drupal features that we want to showcase, and new features stabilising with each minor release. Join the Out of the Box initiative leads Keith Jay (kjay) and Gareth Goodwin (smaz) for this session, where we'll talk about:
+
+> - Where we are, and how we got here
+
+- Our targets for inclusion in Drupal 8.7.0
+- How you can help!
+- Plans for the more distant future
+{% endblock %}
diff --git a/sculpin/source/_presentations/reading-college.md b/sculpin/source/_presentations/reading-college.md
new file mode 100644
index 000000000..192961f4b
--- /dev/null
+++ b/sculpin/source/_presentations/reading-college.md
@@ -0,0 +1,20 @@
+---
+title: BTEC Computing at Reading College
+description: I was asked to speak to a group of 40 BTEC Computing students at Reading College.
+video: ~
+events:
+ - name: Reading College
+ location: Reading, UK
+ url: https://reading.activatelearning.ac.uk
+ date: 2025-05-09
+ urls:
+ slides: /files/presentations/reading-college.pdf
+ code: https://code.oliverdavies.uk/opdavies/reading-college
+new_events: true
+---
+
+{% block abstract %}
+After speaking at a recent meetup, I was asked by an attending lecturer to give a presentation to a group of around 40 BTEC Computing students about web development and my experience in the industry.
+
+This was a 90 minute session plus additional Q&A, including demos of building a static website with HTML and CSS, using a static site generator (Sculpin) and a content management system (Drupal).
+{% endblock %}
diff --git a/sculpin/source/_presentations/rst2pdf.md b/sculpin/source/_presentations/rst2pdf.md
new file mode 100644
index 000000000..f91245188
--- /dev/null
+++ b/sculpin/source/_presentations/rst2pdf.md
@@ -0,0 +1,21 @@
+---
+title: Building and presenting slide decks with rst2pdf
+description: A short talk on using reStructuredText and rst2pdf to build presentation slides, built with rst2pdf.
+speakerdeck:
+ id: 80498c7b5e7448f194091461cb14f1c1
+ ratio: "1.77777777777778"
+ url: https://speakerdeck.com/opdavies/building-and-presenting-slide-decks-with-rst2pdf
+video:
+ id: KZ89tGG-p6M
+ type: youtube
+events:
+ - name: PHP South Wales
+ location: Cardiff, UK
+ url: https://www.meetup.com/PHP-South-Wales/events/275625320
+ date: 2021-01-28
+ online: true
+---
+
+{% block abstract %}
+I've recently used [rst2pdf](https://rst2pdf.org) for building presentation slides. This short talk will show some examples of how I built and presented a slide deck using reStructuredText and rst2pdf.
+{% endblock %}
diff --git a/sculpin/source/_presentations/sculpin.md b/sculpin/source/_presentations/sculpin.md
new file mode 100644
index 000000000..04d24dabc
--- /dev/null
+++ b/sculpin/source/_presentations/sculpin.md
@@ -0,0 +1,71 @@
+---
+title: Building Static Websites with Sculpin
+description: How to use Sculpin to generate static HTML websites.
+events:
+ - name: PHP South West
+ location: Bristol, UK
+ date: 2015-10-14
+ url: https://phpsw.uk/events/2015-10-lightning-talks
+ joindin: https://joind.in/talk/view/15486
+ - name: Drupal Yorkshire
+ date: 2021-08-19
+ location: Leeds, UK
+ url: https://www.meetup.com/DrupalYorkshire/events/280100968
+ online: true
+ - name: PHP North West (PHPNW)
+ date: 2021-09-07
+ location: Manchester, UK
+ url: https://www.phpnw.org.uk
+ online: true
+ - name: GroningenPHP
+ date: 2021-12-09
+ location: Groningen, Netherlands
+ url: https://www.meetup.com/GroningenPHP/events/281648855
+ online: true
+ - name: PHP South West
+ date: 2024-02-14
+ location: Bristol, UK
+ urls:
+ video: https://www.youtube.com/watch?v=axy6ltc9meA
+ demo: https://phpsw-sculpin-demo.oliverdavies.uk
+ - name: BrumPHP
+ date: 2024-05-23
+ location: Birmingham, UK
+ url: https://www.eventbrite.com/e/brumphp-23rd-may-2024-tickets-803037766577
+ - name: PHP Berkshire
+ date: 2024-08-28
+ location: Reading, UK
+ urls:
+ slides: /files/presentations/sculpin/php-berkshire.pdf
+ website: https://www.meetup.com/php-berkshire/events/301850284
+ - name: PHP Thames Valley
+ date: 2025-03-20
+ location: Oxford, UK
+ urls:
+ slides: /files/presentations/sculpin/php-thames-valley.pdf
+ code: https://code.oliverdavies.uk/opdavies/sculpin-demo/src/branch/php-thames-valley
+ website: https://www.meetup.com/php-thames-valley/events/305915971
+new_events: true
+---
+
+{% block abstract %}
+[Sculpin][0] is a static site generator written in PHP. It converts Markdown
+files, Twig templates and standard HTML into a static HTML site that can be
+easily deployed.
+
+This talk will cover a little of the background to Sculpin and what it is, and
+then will move into some demonstrations of how to build a Sculpin site and what
+it can do!
+
+[0]: http://sculpin.io
+{% endblock %}
+
+{% block content %}
+
+
+
Another great talk yesterday by Oliver Davies about building static websites using sculpin. learned a lot, even though I was new to it.