http://slides.com/lawrencemiller/halp/live
Lawrence Miller
Team Lead at the American College of Physicians
@ldpm (twitter and drupal.org)
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)
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 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;
}
How to cope with absentee module maintainers
It's never the wrong thing to open an issue, even if it seems nobody is paying attention.
Find problems quickly by increasing your automated testing and monitoring coverage
https://github.com/mojoaxel/awesome-regression-testing
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
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();
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"
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"
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"
Making sure Drupal stays healthy
https://www.drupal.org/project/nagios
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);
<?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);
}
https://your-slack-address/apps
Using D7 as a data store for your modern front-end framework, a.k.a. "Headless Drupal"
Only if you're already consuming other RSS feeds with your front-end tech
Great for a number of interface patterns, especially Small Multiples (e.g. Pinterest cards)
[
{
"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
The cards below "Hello, world!" are generated from the JSON API; everything else is just HTML and CSS
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
<!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>
Perfect for exposing every single node on your site as JSON
Adding a new Service
(/api_demo/node/49.json)
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);
}
}
https://www.drupal.org/project/cors
http://slides.com/lawrencemiller/halp/
https://www.drupalcampatlanta.com/2018/sessions/halp-im-stuck-drupal-7
Lawrence Miller
@ldpm (twitter and drupal.org)