Halp! I'm Stuck In Drupal 7!
http://slides.com/lawrencemiller/halp/live
Lawrence Miller
Team Lead at the American College of Physicians
@ldpm (twitter and drupal.org)
Should I Stay or Should I Go?
What we will (and will not) talk about
-
Patching contrib modules
-
Testing and Monitoring
-
Headless Drupal
Clean up the Admin UI by setting platform-specific variables from outside the Admin Toolbar
(also disable the "-UI" modules on prod, like Views UI and Rules UI)
But First:
Identify the Problem:
Use the devel module's variable editor to determine which variables you have set right now, and how they differ from platform to platform.
dev, stage, prod all have their own settings.local.php files
all platforms share one settings.php file
# DEV settings.local.php
$conf['my_setting'] = "My DEV setting";
# STAGE settings.local.php
$conf['my_setting'] = "My STAGE setting";
# PRODUCTION settings.local.php
$conf['my_setting'] = "My PROD setting";
# In your regular settings.php file
/**
* Include a local settings file if it exists.
*/
$local_settings = dirname(__FILE__) . '/settings.local.php';
if (file_exists($local_settings)) {
include $local_settings;
}
Chapter 1:
How to cope with absentee module maintainers
How to patch modules yourself in a manageable way:
- Open an issue for your problem
- Submit a patch for your issue
- Treat your patch as any other patch
Step 3: Treat your patch just like any other patch
drush patch-add
- you specify an issue node or a patch file
- drush downloads that patch and applies it...
- ...and updates the patches.make file
patches.make
- later when you run drush dl or drush up, drush will check this file and re-apply any patches it finds.
- An error message could mean you need to re-roll, or it could mean that the patch is now included.
Step 1: The Issue Queue
It's never the wrong thing to open an issue, even if it seems nobody is paying attention.
Step 2: Let's fix it ourselves
- Look in the "version control" tab of the module for instructions on how to clone the module and submit a patch.
- By submitting the patch to this issue, you're guaranteeing that the patch will be available at the same URL for at least as long as the Drupal Association Exists.
Chapter 2:
Find problems quickly by increasing your automated testing and monitoring coverage
Honorable Mention:
Visual Regression Testing
https://github.com/mojoaxel/awesome-regression-testing
Behat
Behat is a Behavior-Driven Development tool written for PHP. It allows you to write human-readable test cases using a language called gherkin.
By using the Drupal Behat Extension, installation is easy and drupal-specific tests are exposed.
https://www.drupal.org/project/drupalextension
CasperJS to log in as a user
casper.start("https://drupal7dev-ldpm.c9users.io", function() {
this.viewport(1200,900);
this.test.assertHttpStatus(200);
this.test.assertTitle('Drupal 7 Demo Site');
this.fill('form#user-login', {
'name': 'alice'
'pass': 'password'
}, true);
});
casper.then(function() {
this.test.assertTextExists('Welcome, Alice');
});
casper.run();
The same Test in gherkin
Scenario: Logging in as Alice
Given I am logged in as 'alice'
And I am on the homepage
Then I should see the heading "Drupal 7 Demo Site"
And I should see the text "Welcome, Alice"
Sample "blackbox" Feature
Feature: Regression Tests
In order to verify that a maintenance update did not change anything
As a site-runner
I want to run the following tests
Scenario: Finding the Site Name Heading in the Header
Given I am on the homepage
Then I should see the heading "Drupal 7 Demo Site" in the "header" region
Scenario: There shouldn't be errors on the homepage
Given I am on the homepage
Then I should not see the text "error"
Sample "Drupal API" Feature
Feature: Regression Tests
In order to verify that a maintenance update did not change anything
As a site-runner
I want to run the following tests
Scenario: Finding the Site Name Heading in the Header
Given I am on the homepage
Then I should see the heading "Drupal 7 Demo Site" in the "header" region
Scenario: There shouldn't be errors on the homepage
Given I am on the homepage
Then I should not see the text "error"
@api
Scenario: Run cron
Given I am logged in as a user with the "administrator" role
When I run cron
And am on "admin/reports/dblog"
Then I should see the link "Cron run completed"
Scenario: Create many nodes
Given "page" content:
| title |
| Page one |
| Page two |
And I am logged in as a user with the "administrator" role
When I go to "admin/content"
Then I should see "Page one"
And I should see "Page two"
Monitoring
- Nagios
- New Relic
- Um...
Making sure Drupal stays healthy
"I mean we're getting 200 OK"
Nagios monitoring module
https://www.drupal.org/project/nagios
Nagios monitoring module
Creating the Synthetic
New Relic Synthetic
var myUniqueKey = 'my_random_string';
var myUri = 'https://dx7o.ply.st/nagios'
var assert = require('assert');
var options = {
uri: myUri,
headers: {
'Accept': 'text/html'
},
qs: {
'unique_id': myUniqueKey
}
};
function callback (err, response, body){
console.log(body);
assert.ok(body.indexOf("CRON:OK") > -1, "Cron has not run recently");
assert.ok(body.indexOf("ADMIN:OK") > -1, "Admin reports an issue");
}
$http.get(options,callback);
Um...
- CasperJS / PhantomJS / Selenium
- Your own cron
<?php
define("NAGIOS_URL", "https://SITENAME/nagios");
define("UNIQUE_ID", "my_unique_id");
define("EMAIL", "me@example.com");
define("ALERT_SUBJ", "SITENAME needs your attention!");
$url = NAGIOS_URL . "?unique_id=" . UNIQUE_ID;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
curl_close($ch);
$stats = preg_split("/[,|;]/", $response);
$errors = array();
foreach ($stats as $stat) {
if (preg_match("/CRITICAL/", $stat)) {
$errors[] = $stat;
}
}
if (isset($errors[0])) {
$msg = "The following errors have been received:\n\n";
foreach ($errors as $error) {
$msg .= $error . "\n";
}
mail(EMAIL, ALERT_SUBJ, $msg);
}
Slack via Email
https://your-slack-address/apps
Chapter 3:
Using D7 as a data store for your modern front-end framework, a.k.a. "Headless Drupal"
- RSS
- views_datasource
- Services
- Custom feed
Pros:
- It's built in to both Drupal Core and Views
- Plenty of RSS parsers available
Cons:
- RSS Parsers and validators can be finicky
- No support for Angular's feed API or Yahoo's alternative anymore (check rss2json.com)
Option 1: RSS
Recommendation:
Only if you're already consuming other RSS feeds with your front-end tech
Pros:
- Pretty easy to install and configure
- You basically already know how to use it
- Has exactly the same flexibility as views in general
Cons:
- Has exactly the same weaknesses as views in general
Option 2: views_datasource
Recommendation:
Great for a number of interface patterns, especially Small Multiples (e.g. Pinterest cards)
Source: Drupal site
Create a View
- requires views_datasource
- enable views_json
- use "Page" display type and "JSON data document" formatter
- In the Formatter Settings, leave the top two fields blank (optional)
- Then just complete your view as normal
[
{
"title": "Aptent Elit Eu Exerci",
"Image": {
"src": "http:\/\/drupal7dev-ldpm.c9users.io:80\/sites\/default\/files\/field\/image\/imagefield_oktsx6.png",
"alt": "Gilvus nisl plaga refoveo verto."
},
"Nid": "1",
"Path": "\/node\/1"
},
{
"title": "Aliquip Jugis Minim",
"Image": {
"src": "http:\/\/drupal7dev-ldpm.c9users.io:80\/sites\/default\/files\/field\/image\/imagefield_YHvfJS.gif",
"alt": "Caecus consectetuer euismod ibidem illum vel vicis virtus."
},
"Nid": "28",
"Path": "\/node\/28"
},
{
"title": "Abbas Comis Conventio Loquor Lucidus",
"Image": {
"src": "http:\/\/drupal7dev-ldpm.c9users.io:80\/sites\/default\/files\/field\/image\/imagefield_mr78UD.png",
"alt": "Augue enim ratis zelus."
},
"Nid": "29",
"Path": "\/node\/29"
},
{
"title": "Cui Iustum",
"Image": {
"src": "http:\/\/drupal7dev-ldpm.c9users.io:80\/sites\/default\/files\/field\/image\/imagefield_4STyap.gif",
"alt": "Distineo feugiat genitus pneum utrum."
},
"Nid": "30",
"Path": "\/node\/30"
},
{
"title": "Enim Gilvus Interdico Jumentum Saepius",
"Image": {
"src": "http:\/\/drupal7dev-ldpm.c9users.io:80\/sites\/default\/files\/field\/image\/imagefield_9XCTua.gif",
"alt": "Distineo meus plaga quidne si."
},
"Nid": "32",
"Path": "\/node\/32"
},
{
"title": "Abico Aptent Hos Mos",
"Image": {
"src": "http:\/\/drupal7dev-ldpm.c9users.io:80\/sites\/default\/files\/field\/image\/imagefield_eNBlTV.jpg",
"alt": "Antehabeo dolor jugis magna quia tum."
},
"Nid": "33",
"Path": "\/node\/33"
},
{
"title": "Defui Paratus",
"Image": {
"src": "http:\/\/drupal7dev-ldpm.c9users.io:80\/sites\/default\/files\/field\/image\/imagefield_Psnez5.png",
"alt": "Acsi brevitas jus lucidus patria praemitto qui saepius tation."
},
"Nid": "35",
"Path": "\/node\/35"
},
{
"title": "Ludus Neque Patria Praesent Ullamcorper Vulputate",
"Image": {
"src": "http:\/\/drupal7dev-ldpm.c9users.io:80\/sites\/default\/files\/field\/image\/imagefield_TLgFoH.jpg",
"alt": "Amet eu fere nimis nutus qui saepius venio vero."
},
"Nid": "36",
"Path": "\/node\/36"
},
{
"title": "Uxor",
"Image": {
"src": "http:\/\/drupal7dev-ldpm.c9users.io:80\/sites\/default\/files\/field\/image\/imagefield_xnlZGQ.gif",
"alt": "Exerci luptatum sit sudo."
},
"Nid": "39",
"Path": "\/node\/39"
},
{
"title": "Abico Aliquip Conventio Distineo Enim Gilvus",
"Image": {
"src": "http:\/\/drupal7dev-ldpm.c9users.io:80\/sites\/default\/files\/field\/image\/imagefield_l81aC7.png",
"alt": "Caecus commodo diam erat incassum mos secundum sino utrum."
},
"Nid": "43",
"Path": "\/node\/43"
}
]
Sample JSON Output
Target: HTML + AngularJS
Target: HTML + AngularJS
The cards below "Hello, world!" are generated from the JSON API; everything else is just HTML and CSS
Target: JS file
angular.module('demo', [])
.controller('Photos', function($scope, $http) {
$scope.remote = "https://drupal7dev-ldpm.c9users.io";
$http.get($scope.remote.concat('/articles.json')).
success(function(data) {
$scope.photos = data;
console.log(data);
}).
error(function (data) {
$scope.data = "Request failed";
console.log("fail");
})
});
js/photos.js
Target: HTML
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
<script src="js/photos.js"></script>
...
</head>
<body>
...
<div ng-app="demo" ng-controller="Photos" class="books">
<div ng-repeat="x in photos" class="mdc-card book">
<a class="..." href="{{ remote }}{{ x.Path }}">
<div class="...">
<img src="{{ x.Image.src }}" alt="{{ x.Image.alt }}">
</div>
<div class="...">
<h2 class="...">{{ x.title }}</h2>
</div>
...
</div>
</div>
...
</body>
Pros:
- Easy
- Powerful
- Supports all CRUD operations, not just Read
Cons:
- Various forms of authentication can be complex to configure
Option 3: Services
Recommendation:
Perfect for exposing every single node on your site as JSON
Adding a new Service
(/api_demo/node/49.json)
Pros:
- Does whatever you need it to do
Cons:
- You gotta build it yourself
Option 4: Custom Feed
Recommendation:
If your use case doesn't fit neatly into Views or Services, Roll your own. That's why we're in Drupal.
<?php
function my_feed_menu() {
$items = array();
$items['feeds/my_feed/%'] = array(
'type' => MENU_NORMAL_ITEM,
'title' => 'My Custom Feed',
'description' => t('Custom JSON feed'),
'page callback' => 'my_feed_callback',
'page arguments' => array(1),
'access arguments' => array('access content'),
);
return $items;
}
function my_feed_callback($year = NULL) {
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'node')
->entityCondition('bundle', 'article')
->propertyCondition('status', NODE_NOT_PUBLISHED)
->propertyOrderBy('created', 'DESC');
if (is_int($year)) {
$first_minute = mktime(0, 0, 0, 1, 1, $year);
$last_minute = mktime(23, 59, 59, 12, 31, $year);
$query->propertyCondition('created', array($first_minute, $last_minute), 'BETWEEN');
}
$result = $query->execute();
if (isset($result['node'])) {
$news_items_nids = array_keys($result['node']);
$news_items = entity_load('node', $news_items_nids);
return drupal_json_output($news_items);
}
}
CORS
https://www.drupal.org/project/cors
- Cross-Origin Resource Sharing
- Only matters for front-end frameworks like AngularJS or REACT; your server-side consumers won't care
Thank You
http://slides.com/lawrencemiller/halp/
https://www.drupalcampatlanta.com/2018/sessions/halp-im-stuck-drupal-7
Lawrence Miller
@ldpm (twitter and drupal.org)
Halp!
By Lawrence Miller
Halp!
Tips for keeping ancient Drupal 7 sites running with less effort.
- 1,711