Oct 21 2019
Oct 21

The 2019 Drupal South sprint is shaping up to be the biggest contribution event in the Australia-Pacific region since Drupalcon Sydney 2013.

This year, core-contributors with over 3000 commit credits between them will be in attendance, including 3 members of the core committers team, 3 members of the Drupal security team, 7 core module/subsystem maintainers as well as maintainers of major contrib modules and command-line tools.

With Drupal 9 just around the corner, this will be a great chance to help the community get popular modules ready for Drupal 9, meet some great people and help shape the future of Drupal.

The PreviousNext team are sponsoring and helping to run the sprint day on Wednesday, November 27th 2019, 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.

If you don't already have a local development environment setup, we recommend using Docker Compose for local development - follow the instructions for installing Docker Compose on OSX, Windows and Linux.

Once you've setup Docker compose, you need to setup a folder containing your docker-compose.yml and a clone of Drupal core. The instructions for that vary depending on your operating system, we have instructions below for OSX, Windows and Linux, although please note the Windows version is untested.

Mac OSX

mkdir -p ~/dev/drupal
cd ~/dev/drupal
wget https://gist.githubusercontent.com/larowlan/9ba2c569fd52e8ac12aee962cc9319c9/raw/ef35764c2bf60b07996fdc57c747c3c99a855b80/docker-compose.yml
git clone --branch 8.9.x https://git.drupalcode.org/project/drupal.git app
docker-compose up -d
docker-compose run -w /data/app app composer install

Windows

git clone --branch 8.9.x https://git.drupalcode.org/project/drupal.git app
docker-compose up -d
docker-compose run -w /data/app app composer install

Linux

mkdir -p ~/dev/drupal # or wherever you want to put the folder
cd ~/dev/drupal
wget https://gist.githubusercontent.com/larowlan/63a0f6efacee71b483af3a2184178dd0/raw/248dff13557efa533c0ca297d39c87cd3eb348fe/docker-compose.ymlgit clone --branch 8.9.x https://git.drupalcode.org/project/drupal.git app
docker-compose up -d
docker-compose exec app /bin/bash -c "cd /data/app && composer install"

If you have any issues, join us on Drupal slack in the #australia-nz channel beforehand and we'll be happy to answer any questions you might have.

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 2019'. 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 Lee Rowlands

Posted by Lee Rowlands
Senior Drupal Developer

Dated 21 October 2019

Add new comment

May 09 2018
May 09

Several times in the past I've been caught out by Drupal's cron handler silently catching exceptions during tests.

Your test fails, and there is no clue as to why.

Read on to find out how to shine some light on this, by making your kernel tests fail on any exception during cron.

If you're running cron during a kernel test and expecting something to happen, but it doesn't - it can be hard to debug why.

Ordinarily an uncaught exception during a test will cause PHPUnit to fail, and you can pinpoint the issue.

However, if you're running cron in the test this may not be the case.

This is because, by default Drupal's cron handler catches all exceptions and silently logs them. This is colloquially known as Pokemon exception handling.

The act of logging an exception is not enough to fail a test.

So your test skips the exception and carries on, failing in other ways unexpectedly.

This is exacerbated by the fact that PHP Unit throws an exception for warnings. So the slightest issue in your code will cause it to halt execution. In an ordinary scenario, this exception causes the test to fail. But the pokemon catch block in the Cron class prevents that, and your test continues in a weird state.

This is the code in question in the cron handler

<?php
try {
  $queue_worker->processItem($item->data);
  $queue->deleteItem($item);
}
// ... 
catch (\Exception $e) {
  // In case of any other kind of exception, log it and leave the item
  // in the queue to be processed again later.
  watchdog_exception('cron', $e);
}

So how do you make this fail your test? In the end, it's quite simple.

Firstly, you make your test a logger and use the handy trait to do the bulk of the work.

You only need to implement the log method, as the trait takes care of handling all other methods.

In this case, watchdog_exception logs exceptions as RfcLogLevel::ERROR. The log levels are integers, from most severe to least severe. So in this implementation we tell PHP Unit to fail the test with any messages logged where the severity is ERROR or worse.

use \Drupal\KernelTests\KernelTestBase;
use Psr\Log\LoggerInterface;
use Drupal\Core\Logger\RfcLoggerTrait;
use Drupal\Core\Logger\RfcLogLevel;

class MyTest extends KernelTestBase implements LoggerInterface {
  use RfcLoggerTrait;

  /**
   * {@inheritdoc}
   */
  public function log($level, $message, array $context = []) {
    if ($level <= RfcLogLevel::ERROR) {
      $this->fail(strtr($message, $context));
    }
  }
}

Then in your setUp method, you register your test as a logger.

$this->container->get('logger.factory')->addLogger($this);

And that's it - now any errors that are logged will cause the test to fail.

If you think we should do this by default, please comment on this core issue.

Photo of Lee Rowlands

Posted by Lee Rowlands
Senior Drupal Developer

Dated 9 May 2018

Add new comment

Feb 07 2018
Feb 07

Great to see this project a really good page builder is badly needed for Drupal - looks like a very good start, well done Lee.

Not sure if you are familiar with the layout builder and visual composer build by NikaDevs (a theme company) but you could do a lot worse then having a look at their approach, it's a very good page builder - which they ave on all their themes.

https://themeforest.net/user/nikadevs

Thanks,

Shane

Jan 22 2018
Jan 22

In November 2017 I presented at Drupal South on using Dialogflow to power conversational interfaces with Drupal.

The video and slides are below, the demo in which I talk to Drupal starts in the first minute.

by Lee Rowlands / 23 January 2018 Open slides in new window

Tagged

Conversational UI, Drupal 8, Chatbots, DrupalSouth
Jan 18 2018
Jan 18

After reading a blog post by Matthias Noback on keeping an eye on code churn, I was motivated to run the churn php library over some modules in core to gauge the level of churn.

Is this something you might like to do on your modules? Read on for more information.

What is churn

As Matthias details in his blog post - churn is a measure of the number of times a piece of code has been changed over time. The red flags start to crop up when you have high complexity and high churn.

Enter churn-php

Churn php is a library that analyses PHP code that has its history in git to identify high churn/complexity scores.

You can either install it with composer require bmitch/churn-php --dev or run it using docker docker run --rm -ti -v $PWD:/app dockerizedphp/churn run /path/to/code

Some results from core

So I ran it for some modules I look after in core, as well as the Drupal\Core\Entity namespace.

Block Content

File Times Changed Complexity Score core/modules/block_content/src/Entity/BlockContent.php 41 6 1 core/modules/block_content/src/BlockContentForm.php 32 6 0.78 core/modules/block_content/src/Plugin/Block/BlockContentBlock.php 20 6 0.488 core/modules/block_content/src/Tests/BlockContentTestBase.php 16 6 0.39 core/modules/block_content/src/BlockContentTypeForm.php 18 4 0.347 core/modules/block_content/src/Controller/BlockContentController.php 8 6 0.195

Comment

File Times Changed Complexity Score core/modules/comment/src/CommentForm.php 60 45 1 core/modules/comment/src/Entity/Comment.php 55 25 0.548 core/modules/comment/src/Tests/CommentTestBase.php 33 29 0.426 core/modules/comment/src/Controller/CommentController.php 32 20 0.274 core/modules/comment/src/CommentViewBuilder.php 37 16 0.25 core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php 32 18 0.24 core/modules/comment/src/Form/CommentAdminOverview.php 29 17 0.191 core/modules/comment/src/CommentAccessControlHandler.php 17 28 0.19 core/modules/comment/src/CommentLinkBuilder.php 15 29 0.17 core/modules/comment/src/CommentManager.php 29 15 0.157

Drupal\Core\Entity

File Times Changed Complexity Score core/lib/Drupal/Core/Entity/ContentEntityBase.php 115 173 0.808 core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php 61 196 0.465 core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php 56 203 0.427 core/lib/Drupal/Core/Entity/Entity.php 131 43 0.212 core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php 41 105 0.16

Conclusion

So, what to do with these results?

Well I think if you're looking to simplify your code-base and identify places that would warrant refactoring, those with a high 'churn' score would be a good place to start.

What do you think? Let us know in the comments.

Photo of Lee Rowlands

Posted by Lee Rowlands
Senior Drupal Developer

Dated 19 January 2018

Comments

Pagination

Add new comment

Nov 21 2017
Nov 21

Need a way to mix fields from referenced entities with regular fields from managed display?

Then the Display Suite Chained Fields module might be for you.

So how do you go about using the module?

Step 1: Enable a display suite layout for the view mode

To use the chained fields functionality, you must enable a display suite layout for the view mode. Select a layout other than none and hit Save.

Screenshot showing how to go about enabling a layout Enabling a layout

Step 2: Enable the entity reference fields you wish to chain

To keep the manage display list from being cluttered, you must manually enable the entity reference fields you wish to show chained fields from. For example, to show the author's picture, you might enable the 'Authored by' entity reference field, which points to the author. After you've enabled the required fields, press Save.

Screenshot showing enabling the fields for chaining Enabling fields for chaining

Step 3: Configure the chained fields as required

Finally, just configure the chained fields as normal.

Screenshot showing chained fields available for configuration Configuring chained fields

That's it - let me know your thoughts in the comments or the the issue queue.

Photo of Lee Rowlands

Posted by Lee Rowlands
Senior Drupal Developer

Dated 22 November 2017

Add new comment

Nov 02 2017
Nov 02

From time to time you may find you need to extend another module's plugins to add new functionality.

You may also find you need to alter the signature of the constructor in order to inject additional dependencies.

However plugin constructors are considered internal in Drupal's BC policy.

So how do you safely do this without introducing the risk of breakage if things change.

In this article we'll show you a quick trick learned from Search API module to avoid this issue.

So let's consider a plugin constructor that has some arguments.

Here's the constructor and factory method for Migrate's SQL map plugin

/**
   * Constructs an SQL object.
   *
   * Sets up the tables and builds the maps,
   *
   * @param array $configuration
   *   The configuration.
   * @param string $plugin_id
   *   The plugin ID for the migration process to do.
   * @param mixed $plugin_definition
   *   The configuration for the plugin.
   * @param \Drupal\migrate\Plugin\MigrationInterface $migration
   *   The migration to do.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EventDispatcherInterface $event_dispatcher) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->migration = $migration;
    $this->eventDispatcher = $event_dispatcher;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $migration,
      $container->get('event_dispatcher')
    );
  }

As you can see, there are two additional dependencies injected beyond the standard plugin constructor arguments - the event dispatcher and the migration.

Now if you subclass this and extend the constructor and factory to inject additional arguments, should the base plugin change its constructor, you're going to be in trouble.

Instead, you can use this approach that Search API takes - leave the constructor as is (don't override it) and use setter injection for the new dependencies.

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
    $instance = parent::create(
      $container,
      $configuration,
      $plugin_id,
      $plugin_definition,
      $migration
    );
    $instance->setFooMeddler($container->get('foo.meddler');
    return $instance;
  }
    
    /**
    * Sets foo meddler.
    */
    public function setFooMeddler(FooMeddlerInterface $fooMeddler) {
      $this->fooMeddler = $fooMeddler;
    }

Because the signature of the parent create method is enforced by the public API of \Drupal\Core\Plugin\ContainerFactoryPluginInterface you're guaranteed that it won't change.

Thanks to Thomas Seidl for this pattern

Photo of Lee Rowlands

Posted by Lee Rowlands
Senior Drupal Developer

Dated 3 November 2017

Comments

Nice!! Thank you for sharing it!

Pagination

Add new comment

Oct 26 2017
Oct 26

Services like dialogflow (formerly api.ai) do a much better job of natural language parsing (NLP) if they're aware of your entity names in advance.

For example, it can recognize that show me the weather in Bundaberg is a request for weather in Bundaberg, if you've told it ahead of time that Bundaberg is a valid value for the City entity.

Having the entity values automatically update in your service of choice when they're created and changed in Drupal makes this much more efficient.

This article will show you how to achieve that.

This is where the chatbot_api_entities sub-module comes in.

When you enable this module you can browse to Admin -> Config -> Web Services -> Entity Collections to create a collection.

The UI looks something like this:

Screenshot from Drupal showing entity collections in Chatbot API Entities module Adding an entity collection to send to dialogflow in Drupal

Each collection comprises an entity-type and bundle as well as a push handler and a query handler.

By default Chatbot API Entities comes with a query handler for each entity-type and a specific one for Users to exclude blocked users.

The api_ai_webhook module comes with a push handler for pushing entities to your dialogflow/api.ai account.

By default, these plugins query based on available entities and the push handler pushes the entity labels.

Writing your own query handler

If for example, you don't want to extract entities from entity labels, e.g. you might wish to collect unique values from a particular field. In this case you can write your own query handler.

Here's an example that will query speaker names from a session content type. The collection handed to the push handler will contain all published sessions.

namespace Drupal\your_module\Plugin\ChatbotApiEntities\QueryHandler;

use Drupal\chatbot_api_entities\Entity\EntityCollectionInterface;
use Drupal\chatbot_api_entities\Plugin\QueryHandlerBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;


/**
 * Defines a query handler that just uses entity query to limit as appropriate.
 *
 * @QueryHandler(
 *   id = "speakers",
 *   label = @Translation("Query speakers from sessions"),
 * )
 */
class SpeakerQuery extends QueryHandlerBase {

  /**
   * {@inheritdoc}
   */
  public function query(EntityTypeManagerInterface $entityTypeManager, array $existing = [], EntityCollectionInterface $collection) {
    $storage = $entityTypeManager->getStorage('node');
    return $storage->loadMultiple($storage->getQuery()
      ->condition('type', 'session')
      ->exists('field_speaker_name')
      ->condition('status', 1)
      ->execute());
  }

  /**
   * {@inheritdoc}
   */
  public function applies($entity_type_id) {
    return $entity_type_id === 'node';
  }

}

Writing your own push handler

Whilst we've written our own query handler to load entities that we wish to extract values from, we need to write our own push handler to handle sending anything other than the label.

Here's an example push handler that will push field values as entities to Api.ai/dialogflow

<?php

namespace Drupal\your_module\Plugin\ChatbotApiEntities\PushHandler;

use Drupal\api_ai_webhook\Plugin\ChatbotApiEntities\PushHandler\ApiAiPushHandler;
use Drupal\chatbot_api_entities\Entity\EntityCollection;
use Drupal\Core\Entity\EntityInterface;

/**
 * Defines a handler for pushing entities to api.ai.
 *
 * @PushHandler(
 *   id = "api_ai_webhook_speakers",
 *   label = @Translation("API AI entities endpoint (speakers)")
 * )
 */
class SpeakerPush extends ApiAiPushHandler {

  /**
   * {@inheritdoc}
   */
  protected function formatEntries(array $entities, EntityCollection $entityCollection) {
    // Format for API.ai/dialogflow.
    return array_map(function ($item) {
      return [
        'value' => $item,
        'synonyms' => [],
      ];
    },
    // Key by name to remove duplicates.
    array_reduce($entities, function (array $carry, EntityInterface $entity) {
      $value = $entity->field_speaker_name->value;
      $carry[$value] = $value;
      return $carry;
    }, []));
  }

}

Learn more

If you're interested in learning more about Chatbots and conversational UI with Drupal, I'm presenting a session on these topics at Drupal South 2017, the Southern Hemisphere's biggest Drupal Camp. October 31st is the deadline for getting your tickets at standard prices, so if you plan to attend, be sure to get yours this week to avoid the price hike.

I hope to see you there.

Photo of Lee Rowlands

Posted by Lee Rowlands
Senior Drupal Developer

Dated 27 October 2017

Add new comment

Oct 19 2017
Oct 19

In a recent project we were outputting CSV and wanted to test that the file contents were valid.

Read on for a quick tip on how to achieve this with Drupal 8's BrowserTestBase

Basically, the easiest way to validate and parse CSV in PHP is with the built in fgetcsv function.

So how do you go about using that inside a functional test - in that instance we're not dealing with a file so its not your ordinary approach for fgetcsv.

The answer is to create a stream wrapper in memory, and use fgetcsv on that.

The code looks something like this:

    $response = $this->getSession()
      ->getDriver()
      ->getContent();
    // Put contents into a memory stream and use fgetcsv to parse.
    $stream = fopen('php://memory', 'r+');
    fwrite($stream, $response);
    rewind($stream);
    $records = [];
    // Get the header row.
    $header = fgetcsv($stream);
    while ($row = fgetcsv($stream)) {
      $records[] = $row;
    }
    fclose($stream);

There you have it, you now have the header in $header and the rows in $rows and can do any manner of asserts that you need to validate the CSV generation works as expected.

Photo of Lee Rowlands

Posted by Lee Rowlands
Senior Drupal Developer

Dated 20 October 2017

Add new comment

Sep 24 2017
Sep 24

Drupal 8.4.0 comes out in October, and at that time 8.3.x will be end-of-life (EOL).

There are two major vendor updates in 8.4.0 so the time to test your contrib and client projects is now.

In this post we talk about the coming changes and how to test your client and contrib projects.

The two major vendor updates in Drupal 8.4.0 are as follows:

You can start testing now by updating to Drupal 8.4.0-rc2.

Symfony 3.x

If your project interacts Symfony directly at the lower level (rather than using Drupal core APIs that in turn use Symfony), you should be sure to review your code to make sure you're not using any of the APIs impacted by the BC breaks between 2.x and 3.x. Hopefully, your automated testing will reveal these regressions for you (you have automated testing right?). See the Symfony change list for the details of BC breaks.

One thing to note with the Symfony update is that whilst core dependencies were updated, your project may rely on other third-party PHP libraries that have dependencies on Symfony 2.x components. This may cause you issues with your update - and require you to update other dependencies at the same time - including drush - so testing sooner rather than later is recommended. If you find you're having issues with composer dependencies, we have another blog post dedicated to debugging them.

jQuery 3.x

While it's most likely that you'll have automated tests to catch any issues with the Symfony upgrade, it's less likely that you'll have test coverage for the jQuery update, as JavaScript test coverage is typically low in Drupal projects, particularly in contrib modules.

Of note in the jQuery update are several BC breaks - listed here http://blog.jquery.com/2016/06/09/jquery-3-0-final-released/ and http://jquery.com/upgrade-guide/3.0/. This may have a major impact on contrib projects that are heavy on JavaScript - and your client project code if you have a large amount of custom JavaScript, both in modules and your theme.

Of particular interest

  • .load removed
  • .unload removed
  • .error removed
  • .bind deprecated (use .on)
  • .delegate deprecated
  • .on('ready', function() {}) removed
  • jQuery('#') and .find('#') throw invalid syntax errors
  • .andSelf() removed (use .addBack())

A recommended approach to auditing and tackling this is to add the jQuery migrate plugin to your project, and begin testing whilst watching the JavaScript console to detect deprecation notices thrown from the plugin.

A word on testing

Finally, if you are reading this and thinking, I really need to add some test coverage to my project, one of our team Sam Becker is presenting on all things testing at Drupalcon Vienna this week. If you can't wait that long, check out his session from the last Drupal South.

Photo of Lee Rowlands

Posted by Lee Rowlands
Senior Drupal Developer

Dated 25 September 2017

Add new comment

Sep 18 2017
Sep 18

Conversational UIs are the next digital frontier.

And as always, Drupal is right there on the frontier, helping you leverage your existing content and data to power more than just web-pages.

Want to see it action - click 'Start chatting' and chat to our Drupal site.

Start chatting

So what's going on here?

We're using the Chatbot API module in conjunction with the API AI webook module to respond to intents. We're using API.ai for the natural language parsing and machine learning. And we're using the new Chatbot API entities sub module to push our Drupal entities to API.ai so it is able to identify Drupal entities in its language parsing.

A handful of custom Chatbot API intent plugin to wire up the webhook responses and that's it - as we create content, users and terms on our site - our chatbot automatically knows how to surface them. As we monitor the converstions in the API.ai training area, we can expand on our synonyms and suggestions to increase our matching rates.

So let's consider our team member Eric Goodwin. If I ask the chatbot about Eric, at first it doesn't recognise my question.

Screenshot of chatbot conversation showing Eric not recognised Eric isn't recognized as an entity

So I edit Eric's user account and add some synonyms

Screenshot of editing Eric's account to add synonyms Adding synonyms to Eric's account

And then after running cron - I can see these show up in API.ai

Screenshot from API.ai console showing Eric's synonyms Synonyms now available in API.ai

So I then ask the bot again 'Who is eric?'

Screenshot showing the default response Screenshot showing the default response

But again, nothing shows up. Now I recognise the response 'Sorry, can you say that again' as what our JavaScript shows if the response is empty. But just to be sure - I check the API.ai console to see that it parsed Eric as a staff member.

Screenshot showing Eric is resolved and intent is matched Intent is matched as Bio and Eric is identified as staff member

So I can see that the Bio Intent was matched and that Eric was correctly identifed as the Staff entity. So why was the response empty? Because I need to complete Eric's bio in his user account. So let's add some text (apologies Eric you can refine this later).

Screenshot of editing Eric's account to add a biography Adding a biography

Now I ask the bot again (note I've not reloaded or anything, this is all in real time).

Screenshot showing Eric's bio in the bot response A working response!

And just like that, the bot can answer questions about Eric.

What's next?

Well API.ai provides integrations with Google Assistant and Facebook messenger, so we plan to roll out those too. In our early testing we can use this to power an Actions on Google app with the flick of a switch in API.ai. Our next step is to expand on the intents to provide rich content tailored to those platforms instead of just plain-text that is required for chatbot and voice responses.

Credits

Thanks go to @gambry for the Chatbot API module and for being open to the feature addition to allow Drupal to push entities to the remote services.

And credit to the amazing Rikki Bochow for building the JavaScript and front-end components to incorporate this into our site so seamlessly.

Further Reading

Photo of lee.rowlands

Posted by lee.rowlands
Senior Drupal Developer

Dated 18 September 2017

Add new comment

Aug 10 2017
Aug 10

This week whilst trying to update one of our projects to the latest version of Drupal 8 core, we had some issues.

We use Composer to manage our dependencies, modules etc, and on this particular occasion, things weren't straightforward.

In order to solve it, we had to use some of the lesser known features of Composer, so decided to share.

The problem

So updating Drupal core with composer is normally pretty simple. And on this occasion, we had no reason to suspect it would be anything different.

Normally we'd just run

composer update "drupal/core" --with-dependencies

But this time, nothing happened.

So we checked that there was a newer version available

composer show -a "drupal/core"

And sure enough, we can see 8.3.6 in the available versions.

Time to dig deeper.

The why

Luckily, composer will tell you why it won't install something.

composer why-not "drupal/core:8.3.6"

Which yielded

drupal/core  8.3.6  conflicts  drush/drush (<8.1.10)

Aha, so drush is the issue.

So maybe we just update both

composer update "drupal/core" "drush/drush"

Nope.

Digging deeper

So after trying a few different combinations of version constraints etc, we decided to remove drush, update and then add it back.

composer remove --dev "drush/drush"

Which worked.

composer update "drupal/core" --with-dependencies

Ok, nice, we now have Drupal 8.3.6

composer require --dev "drush/drush"

Nope.

Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Installation request for drush/drush 8.1.12 -> satisfiable by drush/drush[8.1.12].
    - Conclusion: remove phpdocumentor/reflection-docblock 3.2.2
    - Conclusion: don't install phpdocumentor/reflection-docblock 3.2.2
    - drush/drush 8.1.12 requires phpdocumentor/reflection-docblock ^2.0 -> satisfiable by phpdocumentor/reflection-docblock[2.0.0, 2.0.0a1, 2.0.0a2, 2.0.0a3, 2.0.1, 2.0.2, 2.0.3, 2.0.4, 2.0.5].
    - Can only install one of: phpdocumentor/reflection-docblock[2.0.0, 3.2.2].
    - Can only install one of: phpdocumentor/reflection-docblock[2.0.0a1, 3.2.2].
    - Can only install one of: phpdocumentor/reflection-docblock[2.0.0a2, 3.2.2].
    - Can only install one of: phpdocumentor/reflection-docblock[2.0.0a3, 3.2.2].
    - Can only install one of: phpdocumentor/reflection-docblock[2.0.1, 3.2.2].
    - Can only install one of: phpdocumentor/reflection-docblock[2.0.2, 3.2.2].
    - Can only install one of: phpdocumentor/reflection-docblock[2.0.3, 3.2.2].
    - Can only install one of: phpdocumentor/reflection-docblock[2.0.4, 3.2.2].
    - Can only install one of: phpdocumentor/reflection-docblock[2.0.5, 3.2.2].
    - Installation request for phpdocumentor/reflection-docblock (locked at 3.2.2) -> satisfiable by phpdocumentor/reflection-docblock[3.2.2].


Installation failed, reverting ./composer.json to its original content.

Hm, so we have a version of phpdocumentor/reflection-docblock in our lock file that is too high for drush.

composer why "phpdocumentor/reflection-docblock"

Yields

phpspec/prophecy v1.6.1 requires phpdocumentor/reflection-docblock (^2.0|^3.0.2)

Aha, so prophecy - but it allows either version .. but our lock file has pinned it to the 3.x branch

So lets force composer to downgrade that

composer require --dev "phpdocumentor/reflection-docblock:^2.0"

Now lets see if we can add drush back

composer require --dev "drush/drush"

Success!

Now all that remains is to clean up, because we don't really want to depend on phpdocumentor/reflection-docblock

composer remove --dev "phpdocumentor/reflection-docblock"

Done - quick - commit that lock file while you're winning!

Summary

So while it might be easy to curse Composer for not letting you upgrade, its actually doing exactly what you told it to do.

Your lock file has a pinned version, it is honoring that.

And in order to resolve it, Composer provides all the tools you need in the form of the why and the why-not commands.

Photo of lee.rowlands

Posted by lee.rowlands
Senior Drupal Developer

Dated 11 August 2017

Comments

Wow, never knew about the "why" and "why-not" commands. Very good to know for future debugging! Thanks

As long as your package version constraints in your composer.json file are appropriate - e.g. drupal/core could be "~8.3.3", and drush/drush at "~8.1.10" then updating both at the same time, like this, should have worked:

composer update drupal/core drush/drush --with-dependencies

You did mention updating them both in a single command, but not with the 'with-dependencies' bit, perhaps that's all that was needed? (As it's a common dependency that needed changing.)
Or maybe there's something else in your repo / configuration / composer files that was causing this?

Sometimes the dependency chains are a lot longer. If you do not want to use "composer why" three times to figure out that sugar/white is required by pancake/blueberry, which is required by sunday/brunch which is required by your project, then you can use the --recursive (-r) flag:

composer why -r sugar/white

Also, I wonder how different the results would have been if you had let composer deal with it:

rm composer.lock && composer install

Thanks for a great explanation of `composer why` and `composer why-not`. This is a good guide on how to diagnose and get around problems with updates and the composer.lock file.

However, running `composer update "drupal/core" --with-dependencies` is in general not something that you want to do on your Drupal site. The reason is that the Drupal core developers only update Drupal's dependencies on minor releases -- e.g. from 8.3.x to 8.4.0. If you use `composer update` to get a newer patch release of Drupal core, then you will end up with a different set of dependencies than Drupal is currently using. While this should, in general work, your configuration will not be exactly the same as the one that the core committers are testing, so you become more likely to encounter new dependency bugs before the rest of the community. If your goal is to update a site that is in production, this is generally not what you want.

To work around this problem, try using https://github.com/webflo/drupal-core-strict. This project contains tagged releases that strongly constrain the versions of all of Drupal's dependencies to match what is being tested in Drupal core. If you require this project in your Composer-managed Drupal site, then you will be able to use `composer update` to update your non-Drupal-core-releated dependencies without bringing in more updates than intended.

For additional background on phpdocumentor version constraints and Drush, see https://github.com/drush-ops/drush/pull/2877

We had an issue where a Guzzle security update was needed, but we didn't want to wait for a core release in order to fix it. If core sets its version ranges correctly, updating patch versions should not be a problem.

Pagination

Add new comment

Aug 01 2017
Aug 01

Just a quick post to share a simple way to assert some markup is ordered correctly using BrowserTestBase

Background

On a recent client project we received a bug report that the output of some particular markup wasn't sorted correctly.

Because we like to use test-driven-development, we obviously started with a test so we could be sure that the bug was fixed but also never appeared again.

Testing element order with BrowserTestBase

So as you'd be aware, BrowserTestBase in Drupal 8 is powered by Mink, which is pretty powerful.

In our test we created to pieces of fake content, title 'AAA Test' and 'BBB Test'.

If sorting was working, we'd expect to see 'AAA Test' before 'BBB Test'.

Both were output as links.

With BrowserTestBase and Mink, it was pretty simple.

// Grab all links with class .listing__title.
$links = $this->getSession()->getPage()->findAll('css', 'a.listing__title');
// Transform into an array keyed by link anchor.
$link_titles = array_flip(array_map(function ($link) {
  return $link->getText();
}, $links));
$this->assertTrue($link_titles['AAA Test'] < $link_titles['BBB Test']);

Not bad eh?

Do you have any handy Mink snippets? Share them in the comments.

Testing BrowserTestBase Drupal 8 Mink
Oct 14 2015
Oct 14

Photo
Drupal 8 comes with plenty of new features: the high visibility ones, like CKEditor or Views in core, and those less obvious but equally pivotal to Drupal 8’s strength and flexibility, like the Entity Validation API.

Never heard of it? You're not alone – much of the fanfare around Drupal 8 is devoted to the shiny parts. But under the hood, rock solid developer APIs like the Entity Validation API are what will make Drupal 8 a pleasure to work with for client projects and contributed modules alike.

So what is this Entity Validation API and why should you care?

For Those Who Came in Late

In Drupal versions up to and including Drupal 7, any validation was done in the Form API. Consider the Comment entity, provided by the Comment module. There is a lot of validation relating to comments, such as:

  • If the comment is being updated, we confirm the timestamp is a valid date.
  • If the comment is being updated and the username is changed, we confirm the username is valid.
  • If the comment is anonymous, we validate that the name used for the comment doesn't match an existing username.
  • If the comment is anonymous, we confirm that the e-mail address entered is valid.
  • If the comment is anonymous, we confirm that the homepage is a valid URL.

In Drupal 7 and earlier all of this happens in the comment form validation logic, in comment_form_validate().

The issue here is that this validation is tied to a form submission. If you're saving a comment via some other method, then you have to duplicate all this logic to ensure you don’t end up with invalid comment entities. Common alternate methods include:

  1. using Rules;
  2. using a Restful, Services, or Rest WS endpoint;
  3. programmatically saving via custom code;
  4. using a custom comment form.

The same scenario is repeated for Nodes, Users, Taxonomy terms, and custom Blocks (which aren’t entities per se in Drupal 7, but the story is the same).

It’s Like an Onion, or Maybe a Layer Cake

But before we can talk about Drupal 8's Entity Validation API, we need to go over some background on the Entity API itself.

In Drupal 7, entities are \StdClassobjects; accessing field values depends on the entity and the field. There is no real unification. For example, $node->title is a string, while $node->field_tags is an array.

And so, in Drupal 7 you might see things like this:

$node->field_make_it_stop['LANGUAGE_NONE'][0]['wtf_bbq'];

In Drupal 8, the Entity Field API brings unified access to field properties and first-class objects for each entity type. So in Drupal 8, you see consistency like this:

$node->field_rainbows->value;
$node->title->value;

When you work with fields and entities in Drupal 8, you’re likely to interact with a suite of interfaces that comprise the API.

Key Interfaces in Drupal 8 Entity Field API

Let’s start with ContentEntityInterface, the overarching interface that content entities in Drupal 8 implement. (Node, Comment, Taxonomy Term, and BlockContent, among others, implement this interface.)

Each field and each property on a content entity is an instance of FieldItemListInterface. Even fields like the node title are lists, which just contain a single value.

Each FieldItemListInterface consists of one or more FieldItemInterface objects.

At the lowest level of the API, each FieldItemInterface is comprised of one or more DataDefinitionInterface objects that make up the properties or columns in each item value.

Perhaps a diagram might make this clearer. (See Diagram 1.)

Photo

Note that there are two broad types of fields: base fields and configurable fields. Base fields are the fields that are largely fixed in entity type. Configurable fields are those added during site building using the Field API.

Determining if an Entity is Valid

In Drupal 8, to check if an entity is valid, simply call its validate method:

$violations = $node->validate();

That will give you an instance of EntityConstraintViolationListInterface which helpfully implements \Countable and \ArrayAccess. If the resulting violations are empty, the entity is valid.

There are some handy methods on EntityConstraintViolationListInterface that help you work with any violations, including these:

  • getEntityViolations() filters those violations at the entity level, but is not specific to any field.
  • getByFields(array $field_names) filters the violations for a series of fields.
  • filterByFieldAccess() filters only those violations the current user has access to edit.

As the return implements \ArrayAccess, you can loop over the results and work with each ConstraintViolationInterface item in turn:

  • getMessage() gets the reason for the violation.
  • getPropertyPath() gets the name of the field in error. For example, if the third tag in field_tags is in error, the property path might be field_tags.2.target_id. These align with the form structure if you're in the context of validating a form.
  • getInvalidValue() returns the value of the field that is in error.

Interacting With the Entity Field Validation API

To enable an entity or field to be validated, Drupal needs to know which constraints apply to which field or entity type. This is done by using the API to add and modify constraints. Once you've gathered your requirements, you need to attach your validation constraints to your entity types and fields. How and where you do this depends on the type of constraint; whether you define the entity type in your code; the type of field; and the nature of the constraint. Before we address these, let's take a quick look at the anatomy of a constraint.

Constraints are Plugins

Continuing the learn once – apply everywhere paradigm that runs through much of Drupal 8, validation constraints are defined as plugins. Helpfully, core comes with a plethora of existing constraints such as:

  • NotNull;
  • Length (supporting minimum and maximum);
  • Count;
  • Range (for numeric values);
  • IsNull;
  • Email;
  • AllowedValues;
  • ComplexData (more on that later).

In many cases, you’ll be able to implement your validation logic by combining the existing constraint plugins in core. However, if you do need to create a custom constraint, all that is required is a new constraint plugin.

Adding Constraints to Base Fields

How you add constraints to base fields depends on whether or not your module defines the entity type in question.

If you're dealing with your own entity type, then you will have already implemented FieldableEntityInterface::baseFieldDefinitions(). In this method, you will already be defining your entity properties as an array using the BaseFieldDefinition::create() factory and its various builder methods. One such method is addConstraint. You call this passing the plugin ID for the required constraint as the first argument, and any configuration for the second argument.

This example from Aggregator module adds the FeedTitle constraint to the title property:

$fields['title'] = BaseFieldDefinition::create('string')
->setLabel(t('Title'))
->setDescription(t('The name of the feed (or the name of the website providing the feed).'))
->setRequired(TRUE)
->setSetting('max_length', 255)
->setDisplayOptions('form', array(
'type' => 'string_textfield',
'weight' => -5,
))
->setDisplayConfigurable('form', TRUE)
->addConstraint('FeedTitle', []);

Here, the constraint plugin being added has an ID of FeedTitle. Note that this field definition also includes setRequired(TRUE) and setSetting('max_length', 255). Behind the scenes, those two calls are also calling addConstraint, once each for the NotNull and Length plugins.

Finding the Plugin ID

As with every other plugin in Drupal 8, each constraint plugin defines its ID in its annotation. So, in order to determine what to use in the first argument to addConstraint, it’s simply a matter of opening the required plugin class and inspecting the class-level docblock.

/**
* Supports validating feed titles.
*
* @Constraint(
* id = "FeedTitle",
* label = @Translation("Feed title", context = "Validation")
* )
*/

If your module doesn't define the entity type, but you wish to add a constraint to a base field, you need to implement either hook_entity_base_field_info() or hook_entity_base_field_info_alter(). Then, you can add or remove constraints from entity base fields, or even define new base fields – a little-known power feature of Drupal 8.

Adding Constraints to Configurable Fields

This functionality is also possible for configurable fields added by the Field API. Now, that can only be done in code, but if Drupal's history is anything to go by, pretty soon a contributed module will debut which allows site builders to wire up validation in the UI.

To add a constraint to a configurable field, you need to implement hook_entity_bundle_field_info_alter(). At that point, you have an array of FieldConfigInterface objects, keyed by field name. For any of these, depending on both the entity type and bundle, you can call addConstraint or setConstraint to add or replace the field level constraints.

At the Data Type Level

Until now we've dealt with constraints at the FieldItemInterface level, but what if you need to apply validation at the DataDefinitionInterface level? For example, the EntityReferenceItem field item (each item in the list) contains a number of properties. Each of these is a DataDefinitionInterface. In this case, it has the target_id and entity properties. Similarly TextLongItem, which is used for rich text fields, has two properties in value and format, one to hold the field value and another to track the input format. If you want to add a constraint on a configurable field (FieldConfigInterface) directly to one of these properties, you can implement hook_entity_bundle_field_info_alter() and call setPropertyConstraints or addPropertyConstraints, nominating the constraints for the required property name. If you need to do the same for a BaseFieldDefinition, you can add a new ComplexData constraint using one of the techniques detailed above. The ComplexData constraint takes a nested array of constraints keyed by each property name. For example:

$field->addConstraint('ComplexData', [
  'value' => [
    'Length' => [
      'max' => 150,
      'maxMessage' => t('Title may not be longer than 150 characters.'),
    ],
  ],
]);

Multiple Fields

In some cases you may need a constraint that depends on the value of more than one field. For example, the Comment entity contains a name and an author field. Whether or not the entity or either of these fields is valid depends on the comment author and the values of those fields. Attaching validation constraints at the entity level depends again on whether your module declares the entity type, or whether you are adding the constraint to another module’s entity type.

For your entity type, entitylevel constraints are added using the entity type annotation, just like most of the other entitylevel metadata. Nominate these in the constraints property of the annotation. This is an array of constraint configuration, keyed by the constraint plugin ID.

To add entity-level constraints for another module, implement hook_entity_type_build() to add a new constraint, or hook_entity_type_alter() to alter an existing one.

Creating Your Own Constraint Plugins

To create your own constraint plugin, as with every other plugin in Drupal 8, you need to place a class in the correct folder structure and add an annotation.

Your class needs to live in the Drupal\your_module\Plugin\Validation\Constraint namespace which corresponds to the src/Plugin/Validation/Constraint folder in your module.
Your class needs to include an @Constraint annotation, like that shown above for FeedTitle, and would normally extend from \Symfony\Component\Validator\Constraint. Your constraint plugin really only needs to provide a default value for the violation message, which is a public property called message. You can use the same placeholders as you would for string translations in place of dynamic text.

You then need a validator object. By default, each constraint is validated by a class in the same namespace as the constraint, with the word Validator appended. For example, CommentNameConstraint is validated by CommentNameConstraintValidator. If you don't want to use this pattern, or you’re reusing one validator for several constraints, you can override Constraint::validatedBy() in your constraint plugin to nominate the validator class.

The validator class needs to extend from ConstraintValidator and implement the validate() method, receiving the data to validate and the constraint itself as arguments. Depending on the level at which the constraint is attached, the data to validate may be a ContentEntityInterface, a FieldItemListInterface, or a primitive value.

If you're creating an entity-level constraint, your constraint plugin must extend from CompositeConstraintBase instead of Constraint, and implement the coversFields() method. (See CommentNameConstraint in core.)

Wrapping Up

Having a solid API means that when you approach client projects on Drupal 8, you can think about your domain model first. Normally, during the requirements-gathering process, business rules regarding validation of the client's domain are captured. The new validation API will allow you to think differently about how to implement this logic.

For the first time, you will be able to bake this validation into your entities, whether you use custom entity types or rely on the common building blocks provided by core (Node, Comments, Terms, etc). With validation that isn't tied to form submission, you can write unit tests for your model, which allows you to rapidly iterate and refactor, knowing you've not introduced regressions. Although it may not be one of the most visible features in Drupal 8, the new validation API will change the way we work with Drupal.

Bring it on!

Image: celebration of light 2007 by Jon Rawlinson is licensed under CC BY 2.0

Jun 01 2015
Jun 01

Blindfolded Typing Competition Building rest endpoints with Drupal 8 is a snap. With the Rest UI module, fieldable entities, and the range of field-types in core, a site-builder can readily toss together a HAL+JSON REST server. Lists can be created with views using the REST export display type.

But any API is only as good as its documentation. Wouldn't it be great if Drupal could build out this documentation for us – and keep it current as we make changes?

API Documenation /node/{node}

REST API Documentation in the PHP World

Looking to our open source PHP cousins in Symfony, the NelmioApiDoc bundle generates documentation by parsing doc-blocks on each controller method.

But in Drupal, the REST endpoints are fully-dynamic methods in the REST module's controller. The REST and Serialization modules integrate with the Entity Field API to dynamically introspect the properties of each entity type and bundle. In theory, we should be able to do the same to build out our documentation by leveraging the information APIs in the Entity-Field and Routing APIs.

Providing Business Rules

We can use Drupal to introspect the available routes and the required fields for each end-point, but we can't capture business rules for each field. First we need to specify those rules for each field. Luckily, if we're doing things right, we've already accomplished this by adding help text when we created the field – so we’ll re-use that to build our documentation.

Finding the Endpoints

We first need to find the endpoints we wish to document. In the routing system, Drupal\Core\Routing\RouteProvider is responsible for loading and returning routes. However, it doesn't provide a method to fetch routes matching a pattern; we need to know the route names. And in order to introspect all routes – and to return those that come from the Rest module and those from the Views module that relate to a REST export display – we need to get a list of route names that are prefixed with rest. or view.. Fortunately, the routing system fires events before new routes are saved and when route building is finished. To react to these events, we need an event listener. We also need access to the Entity Manager and Views executable services to introspect the Views module routes and see which contain a REST Export display.

We define our event listener using a service.yml file, then tell Drupal that we want to react to the routing alter, and then route finished events. In the alter event-listener, we inspect the routes and flag those relating to views with REST Export displays – or from the REST module itself – and track their names using the State Key-Value store. We now have the routes, so we can start creating documentation for each of them. The full contents of the subscriber can be seen at rest_api_doc.

Building the Documentation

We want our documentation overview to be at /api/doc, and details for each route at /api/doc/{path}, grouping together routes that share a common path for discoverability. We define those routes using a routing.yml file, and build out a controller class to contain our callbacks. The overview method is fairly straightforward: we re-use the list of routes we collected earlier to present a table of contents; we expose a text-area field via a settings form to allow an administrator to enter some overview text; and we use the RouteProvider to return a discrete list of REST end-points by path for the given route names. The overview looks like this:

API Documentation Overview / List of End-points

End-point Details

Each route that is based on an entity-type contains parameters relating to the content being sent or received. For example, a node end-point will contain the node-id for GET, PATCH, and PUT operations. This is encapsulated in the parameters in the route path, as well as in the route requirements and options. We can inspect this detail in the routes using the Routing API, and use this to build out the documentation. By getting the parameters from each route, we can determine the entity-type. We can then examine the field and property data: Here it becomes a little convoluted.

Most traditional REST APIs would have an end-point for each type of object, and the properties of that object would be largely consistent. But Drupal has the notion of entity-type bundles. For the Node entity-type, these are represented by node-types. The fields that make up one node-type would ordinarily be completely different from the fields that make up another node-type. But from a REST perspective, with Drupal core, these two (or more) node-types share the same end-point. So the fields you must send for POST, PUT, and PATCH operations – as well as the content you receive back from GET requests – will vary wildly, depending on the bundle (node-type) of the item sent or requested. And, of course, it isn't limited to the node entity-type; taxonomy, comment, and blocks all have a notion of different fields for different bundles/types.

To get around this we list base-fields first, and then configurable fields specific to each bundle.

API Documentation /entity/taxonmy_term

Summary

Drupal 8 is a leap forward in REST capabilities from Drupal 7: the Routing and Entity-Field APIs contain built-in information discovery. That helps developers quickly discover the properties and attributes of route and entity objects, and can also be used by Drupal to document itself.

All the code described here has been turned into the Self Documenting Rest API module. Let's continue the conversation in the issue queue for the project.

Image: "Blindfolded Typing Competition" by Foxtongue is licensed under CC BY-NC-SA 2.0

Other articles from this issue:

Columns

J. Ayen Green

Our famischt freelancer constructs a step-by-step guide to landing that Drupal job and keeping the client happily signing paychecks. Nu?

Coming Soon

Features

Sam Boyer

As Dionne Warwick once warbled, “wishin’ and hopin’ and thinkin’ and prayin’ won’t bring Drupal 8 into your arms.” Or something like that. Meanwhile, there’s CRUD (and HAL and POX) to whet your appetite and help you make standards- compliant decisions.

Coming Soon

Amber Himes Matz

Drupal 8 may not be ready... Oh right, we said that. Okay, so meanwhile, you want speed, you want beauty, you want passion that leaps off the screen. Here are two great methods for exposing your views components as JSON. A walk in the park, a kiss in the dark...

Coming Soon

Mar 26 2015
Mar 26

Image Dialogs and Modals are an important UX pattern and can be used effectively both to provide information and to handle user interaction.

A key use for Dialogs and Modals in Drupal is to present a new user interaction without losing the original context. For example, when editing Views settings the modal allows the user to be presented with a new interface without navigating away from their original location.

Displaying Modals in Drupal 7

In Drupal 7, there are a number of approaches and modules for displaying and working with modals and dialogs. Views UI is probably the most common place where sitebuilders interact with modals in Drupal 7, closely followed by Panels/Page Manager. Both of these use modals for simplifying the user interface and the lazy-loading of elements when needed, keeping the interface uncluttered until a specific user interaction is required.

In Drupal 6, there were a number of dialog/modal API modules – with varying popularity – including Modal Frame API, Dialog API, and Popups API, but none have even reached an alpha release for Drupal 7, leaving Ctools Modal as the de facto API for Drupal 7.

Common Use, Different Approach

While each Drupal 6 and 7 modal/dialog module has a common use-case and set of requirements, each implement the functionality in their own way. Additionally, many of these use a Not Invented Here paradigm to roll custom solutions into a problem that’s already been solved in the wider web-community. As a result, many of these solutions are lacking in certain areas, such as accessibility. Also, given the range of different solutions and APIs, DX and consistency suffers.

Drupal 7 already includes the jQuery.UI library which itself contains a Dialog component. The Views modal uses the jQuery.UI Dialog while the Ctools module doesn't – further emphasizing the disconnect in approaches.

With Views coming into core in Drupal 8, we needed a Dialog/Modal API for it to use; this led us to develop the current solution, meaning that core now has an API for this functionality.

In addition, because accessibility is one of the core gates, we needed to solve the problem in a way that didn't exclude screen-reader users, those who prefer a keyboard, and those with JavaScript disabled.

Rather than continue the “not-invented-here” approach, we reached out to the jQuery.UI team and worked with them to solve some accessibility short-comings in the then stable-release. These made it into the jQuery.UI 1.10 release, cross-project collaboration for the win!

Handling non-js Fallbacks

One of the shortcomings of Drupal 7's routing system was that you had to juggle whether the user has JavaScript enabled when serving dialogs/modals. It was common to see URLs containing a nojs slug. For example, in Views UI there were two versions of each URL for JavaScript and non-JavaScript. The markup would render the URLs with the nojs form (e.g., 'http://example.com/admin/structure/views/nojs/display/myview/default/style_plugin' then the JavaScript would handle fetching the content from 'http://example.com/admin/structure/views/ajax/display/myview/default/style_plugin', with the menu callback at the ajax path returning Ajax commands to display a modal, and the nojs returning a normal form via a page callback for those with JavaScript disabled.

Drupal 8's Routing System

Drupal 8's routing system, based on that of Symfony 2, has support for the Accept request header baked into it. This means you can serve two different versions of the same content at any URL depending on the Accept headers used in the incoming request. For example, you could serve an HTML version of a node at node/1 as well as a JSON version, with only the accept-header varying.

This is achieved with a _format entry in your routing requirements entry. For example:

mymodule.route_html:
  path: '/admin/config/mymodule'
  defaults:
    _title: 'My module'
    _content: '\Drupal\mymodule\Controller\MyModuleController::somePage'
  requirements:
    _format: 'html'
    _access: 'TRUE'

mymodule.route_json:
  path: '/admin/config/mymodule'
  defaults:
    _controller: '\Drupal\mymodule\Controller\MyModuleController::jsonCallback'
  requirements:
    _format: 'json'
    _access: 'TRUE'

RouteEnhancers

Another key element in the new Drupal 8 routing system is the concept of RouteEnhancers. These are from the Symfony CMF routing component. They are similar to Drupal 7's hook_menu_alter(), but because they run at the time of Request instead of when the cache is empty, they have the opportunity to essentially re-route an incoming request.

One such enhancer is the ContentControllerEnhancer which handles incoming requests for Ajax, HTML, and dialogs/modals. In the case of Ajax requests, it makes sure the response is routed via the AjaxController. In the case of HTML requests, it sends the request via the HtmlPageController, which is responsible for wrapping the inner-page content in blocks etc. But the behavior we're interested in here is when it routes incoming requests with an Accept header of either application/vnd.drupal-modal or application/vnd.drupal-dialog to the DialogController.

The DialogController

This is the guts of the PHP side of the Dialog API. It handles incoming Dialog requests and returns the response in a format that the JavaScript code running client side then uses to display the dialog or modal.

So how does it work? The ContentControllerEnhancer sends the request via the DialogController in a manner which allows the DialogController to ascertain where the original request would have ended up if it were a standard (HTML) page request. The DialogController then uses this information to get the original content that would have been seen on that page (minus the blocks, etc.) that wrap the inner content on a Drupal page.

The DialogController then creates the necessary AjaxCommand objects for displaying the dialog/modal and returns an AjaxResponse object in a similar fashion to any other AjaxCommand/AjaxResponse. The JavaScript in the client-side code that made the request then executes these commands and the dialog/modal is displayed.

Using the Dialog API

There are two main ways to use the Dialog API: either with a link, or with a form-button.

Simple Link Example

To make a link return the content in a Dialog, all you need to do is add two attributes; the use-ajax class and the appropriate data-accepts attribute, depending on whether you want a modal or a plain dialog. To request a modal, use data-accepts='application/vnd.drupal-modal'. To request a dialog, use data-accepts='application/vnd.drupal-dialog'.

<a class="use-ajax" data-accepts="application/vnd.drupal-modal" href="https://drupalwatchdog.com/volume-4/issue-1/make-mine-modal/some/path">Make mine a modal</a>

Form Example

To use a form button to trigger a dialog, just setup an #ajax property like any other Ajax behavior, add an accept behavior and a callback method to return a new AjaxResponse containing an OpenDialogCommand or OpenModalDialogCommand.

<?php
/**
 * {@inheritdoc}
 */
public function buildForm(array $form, array &$form_state) {
  // Make the button return results as a modal.
  $form['foo'] = array(
    '#type' => 'submit',
    '#value' => t('Make it a modal!'),
    '#ajax' => array(
      'accepts' => 'application/vnd.drupal-modal',
      'callback' => array($this, 'foo'),
    ),
  );
  return parent::buildForm($form, $form_state);
}
 
/**
 * Ajax callback to display a modal.
 */
public function foo(array &$form, array &$form_state) {
  $content = array(
    'content' => array(
      '#markup' => 'My return',
    ),
  );
  $response = new AjaxResponse();
  $html = drupal_render($content);
  $response->addCommand(new OpenModalDialogCommand('Hi', $html));
  return $response;
}

The resultant modal looks like so:

Model - before.

And when this issue lands, it will look like so:

Model - after.

Summary

So that's a quick overview of the Dialog API. I'm looking forward to the possibilities this will open up for Drupal 8 contrib. Particularly for themers, the ability to quickly add two attributes to a link and get the result in a modal is going to make adding dynamic interactions far simpler.

One place where this will make a huge UX improvement is for confirmation forms: clicking the 'delete' link for a piece of content could load the confirmation form in a modal, with no need to redirect the user to a new location.

Bring on Drupal 8!

Image: "222/365 - book in bloom" by orangesparrow is licensed under CC BY-NC-ND 2.0

Dec 18 2014
Dec 18

PhotoIn the beginning there was the Common Gateway Interface, commonly known as CGI – a standard approach used to dynamically generate web pages. Originally devised in 1993 by the NCSA team and formally defined by RFC 3875 in 2004, CGI 1.1 took seven years to go from the original RFC to an endorsed standard.

In 1994, not long after the original CGI standard was documented by NCSA, Rasmus Lerdorf created Personal Home Page tools (PHP Tools), an implementation of the Common Gateway Interface written in C. After going through a number of iterations and name-changes this grew to be the PHP language we know and love.

One of PHP's strengths was the way in which it made many of the request and server specific variables, as defined by the CGI standard, easy to access – through the use of superglobals, namely $_POST, $_GET, and $_SERVER. Each of these is an associative array. In the case of $_POST, the request body is parsed for you and turned into an array of user-submitted values, keyed by field name, and conveniently supporting nested arrays. Similarly for $_GET, the query string is parsed by PHP and turned into a keyed array. In the case of $_SERVER, the gamut of server-specific variables are available for your script to interrogate.

Now, as Drupal developers, we rarely interact with $_POST and $_SERVER, and seldom interact with $_GET. Variables submitted through forms are abstracted behind Drupal's Form API; we ordinarily work with the $form_state variable instead of $_POST. In terms of the $_SERVER superglobal, Drupal provides handy wrappers for many of these values too, so instead we interact with those rather than with $_SERVER directly.

Please note that this article is accurate at the time of writing, however there is a proposal (see http://wdog.it/4/1/attributes and http://wdog.it/4/1/match) to improve the developer experience of working with important attributes stored on the Request object. The goal of these issues is to introduce methods on helper classes to make access to important elements of the request/response life-cycle more explicit instead of relying on using arbitrary string keys to fetch items from the Request object.

Under the proposal, items that relate to the routing system will be available from a RouteMatch class containing useful methods, for example RouteMatch::getRawArguments(). This would be used instead of $request->attributes->get('_raw_variables'). Under the proposal this will form a key developer facing API for working with upcast routing parameters so RouteMatch::getArgument('node') will replace $request->attributes->get('node'). This makes sense because the node object is not part of the request, it is a product of the routing of that request.

In addition the existing RequestHelper class will be expanded to add additional methods for interrogating concepts related to the Request but unrelated to routing. For example RequestHelper::isCleanUrl() can be used to determine if the incoming request utilizes clean urls, instead of $request->attributes->get('clean_urls').

Before using the example code in this article, be sure to first review the above issues.

If It Ain't Broke, Don't Fix It?

So what's wrong with using $_GET, $_POST, and $_SERVER in conjunction with some other Drupalisms™ like arg() and current_path()?
Well, does the code below look familiar?

<?php
  if (($nid = arg(1)) && is_numeric($nid) && !arg(2)) {
    // We have a node page and it's not the node edit page.
    // Do something.
  }

If you're nodding your head, then you're probably aware of one of the drivers behind the Web Services and Core Context initiative providing context to a page request or, better yet, providing context to a request – because, after all, the web has changed and Drupal has to be able to handle more than pages to remain relevant.

So, taking a step back, the Hypertext Transfer Protocol, commonly known as HTTP, is essentially made up of a Request and a Response. An incoming request is made, and then, based on the request contents and some application logic, a response is returned.

Now early in the Drupal 8 release cycle, it was decided that we would leverage the Symfony 2 components, and one such set of components is called HttpFoundation. Essentially these components are object-oriented replacements for PHP superglobals that represent the request and the functions used to generate the response.

In Drupal 8, we represent an incoming request as a Request object (Symfony\Component\HttpFoundation\Request) and a response as a code object (Symfony\Component\HttpFoundation\Response).

Throughout the Drupal 8 cycle, we've been working hard to remove all references to the PHP superglobals and replace them with references to the Request object.

“But why?” I hear you ask.

Well, there are a number of reasons.

First, because we're reacting to an incoming request, and this object is available throughout the whole response/routing process, it represents the ideal place to store context. That is, if you're responding to a request for node/{node}, then you would most likely need to know which Node you are dealing with.

Second, because we're dealing with a first class object, we can do away with a lot of code juggling that is normally required with arrays.

Consider:

And third, if our whole response pipeline is wired to use the incoming request object, we open the possibility of performing functional web testing without the need for an actual HTTP request: we can mock the Request, collect the Response, and compare it against the expected outcome. Contrast this with our current web testing which relies on Curl to make full-fledged requests to a test-only site that is installed during test setup. We're not there yet, but we're getting close.

So that’s the what and the why of the Symfony Request object. Now lets get to the how.

Working with the Request Object

With the Request object, each of $_SERVER, $_GET, and $_POST can be accessed from the server, query, and request properties, respectively. These properties are ParameterBag objects (Symfony\Component\HttpFoundation\ParameterBag), which have useful methods like has(), get(), and set().

<?php
  // Drupal 7.
  $foo = FALSE;
  if (isset($_GET['foo'])) {
    $foo = $_GET['foo'];
  }
 
  // Drupal 8 - assuming Request is $request.
  $foo = $request->query->get('foo');

Getting Context From the Request

So how do you use the Request object to get the context of a page?
In Drupal 7, we had menu_get_object() to fetch an item from the current path. For example, when viewing node/1 the defaults were enough:

<?php
$node = menu_get_object();

For the taxonomy terms on taxonomy/term/1, you had to pass the arguments:

<?php
// We know that the menu callback is /taxonomy/term/%taxonomy_term
// with the term in position 2.
$term = menu_get_object('taxonomy_term', 2);

In Drupal 8, upcast parameters are simply available from the attributes property of the request. The attributes property is also a ParameterBag object, so you also have the convenient has(), set(), and get() methods.

<?php
// Assuming the Request is $request.
$node = $request->attributes->get('node');

How simple is that!

Getting the Route Name

In the earlier example, where we were trying to ascertain if we were on a 'node page', it involved some juggling using the arg() function. One of the great features of the new Routing system in Drupal 8 is that each route has a name. For example, consider the following routing.yml entry from Forum module:

forum.delete:
  path: '/admin/structure/forum/delete/forum/{taxonomy_term}'
  defaults:
    _form: '\Drupal\forum\Form\DeleteForm'
    _title: 'Delete forum'
  requirements:
    _permission: 'administer forums'

In this example the route name is 'forum.delete', and this route handles the path 'admin/structure/forum/delete/forum/{taxonomy_term}', where taxonomy_term is an integer corresponding with a term ID.

In your code, if you want to check the route name, you can access a special attribute on the Request object and, instead of using a random string to refer to the attribute name, use the ROUTE_NAME constant defined on the RouteObjectInterface (\Symfony\Cmf\Component\Routing\RouteObjectInterface).

By the time Drupal 8 comes out, we might have a request helper object to make this simpler, but here's an example of how to fetch the route name at the moment:

<?php
 
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 
$route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME);
 
if ($route_name == 'mymodule.someroute') {
  // Do something.
}

Getting Request Headers

The Symfony Request object also includes a HeaderBag (Symfony\Component\HttpFoundation\HeaderBag), which gives easy access to any request headers you might need. The HeaderBag contains the same convenient methods as the ParameterBag

Deep Integration with the Routing System

Looking back at that same routing entry from forum.routing.yml, you'll note the 'defaults' section. Those values can also be found in the attributes parameter bag:

<?php
// Assuming the Request is $request.
if ($request->attributes->has('_title')) {
  $title = $request->attributes->get('_title');
}

I'll Swap You an Int for an Object – ParamConverters

Note that in the forum routing entry above, the path includes a {taxonomy_term} placeholder. In the URL this would be a term ID, so the URL might look like admin/structure/forum/delete/forum/1. But in your code, the taxonomy_term attribute will be a full-fledged object implementing TermInterface (\Drupal\taxonomy\TermInterface), meaning you have direct access to all the methods and properties a Term contains.

<?php
$term = $request->attributes->get('taxonomy_term');
if ($term->bundle() == 'forum') {
  $name = $term->label();
}

This works using the new routing system concept of parameter converters. These are objects implementing ParamConverterInterface (Drupal\Core\ParamConverter\ParamConverterInterface). This interface is not dissimilar to Drupal 7's %node style placeholders in hook_menu entries but is much more explicit, no longer relying on function naming conventions. By default, Drupal core ships with support for upcasting any entity-type-ID into an actual EntityInterface (\Drupal\Core\Entity\EntityInterface) object. So if you name your placeholder with an entity-type-id, it will be automatically upcast for you. If you're interested in gaining further insight into the upcasting system, take a look at the ParamConverterInterface.

Raw Values

What happens if you want to get access to the raw values before they were upcast?
In the previous example, suppose you wanted to get access to the raw term ID (1) instead of the actual term object, and let’s assume that for some reason just using $term->id() wasn't an option (just go with me on this). In this case, the raw values are stored in the request attributes, in another ParameterBag that you can fetch the values from like so:

<?php
if ($request->attributes->get('_raw_variables')->has('taxonomy_term')) {
  $term_id = $request->attributes->get('_raw_variables')->get('taxonomy_term');
}

There is an issue in the core queue to add a constant for _raw_variables to make this more robust, so using _raw_variables might be simplified by the time Drupal 8 is released.

Accessing the Request Object in Your Code

That covers working with the request, but how do you get access to it in your code? It depends on where you need it, as the approach differs between procedural code, services, and controllers.

Requesting the Request in Your Controller

If you're building a controller (the Drupal 8 equivalent of a page callback), and you need access to the request, the good news is: all you have to do is ask. And by ask I mean declare it in your method arguments. Some reflection wizardry in the Symfony routing components will ensure it gets handed to your method automagically. In this example, the MyModuleController::fooBar method type-hints its $requestparameter as a Symfony Request object and the ControllerResolver (\Symfony\Component\HttpKernel\Controller\ControllerResolver) ensures that the active Request is passed to the fooBar method when called.

<?php
/**
 * @file
 * Contains \Drupal\mymodule\Controller\MyModuleController.
 */
 
namespace Drupal\mymodule\Controller;
 
use Drupal\Core\Controller\ControllerBase;
 
/**
 * Controller methods for mymodule routes.
 */
class MyModuleController extends ControllerBase {
 
  /**
   * Provides content for the /mymodule/foobar path.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current request
   */
  public function fooBar(Request $request) {
    $quantity = $request->query->get('quantity');
    return array(
      '#type' => 'markup',
      '#markup' => $this->t('You requested %quantity items.', array(
        '%quantity' => $quantity,
      )),
    );
  }
}

Accessing the request from procedural code

In procedural code, you don't have the opportunity to inject dependencies or magically receive the request, so you have to call out to the \Drupal singleton and use the static request method.

<?php
 
/**
 * Utility to check if the foo attributes exists on the Request.
 *
 * @return bool
 *   Returns TRUE if the request has a foo attribute.
 */
function mymodule_request_has_foo() {
  $request = \Drupal::request();
  return $request->attributes->has('foo');
}

Now while that might seem simpler than having the Request object passed in as an argument, you should resist the temptation to use \Drupal::request() in object-oriented code. Calling out to the global \Drupal singleton in your object-oriented code makes unit-testing your code more difficult, as you need to setup a container, add the request to it, and then call the setContainer method on \Drupal before you can do so.

Injecting the Request Into a Service

If you are creating a service and you need access to the request, you can nominate it as a dependency in your services.yml file thusly:

services:
  mymodule.foo:
    class: Drupal\mymodule\MyModuleManager
    arguments: ['@database', '@request']

But this depends on the life-cycle of your service, as the request service isn't always available. For information on how to create a service that might need to be instantiated before Drupal enters the Request scope, see the Getting Advanced section later in this article.

Bring Your Own – Creating a Request

If you're writing code, such as a custom application or a shell script that lives outside the normal Drupal bootstrap, you might find that you need to instantiate a Request object. Or you might be working on some tests and wish to create a pseudo Request. In these cases, you need to build your own Request object. There are two approaches to doing so, depending on your use case.

Transforming Globals Into a Request Object

If you're working in a script or custom outside the standard Drupal bootstrap, you can easily transform the superglobals into a Request object using the static createFromGlobals method:

<?php
 
use Symfony\Component\HttpFoundation\Request;
 
$request = Request::createFromGlobals();

Making Your Own Request

If you need to create a request for simulation purposes while testing or some other purpose such as a sub-request, you can manually create one using the static create method. This takes arguments for the URL, method, parameters, etc.

<?php
 
use Symfony\Component\HttpFoundation\Request;
 
$uri = 'http://foo.com/bar';
$method = 'get';
$params = array(
  'yes' => 'please'
);
$request = Request::create($uri, $method, $params);
 
// Will return 'please'.
$yes = $request->query->get('yes');

Getting More Advanced

Earlier, we talked about creating a service with a dependency on the Request and noted that this would only work if your service was only instantiated during the request scope. When the DrupalKernel (\Drupal\Core\DrupalKernel) handles an incoming request, it calls the handle method on the HttpKernel service (\Drupal\Core\HttpKernel This notifies the dependency injection container that the request scope has been entered, sets the request service in the container, and generates a response. Once the response has been built by the kernel, it notifies the container that the request scope has been left.
When the container is in the request scope, calls to fetch the 'request' service are successful. But if your service requires the Request object and can be initialized before or after the kernel is in the request scope, the request object will not be available. In these instances you'll raise a RuntimeException (Symfony\Component\DependencyInjection\Exception\RuntimeException) with the message You have requested a synthetic service ("request"). The DIC does not know how to construct this service because your service was initialized too early or too late.
With such a service, you need to use setter injection instead of constructor injection. This just means that instead of handing in the Request object as an argument to your service's constructor, you need to have a setter method to handle handing in the Request object at a later time.
One such service in core is the FormBuilder (Drupal\Core\Form\FormBuilder) which is the successor to Drupal 7's drupal_get_form(), drupal_build_form(), and other functions.

Consider its definition from core.services.yml:

  form_builder:
    class: Drupal\Core\Form\FormBuilder
    arguments: ['@module_handler', '@keyvalue.expirable', '@event_dispatcher', 
      '@url_generator', '@string_translation', '@?csrf_token', '@?http_kernel']
    calls:
      - [setRequest, ['@?request=']]

Here, the calls definition tells the dependency injection container to call setRequest on the FormBuilder object, only when the request object is available. If the request object is not available, the call is ignored. The “?” In the request service definition tells the container builder to ignore invalid references; for example, that the CSRF token and HTTP kernel dependencies are also declared with the “?” notation.
For the FormBuilder service, if it is used in circumstances where it might be built before the request scope is entered, either code calling to the form builder service will need to take care to call setRequest on its behalf, or an event listener will be needed.

Reacting to the Request - Event Listeners

When Drupal enters the request scope, a Symfony Event is fired. Symfony Events are the object-oriented equivalent of Drupal hooks. The event fired is KernelEvents::REQUEST (Symfony\Component\HttpKernel\KernelEvents). If you need to react to the Kernel entering the request scope, you can create an event listener and register KernelEvents::REQUEST as one of the subscribed events. Your listener method will receive a GetResponseEvent (Symfony\Component\HttpKernel\Event\GetResponseEvent) object as an argument and you can use GetResponseEvent->getRequest() to access the request. This represents the most advanced end of the scale, but the Symfony event-dispatcher component is well worth learning and will be another valuable addition to your programmer’s knowledge base. If you're interested in learning more about Symfony Events, see the Event Dispatcher chapter in the Symfony Components documentation.

Next Steps

So where to from here? Drupal 8 is still under development and there are some final pieces of the puzzle yet to be completed, particularly around web testing.

Web Testing with the Kernel Instead of Curl

Although we’re not there yet, one of the major advantages of using the Request object throughout our code instead of the PHP superglobals is the ability to mock a page request. At present our Drupal 8 functional testing still uses Simpletest, which relies on Curl to make real requests to a fully functional test-site. All of this adds overhead, complexity, and execution time to our test runner. We still have a few more places to clean up where we're accessing the superglobals, but we're almost at the point where something like this might become a reality.
Essentially, we might be able to simulate the whole request-response cycle from inside the test-runner. I don't know about you, but I think that would be pretty darn neat!

Image: ©iStockphoto.com/Gudella

Jun 18 2012
Jun 18

In this episode we chat with Ivan Zugec at Drupal Down Under 2012 about his favourite bits of Drupal.
Ivan has been using Drupal for around 5 years, coming to Drupal looking for an ecommerce solution back in the 4.6 days. He gave ecommerce a try but couldn't get it to work. Around the same time he needed a CMS solution that provided clean-urls for a client and at that time, Drupal was the only open-source product providing that at no cost.
We discuss Ivan's contributions to the search API, in particular the search api pages. We discuss the percurliarities of search in Drupal and the fact that nearly every client wants a differnt search solution. We discuss how search api allows you to create your search pages using views and the flexibility this provides.
We discuss Drupal 8 and the advantages of a standardized exportable api that will be provided by the configuration management initiative.

Note since recording this podcast Ivan is no longer working on the Search API Pages module.

Jun 18 2012
Jun 18

We talk with Bevan about his experiences at Drupal Downunder 2012.
Bevan discusses his highlights of Drupal Down Under.
Bevan discusses his transition from web-designer (or in his words wannabe-designer!) to the technical side of building the web.
Having worked with Drupal for 6 years Bevan is a Drupal veteran in Australia and New Zealand.
We discuss the growth of Drupal in Australia and the demand for Drupal talent.
We finish up by discussing Bevan's togmine project which integrates Redmine and Toggl, the module basically provides a bridge between toggle tracked time and enters it into Redmine.

Jun 18 2012
Jun 18

In this episode we catch up with Simon Hobbs (sime).
Simon is quite well known in the Australian Drupal community.
Simon talks about the early days of Drupal in Australia with the beginnings of the meetups in Melbourne.
We talk about Simon's experiences running his own company and his transition to working for Peregrine Adventures.
We discuss updating sites from Drupal 6 to 7 and the future of Drupal 8, in particular the push towards Drupal becoming more framework-oriented to adapt to today's developing web.
We discuss the success of Drupal Downunder and future plans for Drupal growth in Australia.

Jun 17 2012
Jun 17

Just a quick update to all of those who were kind enough to participate in an interview with me during Drupal Downunder 2012 to explain what's happening with Drupal Yarns.

I've not forgotten you!

Things have changed considerably for Nick and myself, both of us have accepted permanent positions with PreviousNext, Australia's largest Drupal Firm. As a result Rowlands Group has effectively wound down all activities.

The move came suddenly and this has made it difficult for me to find time to edit and post the Yarns I recorded. Now that the carry-over Rowlands Group work has been completed I'm hoping to have more time to edit and post the remaining Yarns.

Thanks again to all who took part and keep an eye out for more posts appearing in late June and throughout July.

Lee Rowlands (larowlan).

Mar 21 2012
Mar 21

Rowlands Group is a boutique Drupal, Web and Smart-Phone Development firm based out of Moore Park Beach, in sub-tropical Queensland Australia, but servicing clients worldwide.

We specialise in providing custom Drupal Development services to other Web-Development and Design firms.

Why not get in touch to find out how we can help make your next Drupal project sing.

Feb 29 2012
Feb 29

Background

In Drupal 6, Views Bulk Operations and Draggable Views defined views style plugins in order to deliver their respective functionalities. While this is great it meant you could only have one or the other, not both.
Then Views 3 came along with support for views as forms. This is the functionality you see in Drupal Commerce that allows the cart page to be generated using views.

Drupal 7

In order to take advantage of this new functionality, Views Bulk Operations in Drupal 7 was rewritten as a field handler with form support instead of a style plugin. Similarly the new 7.x-2.x branch of Draggable Views is also a rewrite to utilise this functionality.
So this now means you can combine both Draggable functionality with Views Bulk Operations functionality in the one view right?
Unfortunately if you define multiple Bulk Operations in your view, Views Bulk Operations will remove the default Save button added by views. This button is needed by Draggable Views to save the new order. But this is Drupal, so all is not lost - you can add the Save button back with a custom module and some form alter hooks.

Writing the custom module

First of all we need to target the correct form, so grab the form id of the view you're trying to add the save button back to. If you don't know how to do this either inspect the dom and find the form tag or just implement hook_form_alter and output the $form_id parameter till you find the right one.
In this case we'll call our module mymodule and our form id will be views_form_myview_page. In case you didn't guess the format of the form id's generated by views follow a views_form_VIEWID_DISPLAYID patterm. So start with an implementation of hook_form_FORM_ID_alter

<?php
/**
* Implements hook_form_FORM_ID_alter() for views_form_myview_page().
*
* Alter the form to add the 'save' button back in. This button is removed by
* views_bulk_operations - rendering draggableviews non-functional.
*
* @see views_bulk_operations_form
*/
function mymodule_form_views_form_myview_page_alter(&$form, $form_state) {
 
// Repair the save button removed by views_bulk_operations.
  // Here we make sure that draggableviews is operating on the form.
 
if (isset($form['draggableviews']) && !empty($form['draggableviews']) &&
   
// And confirm the button is missing.
     
!isset($form['actions']['submit'])) {
   
// Then we make sure the user has the correct permissions.
   
if (!user_access('access draggableviews')) {
     
// No need to fix save button.
     
return;
    }
   
// Now we retrieve the Draggable Views options - Draggable Views allows you to
    // override the caption on the 'Save' button as well as allow ajax submission.
   
$options = $form['view']['#value']->field['draggableviews']->options['draggableviews'];
   
// Add back the save button.
   
$form['actions']['submit'] = array(
     
// Use the label provided by Draggable Views.
     
'#value' => t($options['save_button_label']),
     
'#type' => 'submit',
     
// Attach a button level submit handler so Views Bulk Operations submit handlers
      // don't fire when the 'Save' button is clicked.
     
'#submit' => array('draggableviews_views_form_submit'),
     
// Ensure that form level validation does not occur when the 'Save' button is clicked
      // (otherwise Views Bulk Operations won't be happy that you've not selected any rows
      // or an operation).
     
'#validate' => array('mymodule_psuedo_validate'),
    );
// Respect the original Draggable Views ajax configuration option
   
if ($options['ajax']) {
     
$form['actions']['submit']['#ajax'] = array(
       
'callback' => 'draggableviews_view_draggabletable_form_ajax',
      );
    }
  }
}
?>

Now we've got our save button back - we just need to write the validation handler 'mymodule_psuedo_validate'.
As you can guess from the name, it doesn't do anything other than prevent the save button firing the form level validation code (without this Views Bulk Operations will return validation errors regarding selection of rows and an operation).

<?php
/**
* Psuedo validation handler
*
* @param array $form
*   the form
* @param array $form_state
*   the form state
*
* @see views_bulk_operations_form_validate()
*/
function mymodule_psuedo_validate($form, &$form_state) {
 
// We deliberately do nothing here, if this validation handler isn't attached
  // to our 're-added' save button then views_bulk_operations_form_validate is
  // called and validation fails.
}
?>

Wrapping up

And that's basically it with this you can create a flexible administration view for your content that rolls both ordering and common administrative tasks into a single form.
Thanks to the maintainers of Views Bulk Operations and Draggable Views for their work on these modules.

Feb 15 2012
Feb 15

In this episode of Drupal Yarns we catch up with Miguel Jacq (mig5) one of the maintainers of the Ægir hosting platform.
We discuss how mig5 came to work on the Ægir project covering his initial discovery of the project from a need to automate upgrades through to submitting numerous patches which ultimately resulted in the maintainers giving him commit access.
We talk about the origins of the Ægir project and the future directions of the project, in particular some of the new features available in the Ægir contrib space.
Finally we talk about Miguel's consulting company mig5.net and the niche he's found for sysadmin with a Drupal flavour.
Edit: Audio player is now working again - thanks for those who left a comment/notified us - we'd done some migration and hadn't updated the jplayer config to reflect the new path.

Feb 06 2012
Feb 06

In this episode of Drupal Yarns we catch up with one of Australia's two nominees for the Drupal Association at-large Directors - Ryan Cross (rcross) (view the nomination).

We featured the other nominee (Donna) in our last episode.

In this episode we talk to Ryan about his background and how he came to be heavily involved in the Drupal project and Australian communities.

We talk about his work with Cross Functional, a Drupal shop based out of Sydney Australia and the type of work they're doing with Drupal at the moment.

Completing the circle we talk to Ryan about the future of Drupal and what he's most looking forward to in Drupal 8. Ryan talks about the WSSCI initiative and what it will mean for Drupal and the kinds of projects that Drupal will empower.

If you haven't voted yet, head over to the Drupal association site and vote before the 8th of February deadline.

Feb 02 2012
Feb 02

In this episode of Drupal Yarns we talk with Donna Benjamin (KatteKrab) about the success of Drupal Downunder and various other Australian Drupal issues.

Donna Benjamin is the co-founder of Creative Contingencies and has been using Drupal for over five years.

Donna talks about how she came to Drupal and some of the challenges facing Drupal in terms of being everything to everyone.

On the day of Drupal's 11 birthday (see cupcakes!) we talk about emerging CMS's and where Drupal fits in the open-source ecosystem.

We talk about Drupal's stengths and the change in mindset of business regarding open-source.

Jan 24 2012
Jan 24

Rowlands Group is a boutique Drupal, Web and Smart-Phone Development firm based out of Moore Park Beach, in sub-tropical Queensland Australia, but servicing clients worldwide.

We specialise in providing custom Drupal Development services to other Web-Development and Design firms.

Why not get in touch to find out how we can help make your next Drupal project sing.

Jan 23 2012
Jan 23

Shop 4 Beachside Central,

63 Sylvan Drive

Moore Park Beach

Queensland 4670 Australia

Jan 14 2012
Jan 14

Rowlands Group is a boutique Drupal, Web and Smart-Phone Development firm based out of Moore Park Beach, in sub-tropical Queensland Australia, but servicing clients worldwide.

We specialise in providing custom Drupal Development services to other Web-Development and Design firms.

Why not get in touch to find out how we can help make your next Drupal project sing.

Jan 14 2012
Jan 14

One of the options for SendMyPostcards.com is for users to choose from their Facebook Photos and Albums for the front of their postcard.

In this article I'll demonstrate how to use the Facebook Graph API from Drupal to access user's details from Facebook including, but not limited to, their albums and photos.

Getting Started

To get started you need the fbconnect module. The module has a fairly involved install process that includes:

  • Standard module installation
  • Registration of your site with the Facebook Developer program and obtaining the appropriate API keys etc
  • Downloading the Facebook PHP SDK
  • For Drupal 6, making changes to your page.tpl.php to ensure that fbxml works

Detailed instructions can be found in the module's read me files, once the whole thing is setup you should have a Facebook Connect button on the registration and login forms.

Requesting permissions

Fbconnect requests a basic set of permissions when the user registers on your site via Facebook, such as their basic details and the ability to send email to them via facebook. However anything beyond that requires elevated permissions. Eg to see their friends or photos/albums.

Luckily fbconnect's api exposes a hook allowing you to add to the requested permissions - hook_fbconnect_login_button_alter. This hook takes one argument $attrs. Using this hook we can request additional permissions. Eg for example to request access to the user's photos:

<?php
/**
* Implements hook_fbconnect_login_button
*/
function mymodule_fbconnect_login_button_alter(&$attrs) {
 
$attrs['perms'] .= ',user_photos';
}
?>

Using the Facebook Graph API

Making sure we're ready to make requests

Once we've got the correct permissions, we can access the user's photos while they have an active session on our site and are logged in to Facebook.
First we check if they're logged into Facebook and grab a reference to the :

<?php
if (!user_is_anonymous() && fbconnect_get_fbuid() && _get_user_fbuid($user->uid)) {
 
// .. processing in here
}
?>


If they're not logged in we can show a login button, eg in a form builder

<?php
  $form
['fbconnect_button'] = array(
   
'#type' => 'item',
   
'#description' => t('Sign in using Facebook'),
   
// We use fbconnect_render_button to render the button with our required attributes (note the user_photos permission).
   
'#value' => fbconnect_render_button(array('perms' => 'email,user_photos')),
   
'#weight' => 1,
   
'#id' => 'fbconnect_button',
  );
  if (!
user_is_anonymous()) {
   
// The user is logged into our site but not facebook
   
$form['fbconnect_button']['#description'] = t('Sign in to your Facebook account');
  }
 
$form['#submit'][] = 'fbconnect_redirect_submit';
?>


Now this form will end up at the default location that fbconnect_redirect_submit sends them too when the user signs in, if you need it to go back somewhere else, you'll need to implement hook_form_alter for $form_id fbconnect_autoconnect_form and add your own submit callback that sets $form_state['redirect'] - just your everyday run off the mill form_alter to modify the redirect location.

Making the request

Once your user is logged into facebook and your site, you're right to start making requests to the Facebook graph api. Eg fetching their albums and images

<?php
// First we get a reference to the facebook client, provided by the Facebook PHP SDK and neatly wrapped by fbconnect
$fb = facebook_client();
// Get their albums
$albums = $fb->api('/me/albums');
// Loop their albums
foreach ($albums['data'] as $album) {
   
$album = (object)$album;
   
//fetch the images
   
$album->images = array();
   
$images = $fb->api('/'. $album->id .'/photos?limit=25');
    foreach (
$images['data'] as $image) {
     
$image = (object)$image;
     
// Do something with the image ....
   
}
   
// Do something with the album ....
 
}
?>


In terms of the 'something' - for SendMyPostcards.com we temporarily cache the user's albums and images. For performance reasons we only fetch the first 25 images from their first album. We request each subsequent album and page as needed and update the cached object. We don't use any permanent storage of the albums or photos as obviously the user will add to their albums and photos over time and we want to fetch the latest values next time they use the site (or after the cache is cleared).

Taking things further

The graph api is very powerful, for the full reference on what you can access using this technique see the developer documentation.
How have you used the Graph API? Let us know via the comments.

Aug 12 2011
Aug 12

Displaying content using a carousel is a common design pattern, and with Drupal it's pretty easy to use views + jcarousel to whip up a carousel for displaying in blocks etc.
Jcarousel comes with some default skins (tango, ie and default) and whilst they're nice, they rarely fit the design you're working too. So as a result we commonly find ourself leaving the carousel skin as 'None' (in the view style output settings) and just adding in the css via the theme. It's also common to have a number of carousels on one site, each with different sized items and their own particular requirements.
And that's when it can get complicated, jcarousel's javascript requires some pretty specific css.
If you miss the item width or size, or don't setup the clip properly you'll be left with a carousel that just doesn't work. And if you've got several carousels, the maintenance burden for the css starts to build up.
Enter SASS, everyone's favourite css preprocessor.
We're happy to share out SASS mixin for horizontal-jcarousels - the code is pretty self explanatory:

/*
* jcarousel mixin
* @author Lee Rowlands (rowlandsgroup.com)
* @param $selector carousel selector)
* @param $carousel_width
* @param $carousel_height
* @param $carousel_padding
* @param $item_width
* @param $item_height
* @param $item_margin
* @param $button_width
* @param $button_height
* @param $button_image
* @param $button_top
* @param $button_left
* @param $button_right
* @param $button_left_position (background position)
* @param $button_right_position
* @param $button_left_position_hover
* @param $button_right_position_hover
* @param $button_left_position_disabled
* @param $button_right_position_disabled
*/
@mixin horizontal-jcarousel($selector, $carousel_width: 960px,
  $carousel_height: 150px, $carousel_padding: 0 50px, $carousel_margin: auto,
  $item_width: 160px, $item_height: 160px, $item_margin: 0 10px,
  $button_width: 31px, $button_height: 71px,
  $button_image: url(../images/carousel.png), $button_top: 10px,
  $button_left: 42px, $button_right: 42px, $button_left_position: 0 -35px,
  $button_right_position: 0 -106px, $button_left_position_hover: 0 -35px,
  $button_right_position_hover: 0 -106px,
  $button_left_position_disabled: 0 -35px,
  $button_right_position_disabled: 0 -106px) {
  #{$selector} {
    .jcarousel-container-horizontal {
      width: $carousel_width;
      height: $carousel_height;
      padding: $carousel_padding;
      margin: $carousel_margin;
    }
    .jcarousel-clip-horizontal {
      width: $carousel_width;
    }
   
    .jcarousel-item {
      padding: 0;
      width: $item_width;
      height: $item_height;
      overflow: hidden;
      border: none;
      list-style: none;
    }
    .jcarousel-item-horizontal {
      margin: $item_margin;
    }
    .jcarousel-next,
    .jcarousel-prev {
      display: block;
      width: $button_width;
      height: $button_height;
      background-image: $button_image;
    }
   
    /**
     *  Horizontal Buttons
     */
    .jcarousel-prev-horizontal {
      position: absolute;
      top: $button_top;
      left: $button_left;
      background-position: $button_left_position;
    }
    .jcarousel-prev-horizontal:active,
    .jcarousel-prev-horizontal:hover {
      background-position: $button_left_position_hover
    }
    .jcarousel-prev-disabled-horizontal,
    .jcarousel-prev-disabled-horizontal:hover,
    .jcarousel-prev-disabled-horizontal:active {
      cursor: default;
      background-position: $button_left_position_disabled     
    }
    .jcarousel-next-horizontal {
      position: absolute;
      top: $button_top;
      right: $button_right;
      background-position: $button_right_position;
    }
    .jcarousel-next-horizontal:active,
    .jcarousel-next-horizontal:hover {
      background-position: $button_right_position_hover;
    }
    .jcarousel-next-disabled-horizontal,
    .jcarousel-next-disabled-horizontal:hover,
    .jcarousel-next-disabled-horizontal:active {
      cursor: default;
      background-position: $button_right_position_disabled     
    }
  }
}

Then adding the necessary css is as simple as

@include horizontal-jcarousel(".view-ClientCarousel")


Be sure to setup some sensible defaults for the project, it's most likely that the button images won't change over a project - so you should setup your argument defaults to suit the given project.

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