Mar 19 2020
Mar 19

Drupal 9 is just around the corner. Here's what you need to do to ensure a smooth upgrade.

Changes from Drupal 8 to Drupal 9

Unlike the Drupal 7 → 8 update, which required a rebuild and migrate, Drupal 9 is a simple incremental update from Drupal 8.

Third-party Dependencies

Drupal 8 is built upon 30+ third-party libraries. In order to keep Drupal stable, these have been kept at major version numbers throughout the Drupal 8 release cycle. Some of these will be end-of-life in coming years, so Drupal 9 is bumping these to newer versions in order to maintain long-term support coverage. The major dependency changes are:

  • Symfony 3.4.x  → 4.4.x
  • Twig 1.x → 2.x

In addition, Drupal 9 requires Drush 10.x or later.

Deprecated Code Removal

Drupal 8 introduced a backwards compatibility policy that required public APIs to remain stable while new features or improvements were added. The old APIs were marked as deprecated, but kept in place for backwards compatibility. This gives developers fair warning that code will be removed in future versions of Drupal giving them time to move to the new APIs.

Drupal 9 removes all deprecated code that has built up during the Drupal 8 release cycle.

Platform requirement changes

In addition, platform requirements have changed. Most importantly, this includes new minimum versions of:

  • PHP 7.3 or later
  • MySQL 5.7 or later

Getting Your Site Ready

Fortunately, there are only a few steps you need to follow in order to get your site ready for Drupal 9.

Update Contributed Modules to their Latest Version

Drupal 9 will work with modules that worked on Drupal 8, so there are no major rewrites required. This means the current module version naming scheme (e.g. 8.x-1.0) doesn't make much sense any more. It will gradually be replaced with a semantic versioning scheme (e.g. 2.1.0) as new module releases are published.

If you are a site builder, the easiest way to ensure your site is compatible with Drupal 9 is to keep your contributed modules up to date. You can check the status of your contributed modules using Acquia's Drupal 9 Deprecation Status page.

Replace Deprecated API Usage

Of course, many Drupal sites include custom modules. In order to get your own modules ready, you need to:

  • replace any usages of deprecated API usage
  • specify core version requirement to include Drupal 9

To automatically find usages of deprecated API usage, there is the excellent Drupal Check tool by Matt Glaman. Follow the installation instructions, then from the command line, run it against your custom module:

drupal-check web/modules/contrib/address

Almost all Drupal deprecations have handy instructions on the newer alternative API methods to use as well as a link to the change record that describes it in more details. For example:

drupal_set_message() is deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Messenger\MessengerInterface::addMessage() instead. See https://www.drupal.org/node/2774931

Reading the change notice, we just need to change:

drupal_set_message("example message");

to:

\Drupal::messenger()->addMessage("example message")

You can run drupal-check again and again until you have no more deprecation messages.

If you aren't comfortable with the command line, there is the excellent Upgrade Status module that you install to give you a user friendly report of what needs updating. There is also Drupal 8 Reactor which can automate some of this for you.

Specify Core Version Requirement

The last step is to specify your modules core compatibility. As per the change record there is a new key to add you your module info.yml file:

core_version_requirement: ^8 || ^9

This new key allows you to use composer-style version constraints and tells Drupal your module is compatible with Drupal 8 and 9.

Summary

That's it! There aren't many steps required to get your site ready for Drupal 9, and the best news is you can do it now and be ready the day Drupal 9 is released.

Photo of Kim Pepper

Posted by Kim Pepper
Technical Director

Dated 19 March 2020

Nov 13 2019
Nov 13

PreviousNext continue to be major contributors to the development and promotion of Drupal 8. As participants of the Drupal 8.8.0 Beta Testing Program, we thought it would be useful to document the steps we took to update one of our sites on Drupal 8.7 to the latest 8.8.0 beta.

Every site is different, so your mileage may vary, but it may save you some time.

Drupal 8.8 is a big release, with a number of new features added, and APIs deprecated to pave the way to a Drupal 9.0 release. Thankfully, the upgrade process was fairly straightforward in our case.

Upgrade PathAuto

First step was to deal with The Path Alias core subsystem has been moved to the "path_alias" module This meant some classes were moved to different namespaces. In order to make things smoother, we installed the latest version of pathauto module and clear the caches.

composer require drupal/pathauto:^[email protected]
drush cr

Core Dev Composer Package

We use the same developer tools for testing as Drupal core, and we want to switch to the new core composer packages, so first we remove the old one.

composer remove --dev webflo/drupal-core-require-dev

Update Patches

We sometimes need to patch core using cweagans/composer-patches. In the case of this site, we are using a patch from ckeditor_stylesheets cache busting: use system.css_js_query_string which needed to be re-rolled for Drupal 8.8.x. We re-rolled the patch, then updated the link in the extra/patches section.

Update Drupal Core and Friends

In our first attempt, composer could not install due to a version conflict with some symfony packages (symfony/finder,  symfony/filesystem and symfony/debug). These are transient dependencies (we don't require them explicitly). Our solution was to explicitly require them (temporarily) with versions that Drupal core is compatible with, then remove them afterwards.

First require new Drupal core and dependencies:

composer require --update-with-dependencies \
  drupal/core:^[email protected] \
  symfony/finder:^3.4 \
  symfony/filesystem:^3.4

Second, require new core-dev package and dependencies:

composer require --dev --update-with-dependencies \
  drupal/core-dev:^[email protected] \
  symfony/debug:^3.4

Lastly, remove the temporary required dependencies:

composer remove -n \
  symfony/finder \
  symfony/filesystem \
  symfony/debug

Update the Database and Export Config

Now our code is updated, we need to update the database schema, then re-export our config. We use drush_cmi_tools, so your commands may be different, e.g. just a drush config-export instead of drush cexy.

drush updb
drush cr
drush cexy

Settings.php

We also need to update our settings.php file now that The sync directory is defined in $settings and not $config_directories.

This is a trivial change from:

$config_directories['sync'] = 'foo/bar';

to:

$settings['config_sync_directory'] = 'foo/bar';

Also we need to move temporary files directory from config to settings.


$config['system.file']['path']['temporary'] = 'foo/bar';

to:


$settings['file_temp_path'] = 'foo/bar';

Final Touches

In order to make sure our code is compatible with Drupal 9, we check for any custom code that is using deprecated APIs using the excellent PHPStan and Matt Glaman's mglaman/phpstan-drupal. (Alternatively you can use Drupal Check.)

 We were using an older version that was incompatible with "nette/bootstrap":">=3" so needed to remove that from the conflict section and do the remove/require dance once again.

composer remove --dev \
  phpstan/phpstan-deprecation-rules \
  mglaman/phpstan-drupal

composer require --dev --update-with-dependencies \
  phpstan/phpstan-deprecation-rules:^0.11.2 \
  mglaman/phpstan-drupal:^0.11.12

And that's it! Altogether not too painful once the composer dependencies were all sorted out. As we are testing the beta, some of these issues may be addressed in future betas and RCs.

I hope you found this useful! Got a better solution? Let us know in the comments!

Update: Added additional settings changes.

Photo of Kim Pepper

Posted by Kim Pepper
Technical Director

Dated 13 November 2019

Comments

This may be relatively simple for a major development company, but it would not be easy for a more casual developer with a few small sites. Just recognizing what needs to be done is non-trivial. I am just playing with a D7 to D8 migrate at this point and it looks like I should delay until 8.8.0 to avoid some of the issues. Or will some of these issues still arise on a clean install?

Drupal 8.8.0 was released today - and yes, now would probably be a better time to start your D7 to D8 migrate. Just in general, it might not be bad to wait for an 8.8.1, just in case there are any other little clean ups that go in, of course.

Thanks for this post, it helped me through the update process by addressing important issues. What I did to get the missing dependencies that prohibited the update to 8.8.0 was running "composer update" instead of your suggestion in "Update Drupal Core and Friends". It just worked, any dependency was updated an so was core. In my case an older version of typo3/phar-stream-wrapper was the problem.

Thanks for the documentation! This was very helpful for such a tricky update.

Thanks for this. I agree with Walt that Drupal updates are not easy for the casual developer, so we do appreciate the time taken to write and publish these articles. The 8.3 to 8.4 upgrade was a disaster for my project. With the help of this article, the official d.o 'special considerations' instructions and another article at https://www.palantir.net/blog/guide-drupal-8-8-update , I was able to move my drupal ecommerce project from 8.7 to 8.8 with relative ease. Still, upgrading drupal feels like a leap in the dark. There is no 'one size fits all' instruction. Backup everything, then be brave!

I have used a single cpmmand `composer update drupal/core "symfony/*" --with-dependencies` to update Drupal Core and Friends.

Pagination

Feb 11 2019
Feb 11

The time has come to close the book on our government Drupal distribution, aGov. We are no longer actively developing sites using aGov, and instead are focussing our efforts on the new GovCMS Drupal 8 distribution. Here’s a short history of aGov and how we got to this point.

Why we developed aGov

PreviousNext has a long history of developing sites for government at all levels. Back in 2012 there was a big shift in government moving towards accessibility, security and mobile support. There were many government sites built using legacy software that were too expensive and complex to update to conform to these requirements. At the same time government was embracing open source as a legitimate replacement.

We were seeing an increasing number of government agencies coming to us to help them meet these requirements, and we were implementing the same changes on each and every project.

aGov was developed as a response to this. The intention was to create a serious alternative to legacy applications, that could be spun up with minimal effort.

Drupal 7 was not very accessible out of the box, and aGov combined numerous contrib modules and a base theme that helped it meet WCAG 2 AA compliance.

aGov takes off

Thanks in part to the aGov distribution, we saw a huge adoption of Drupal in government. Agencies were able to create sites relatively easily, and host where ever they chose, be that in the cloud or on-premise. At its peak, aGov was in use on 600 sites and has been downloaded almost 200,000 times, consistently staying in the Top 20 Drupal Distributions on Drupal.org.

Distributions versus Starter Kits

Back in 2012, Drupal distributions were seen as the answer to the code reuse problem. In Drupal 7, configuration is all stored in the database, making it difficult to manage. The solution was the Features module, which had to accommodate myriad different configuration formats and try to wrangle them all into code that could be deployed. As aGov was typically modified and extended in 99% of cases, this configuration was difficult to maintain and supporting upgrade paths for each and every site was near impossible.

We improved the situation with a 7.x-2.x release and later with a 7.x-3.x release, however the inherent limitations of Drupal 7 were still there.

GovCMS

Around the 2nd half of 2014 we worked with the Australian Department of Finance to fork aGov to the GovCMS distribution. GovCMS is a hosting, procurement process and Drupal distribution to allow government agencies to build and deploy Drupal websites with minimum friction. The potential of Drupal and aGov was now being realised in a comprehensive Drupal SaaS platform.

The Department of Finance took ownership of the GovCMS codebase, however we still saw a need for more complex sites that would benefit from aGov, so decided to keep it alive. In aGov 7.x-3.x we incorporated UI Kit, the government design system, and in 2015 started active development on a Drupal 8 version.

Drupal 8

PreviousNext invested large amounts of time into the Drupal 8 release, which greatly increased our overall expertise and confidence in using it.

Drupal 8 solved a lot of the problems aGov was initially created for. First class accessibility and mobile support, as well as a comprehensive configuration management system that made moving configuration changes around a breeze.

As such, the Drupal 8 version of aGov was much smaller and simpler than it’s Drupal 7 equivalent. This also meant we would often use vanilla Drupal 8 instead of aGov when working on government sites as there was less need for an ‘opinionated’ (read inflexible) solution for different sites.

Maintenance Fixes Only

For the last two years, we have been supporting aGov, by updating core and contributed modules. However, as we no longer use it ourselves, there has been little impetus to extend or enhance the distribution. It became apparent that we could no longer continue to support it alone.

After six years since its original creation, we will be marking the project’s Maintenance Status as Seeking New Maintainer and its development status as No further development.  We will no longer be active in the issue queue or creating new releases.

If you are a user of aGov, you will be still able to upgrade Drupal core and contributed modules on a site by site basis as before. If you encounter upgrade issues, you should create tickets in the project where the issue occurs.

If anyone is interested in taking over maintainership, please contact us through the project’s issue queue.

Where to from here?

Late last year the GovCMS team successfully launched its Kubernetes-based hosting platform, and improved developer workflows. With 247 live websites and 48 site currently in development, the Drupal-based platform is stronger than ever, and continues to grow. 

PreviousNext has been developing GovCMS sites since day one, and we’re continuing to grow this part of our business. Dropping support for aGov is going to free up some of our time to put to better use on the future of government sites in Australia.

Photo of Kim Pepper

Posted by Kim Pepper
Technical Director

Dated 11 February 2019

Comments

A pat on the back for the pioneering work you guys did and for pushing the project forward throughout the years. The legacy will live on.

Huge thanks to you Kim, and the whole PreviousNext team for creating and maintaining aGov. It was a "critical success factor" for the adoption of Drupal in Government around Australia, and led to increased maturity in the community.

But thanks also for this very gracious "sunset" post. Yes, it's clearly time to let this one go.

Kudos.

Pagination

Add new comment

Nov 22 2018
Nov 22

Update: Re-published for DrupalSouth 2018 edition

The PreviousNext team are sponsoring and helping to run the sprint day on Wednesday, December 5th 2018, and there are a few things you can do now to hit the ground running on the day.

What's a Sprint Day about anyway?

Contribution Sprints are a great opportunity to get involved in contributing to Drupal. Contributions don't have to be just about code. Issue triage, documentation, and manual testing are examples of non-code contributions.

If you are new to contributing, you can take a look at the New Contributor tasks on the Drupal.org Contributor Tasks page.

While there will be experienced contributors there on the day to help, keep in mind, this is not a training session. :-)

Set Up a Development Environment

There is more than one way to shear a sheep, and there is also more than one way to set up a local development environment for working on Drupal.

We've create a Drupal project starter kit for sprint attendees which should speed up this process. Head over to https://github.com/previousnext/drupal-project and follow the README.

If you have any issues, feel free to post them in the Github issue queue https://github.com/previousnext/drupal-project/issues and we'll try and resolve them before the day.

Find Issues to Work On

If you want to see what might be an interesting issue to work on, head over to the Drupal.org Issue Queue and look for issues tagged with 'DrupalSouth 2018'. These are issues that others have tagged.

You can also tag an issue yourself to be added to the list.

Being face-to-face with fellow contributors is a great opportunity to have discussions and put forward ideas. Don't feel like you need to come away from the day having completed lines and lines of code.

We look forward to seeing you all there!

Photo of Kim Pepper

Posted by Kim Pepper
Technical Director

Dated 22 November 2018

Add new comment

Jul 03 2018
Jul 03

Back in the Drupal 6 days, I built the BOM Weather Drupal module to pull down weather data from the Australian Bureau of Meteorology (BOM) site, and display it to users.

We recently had a requirement for this in a new Drupal 8 site, so decided to take a more modern approach.

Not that kind of decoupled Drupal

We often hear the term Decoupled Drupal but I'm not talking about a Javascript front-end and Drupal Web Service API backend.

This kind of decoupling is removing the business logic away from Drupal concepts. Drupal then becomes a wrapper around the library to handle incoming web requests, configuration and display logic.

We can write the business logic as a standalone PHP package, with it's own domain models, and publish it to Packagist.org to be shared by both Drupal and non-Drupal projects.

The Bom Weather Library

We started by writing unit-testable code, that pulled in weather forecast data in an XML format, and produced a model in PHP classes that is much easier for consuming code to use. See the full BOM Weather code on GitHub 

For example:

$client = new BomClient($logger);
$forecast = $client->getForecast('IDN10031');

$issueTime = $forecast->getIssueTime();

$regions = $forecast->getRegions();
$metros = $forecast->getMetropolitanAreas();
$locations = $forecast->getLocations();

foreach ($locations as $location) {
  $aac = $location->getAac();
  $desc = $location->getDescription();

  /** @var \BomWeather\Forecast\ForecastPeriod[] $periods */
  $periods = $location->getForecastPeriods();

  // Usually 7 days of forecast data.
  foreach ($periods as $period) {
    $date = $period->getStartTime();
    $maxTemp = $period->getAirTempMaximum();
    $precis = $period->getPrecis();
  }
}

The library takes care of fetching the data, and the idiosyncrasies of a fairly crufty API (no offence intended!).

Unit Testing

We can have very high test coverage with our model. We can test the integration with mock data, and ensure a large percentage of the code is tested. As we are using PHPUnit tests, they are lightning fast, and are automated as part of a Pull Request workflow on CircleCI.

Any consuming Drupal code can focus on testing just the Drupal integration, and not need to worry about the library code.

Dependency Management

As this is a library, we need to be very careful not to introduce too many runtime dependencies. Also any versions of those dependencies need to be more flexible than what you would normally use for a project. If you make your dependency versions too high they can introduce incompatibilities when used a project level. Consumers will simply not be able to add your library via composer.

We took a strategy with the BOM Weather library of having high-low automated testing via CircleCI. This means you test using both: 

composer update --prefer-lowest

and

composer update

The first will install the lowest possible versions of your dependencies as specified in your composer.json. The second will install the highest possible versions. 

This ensures your version constraints are set correctly and your code should work with any versions in between.

Conclusion

At PreviousNext, we have been using the decoupled model approach on our projects for the last few years, and can certainly say it leads to more robust, clean and testable code. We have had projects migrate from Drupal 7 to Drupal 8 and as the library code does not need to change, the effort has been much less.

If you are heading to Drupal Camp Singapore, make sure to see Eric Goodwin's session on Moving your logic out of Drupal.

Photo of Kim Pepper

Posted by Kim Pepper
Technical Director

Dated 4 July 2018

Comments

Thanks for writing this! It's great to see this approach gain traction in Drupal 8. We're doing the same thing with the Drupal 8 version of the media_mpx module (library at https://github.com/Lullabot/mpx-php). As you say, test coverage of the critical functionality is so much simpler when you aren't dealing with the testing difficulties of Drupal 8 entities.

We've had good success bridging Drupal services back into non-Drupal libraries. For example, we use the cache PSR's to allow the PHP library to save data to Drupal's cache. You might be interested in https://github.com/Lullabot/drupal-symfony-lock which does the same thing for locks.

Thanks Andrew. I will check them out!

Pagination

Add new comment

Jan 22 2018
Jan 22

All PreviousNext Drupal 8 projects are now managed using Composer. This is a powerful tool, and allows our projects to define both public and private modules or libraries, and their dependencies, and bring them all together.

However, a if you require public or private modules which are hosted on GitHub you may run into the API Rate Limits. In order to overcome this, it is recommended to add a GitHub personal access token to your composer configuration.

In this blog post, I'll show how you can do this in a secure and manageable way.

It's common practice when you encounter a Drupal project to see the following snippet in a composer.json file:

"config": {
    "github-oauth": {
        "github.com": "XXXXXXXXXXXXXXXXXXXXXX"
    }
},

What this means is, everyone is sharing a single account's personal access token. While this may be convenient, it's also a major security risk should the token accidentally be made public, or a team member leaves the organisation, and still has read/write access to your repositories.

A better approach, is to have each team member have their own personal access token configure locally. This ensures that individuals can only access repositories they have read permissions for, and once they leave your organisation they can no longer access any private dependencies.

Step 1: Create a personal access token

Go to https://github.com/settings/tokens and generate a new token.

Generate GitHub Token

You will need to specify all repo scopes.

Select GitHub Scopes

Finally, hit Generate Token to create the token.

GitHub token

Copy this, as well need it in the next step.

Step 2: Configure Composer to use your personal access token

Run the following from the command line:

composer config -g github-oauth.github.com XXXXXXXXXXXXXXXXXXXXXXX

You're all set! From now on, composer will use your own individual personal access token which is stored in $HOME/.composer/auth.json

What about Automated Testing Environments?

Fortunately, composer also accepts an environment variable COMPOSER_AUTH with a JSON-formatted string as an argument. For example:

COMPOSER_AUTH='{"github-oauth": {"github.com": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}}'

You can simply set this environment variable in your CI Environment (e.g. CircleCI, TravisCI, Jenkins) and have a personal access token specific to the CI environment.

Summary

By using Personal Access Tokens, you can now safely remove any tokens from the project's composer.json file, removing the risk this gets exposed. You can also know that by removing access for any ex-team members, they are no longer able to access your organisations repos using a token. Finally, in the event of a token being compromised, you have reduced the attack surface, and can more easily identify which user's token was used.

Photo of Kim Pepper

Posted by Kim Pepper
Technical Director

Dated 22 January 2018

Add new comment

Nov 28 2017
Nov 28

At DrupalSouth 2017, I presented a session on the new Workflows module, which just went stable in Drupal 8.4.0. Workflows was split out from content moderation as a separate module, and can be used independently to create custom workflows. In this presentation, I gave a demonstration of how to create a basic workflow for an issue tracker.

Since 2011 we have had access to a content moderation tool in Drupal 7 in the form of Workbench Moderation. This module introduced the concept of Draft ➡ Review ➡ Published workflows, with different user roles having specific permissions to move from one state to the next.

Unfortunately, the underlying Drupal core revision API was not designed to deal with this, and there were some pretty crazy workarounds.

Content moderation has long been a key feature request for Drupal, and so effort was made to port Workbench Moderation across to Drupal 8. 

Content Moderation drove a lot of cleanup in Drupal core APIs, including proper support for forward revisions, and adding revision support to other content entities besides Content Types, such as Custom Blocks. More are on the way.

In Drupal 8.3, the Workflows module was split out of Content Moderation. Why you may ask? Well, because the Workflows module provides the state machine engine that Content Moderation relies on.

What is a State Machine?

A state machine defines a set of states and rules on how you can transition between those states.

Door state machine A door state machine

In our simple example of a door, it can only be opened, closed or locked. However, you can't go directly from locked to open, you need to unlock it first.

Content Moderation Workflow Configuration

Content Moderation provides a set of Workflow states and transitions by default.

Content Moderation States Content Moderation States Content Moderation Transitions Content Moderation Transitions

If we were to put this together in a state machine diagram, it would look like the following:

Content Moderation State Machine Content Moderation State Machine

From the above diagram, it becomes clear what the allowed transitions are between states.

So now Workflows has been configured with our Content Moderation states and transitions, what is left for Content Moderation to do?

What Does Content Moderation Do?

It turns out quite a lot. Remember, that Workflows only provides the state machine. It in no way prescribes how you should manage the current state of a particular entity.

Content Moderation provides:

  • Default Workflows configuration
  • A fairly complex WorkflowType plugin which works with the Revision API.
  • Storage for individual states on content entities
  • Configuration of which entity bundles (Content types, etc.) should have Content Moderation
  • A number of admin forms for configuring the workflows and how they apply
  • Permissions

Building an Issue Tracker

We want to build a very simple issue tracker for our example. The state machine diagram is the following:

Issue Tracker State Machine Issue Tracker State Machine

That's the simple bits out of the way. Now, in order to build an issue tracker, we will need to replicate the rest what Content Moderation does!

Fortunately there is a module that can do most of the heavy lifting for us.

Workflows Field

“This module provides a field which allows you to store states on any content entity and ensure the states change according to transitions defined by the core workflows module.” 

Perfect! Let's download and install it.

Next we want to add a new Workflow. We can assign it a label of Issue Status and you'll see that we have a new Workflows Field option in the Workflow Type dropdown.

Add new workflow Add new workflow

We can then configure the desire Workflows states and transitions.

Issue States Issue States Issue Transitions Issue Transitions

Thats the our Workflows configured. Now we need to create a new Issue content type to attach our workflow to. It's assumed you know how to create a content type already. If not, check out the User Guide.

Next, we need to add our Workflows Field to our Issue content type. Follow the usual steps to add a field, and in the drop down choose Workflows as the field type, and our previously created Issue Status workflow.

Add workflows field Add workflows field

Test it out!

Now we can test our our workflow by creating a new Issue from the Content page. If everything was configured correctly, we should see a new field on the edit form for Status.

Issue status form Issue status form

Given the transitions we defined in our workflow, we should only be allowed to see certain values in the drop-down, depending on the current state.

Testing workflow constraints Testing workflow constraints

What next?

That's it for setting up and configuring a custom workflow using Workflows Field. Some next steps would be:

  • Add permissions for certain users (there's an issue for that #2904573 )
  • Add email notifications

How would you use the new Workflows API?

Let me know in the comments!

Photo of Kim Pepper

Posted by Kim Pepper
Technical Director

Dated 29 November 2017

Add new comment

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web