Feed aggregator
Mediacurrent: Preparing for a Drupal Website Redesign
When was the last time your website had a facelift or you audited your Drupal modules, site security, SEO, etc?
We believe website redesigns fundamentally fall into two categories: the right and the wrong.
The right way delivers true value to an organization and the wrong way just consumes time and money for window dressing—which does nothing for your bottom line.
Recently a few of our teammates gathered our thoughts around Drupal website redesigns. As we talked through the different areas we coach our customers on, we decided to put them into one eBook.
"Preparing for a Drupal Website Redesign" is a collection of important suggestions your organization should consider before embarking on your next website design. Suggestions such as:
capdrupal a capistrano recipe for Drupal
Hi!
We are working on https://github.com/antistatique/capdrupal for some months now. This is a capistrano recipe to deploy drupal website. It works with multistage...
It's far from perfect but feedback more than welcome, please use the issue at github.
Code Enigma: Getting started with Test Driven Development - Choosing a Test Harness
Following on from the first blog post in this series, An Introduction to Test Driven Development, Chris introduces the test harness so strap yourself in!
My first experience of work "in the real world" came through work experience. I was lucky enough to get one of only two placements with IBM at Havant and my assigment was to build to specification the wiring loom used to connect the IBM 3745/3746 communications controller to its test harness. These units had to be regularly and thoroughly tested throughout production and so periodically each unit was hooked up to a test harness by means of the aforementioned wiring loom so that a range of tests could be executed against the unit and pass/fail reports and data collated. My time at IBM provided a fabulous insight into engineering/testing and firmly cemented in my mind the concept of a test harness.
In software development, a test harness provides exactly the same functionality, it provides a means of executing tests against software and giving feedback on whether the software is behaving as expected.
The specific test harness you choose will likely vary from project to project and is dependant on a number of factors, the coding language of the project and experience of the developers and test engineers involved may have an impact on the decision making process, a Google search for "test harness for language x" will no doubt turn over a number of options for each language and whilst there’s no hard and fast rule that your test suite and project code language must match, the skillset of the team surely will play a part.
Another major consideration should be around the types of test you want to execute, (unit, integration, system, acceptance etc) as some test harness frameworks lend themselves more towards one or a subset of types. You can of course use more than one test harness if it makes sense for your project with each providing different elements of a test plan strategy: unit tests running on every build and full smoke and functional tests running on release for example.
Consideration also needs to be given to the development methodology being applied - perhaps PHPUnit is a best choice if a purely Test Driven approach is adopted, on the other hand, Behat is a clear winner for Behaviour Driven Development.
I'll leave the last key point for consideration till the end! First, let's briefly review 4 popular test frameworks.
SimpleTestFor those of us working with PHP (and specifically Drupal), there are a number of options. Coupled closely with Drupal is SimpleTest which was moved to core in Drupal 7. It's modeled on the open source SimpleTest unit test framework so those familar with its suite of assertions will be comfortable and will get the extra benefit of helper methods such as drupalLogin(), drupalLogout(), drupalCreateNode() and drupalCreateUser() for establishing fixture[1].
Here's an example of a user login test in SimpleTest taken from the user module and modified slightly for clarity:
<?php /** * User login test. */ class UserLoginTestCase extends DrupalWebTestCase { public static function getInfo() { return array( 'name' => 'User login', 'description' => 'Ensure that login works as expected.', 'group' => 'User', ); } /** * Test that a user can login. */ function testUserLogin() { $user = $this->drupalCreateUser(array()); $this->drupalLogin($user); } }As you can see, tests are expressed in PHP code and SimpleTest provides a number of assertions that can be used.
I won't go into much detail here about SimpleTest as many good blog posts already exist about its use and limitations (duration of test run being a major issue which SimpleTest Clone seeks to address, some have had success with switching to MyISAM for tests and others, along with Drupal Quality Assurance have adopted ramdisk with a mixed outcomes it seems.)
Code Enigma offers high performance Drupal hosting with built in support for running automated tests, see our http://hosting.codeenigma.com/faq for more information or get in touch if you'd like to talk it through.
PHPUnitPHPUnit is a popular open source PHP test framework created and maintained by Sebastian Bergmann which, from its name, is a good choice for unit testing although you can combine it with other libraries, Guzzle and Selenium Server for example enabling browser automation for functional and acceptance testing.
I personally find PHPUnit invaluable and use it on a daily basic, tests are easy to read, write and are quick to run if you're actually doing true unit testing. Of course, that in itself raises a challenge when working with Drupal where dependencies are commonly requested rather than injected. Drupal 8 is heading in the right direction with Mark Sonnabaum leading the introduction of PHPUnit tests in the upcoming release. I recommend watching the video of his recent Drupalcon Portland session on the subject.
A really basic example PHPUnit unit test:
<?php /** * Adds two numbers and returns the result. */ function add($a, $b) { return $a + $b; } class AddTest extends PHPUnit_Framework_TestCase { public function testadd() { $this->assertSame(3, add(1, 2)); } }Once again, tests are expressed in PHP code with PHPUnit providing perhaps a slightly richer set of assertions than you get with SimpleTest.
BehatCreated by Konstantin Kudryashov, Behat is the PHP Behaviour Driven Development enabler for PHP projects - it enables composition of human readable tests (in Gherkin) that Behat turns into executable acceptance tests. This is the beauty and strength of BDD (and Behat), together they provide a means whereby business owners, developers, project managers and testers can frame requirements in a common language, the language of the business domain and from there derive a suite of acceptance tests that indicate delivered value.
The DrupalExtension module provides additional step definitions for some of the basic operations normally performed on a Drupal site, in addition it provides support for regions defined in the theme layer for more accurate querying of the user interface.
I recently gave a talk about BDD and Behat at the North West Drupal user group - you may find my slides and code helpful.
An example feature and scenario:
Feature: Login As a visitor I want login So that I can manage my account Scenario: A user enters their credentials and logs in successfully Given I am on "/" And I fill in "admin" for "name" And I fill in "letmein" for "pass" When I press the "Log in" button Then I should see "My account"Expressed in a structured language called Gherkin, Behat along with the Mink extension can take this feature and turn it into an executable acceptance test without the need to write any PHP code.
CodeceptionCodeception is a PHP framework created and maintained by Michael Bodnarchuk. It enables acceptable, functional and unit testing which are created by expressing context, actions and expected behaviour in simple expressive PHP code.
At the time of writing, Codeception has switch on module support for a number of PHP projects such as Symfony, Laravel, Yii and ZF but unfortunately no-one has written the equivalent for Drupal yet. Keep an eye on Mike Bell and Paul Byrne who have both written very good blog posts about Codeception, Testing with Codeception and Drupal Projects and More testing with Codeception and Drupal projects
An example Codeception test:
<?php $I = new WebGuy($scenario); $I->wantTo('ensure that frontpage works'); $I->amOnPage('/'); $I->see('Home');Tests are composed in simple expressive PHP code.
ConclusionIt's perhaps now apparent that whichever test framework you choose, the end result is much the same: tests are executed either directly against code (unit testing) or against the site itself (functional/acceptance testing through browser automation) and the failures are reported in some way. The key variant these frameworks offer, and in my book, the most important element for consideration is the input. Answering the following pivotal question helps:
How best can tests be expressed for this project and the people involved in its delivery?
Although not explicit, you might want to include the client when you consider the people involved!
1. A fixture is a known application state which tests will run against, establishing a fixture is the work you do to get to that know state, perhaps the creation of a user for example.
Related Service Areas: DevelopmentTeaser: TDD is a daunting prospect for the newcomer, but Chris Maiden explains it's not so hard to get goingCategories: CommentConsultancyDevelopmentDrupal PlanetPrimary Category: DevelopmentTags: test driven developmentagiletestingsoftware qualitysoftware engineeringtddKnackForge: Drupal 7 Form API - Using #states on #markup field
An essential feature of a dynamic site would be to let its users add some contents to it. Often this is done using HTML forms. Drupal offers a rich and relatively simple way to use API for building forms for its Developers. In Drupal parlance it is referred as Form API (or FAPI).
Google Plus One Linkedin Share Button Tweet Widget Facebook LikeWeb Omelette: Cool module: Responsive Navigation
Responsive Navigation is a cool Drupal module that incorporates the responsive-nav.js plugin into your site. In this article I am going to show you how to install and use this module.
drunken monkey: Creating helpful documentation
Good documentation helps both a module's maintainers and its users, and is quintessential for the success of at least more complex modules. That's why you, as a module maintainer, should not put this (admittedly rather unpopular) task off any further but write some helpful documentation rather sooner than later.
On a related note, I just set a good example by finally expanding the handbook documentation for the Search API, and adding some for the Solr search module as well. Go check it out!
Why good documentation paysWhen we talk about documentation, there are of course two sub-categories: documentation for users (site builders/administrators) and for developers. There are different reasons for having both types of documentation:
User documentationUser documentation is very important as a first step because it lets you both attract new and keep old users. Users who first hear of your module and take it out for a spin will usually have little tolerance for things not working as expected or for having to figure out everything by themselves. Sure, supporting such (potential) new users in the issue queue and on IRC help, but having a clear "How to" tutorial, maybe with a list common pitfalls, just makes it so much easier for new users. If someone just wants to try out your module, not sure whether they would have any benefit from it, there will be little incentive to actually try to find help if things go wrong, or if they cannot easily find instructions on what to do after enabling your module.
As a nice side effect, proper documentation will lead to less support requests in the issue queue or on IRC, thus even saving you time in the long run. If you keep the documentation up to date with support requests you've seen more often, users should find what they're looking for there in most cases – and if they don't find the documentation and still create a support request, a simple link to the documentation will suffice.
In some cases, there is already some documentation directly in the source code, either as a README.txt or as Advanced help integration, maybe even plenty of it.
Still, in terms of being able to find it and, e.g., also using it before downloading a module to see what you can do with it, handbook pages on Drupal.org still have the advantage. Furthermore, having documentation in the handbook significantly lowers the barrier for users of your module to get involved and extend the module's documentation (or to keep it up to date).
As long as there is no handbook documentation of your module at all, I'd wager the chances of someone else going and creating some are rather slim.
Just like user documentation, developer documentation helps you attract new and keep old developers. Developers, who can help with patches in the issue queue, implement new features or create and maintain extension modules of your module. In my case, with the Search API being primarily a framework for creating search-related modules, this is of course especially important – but having people active in the issue queue who can fix the bugs they discover and implement the features they request themselves, who can review your more complex patches, etc., is quite valuable for nearly any module out there. Just by someone else reading through your code (if it's a more complex module), a few rough edges will pop up which would sooner or later have led to problems for someone (perhaps you yourself, as the one who then should fix them).
For developer documentation, naturally, source code comments, a proper .api.php file, etc., are very important. Still, none of these can really give new developers the “great picture” of how your module works. They just document single functions, methods, classes and hooks, but there is no general place for documenting how it all fits together. For that, again, I think handbook pages are the way to go.
New/Improved Search API documentationSo, after all the grand words, have I myself lived up to them? Well, no, or at least only very recently. For several years now, there was only some basic documentation for the Search API in the handbook, for the first year or so there was none at all.
However, being asked the same questions over and over in support requests, and recognizing all of the above arguments, I finally got round and significantly expanded the existing documentation:
- There are now a Common pitfalls and a Frequently asked questions section, listing support requests I've received more often over the last years.
- The developer documentation section is completely new. There's still a lot to add, but the two (in my opinion) most important parts are covered: an in-detail explanation of the Search API's architecture and workflow, and documentation about adding custom item types / data sources.
- I moved some of the text from the project page into the handbook – concretely, the extension modules and the related materials. This allows others to easily add to those lists as well, and additionally brings down the length of the project page to just a little over two screen pages.
- Lastly, there is finally also some handbook documentation for the Search API Solr search module, including advice on how to customize the Solr configuration.
Of course, good documentation is hardly ever finished, and there is still a lot to be added, but I do think that this is at least a sound base now so that most users will find what they want to know.
Help still wantedWhile, as the creator and maintainer, I'm certainly quite knowledgable concerning all Search API-related topics, I'm also for the very same reason one of the worst people to judge what needs documenting, what could be unclear, how things are best explained, etc.
Therefore, if you have dealt with the Search API on any level in the past or – better still – want to look into it in the near future, please take a look at the new (and old) documentation, see if everything is clear, what might be missing, etc., and tell me your thoughts in the issue queue.
Or, of course, just jump in and edit or expand the documentation yourself! It's a great place to start contributing, helping others avoid potential problems you've encountered.
Some more documentation projects I have in mind for the nearer future include creating an example module for implementing a custom datasource (possibly by integrating Search by Page with the Search API); and maybe creating a new, up-to-date version of the old Search API screencast. If you've got other suggestions, please leave them here as a comment or create an issue for them.
Image credit: Johannes Jansson/norden.org
Tags: Planet DrupalSearch APISearch API SolrDocumentationKároly Négyesi: Drupal 8 progress from my / MongoDB perspective: update #20
There are a lot of great things happening! Let's first see those that are already in: the config system got back to a saner track by removing the partial import capability and only allowing import of full config trees. This removed manifests which, honestly, I never truly grokked. Also, config import and sync were converted to pluggable services earlier so if someone can figure out how to do partials better then they can do. There was an epic profiling effort made by the Twig team to get the conversion committable -- and then Alex Pott honored them by committing patches one by one instead of rolled together to give them credit. Same with the views rename method patches. Big, big kudos to Alex for this!
Database connections are now serializable. As MongoDB doesn't use PDO, this is only important to us in the way the Database class has been refactored to allow for this.
Entity controllers now can have their dependencies injected (what about entities themselves? see below). Although batch v2 didn't happen, the batch storage is now pluggable (and soon the batch specific tables will be gone completely as msonnabaum points out that the expirable keyvalue storage is fit for it).
On a performance front, let's note that contextual links are now AJAX, making them compatible with render caching. The issue was filed in 2010 and I filed it as "this will never be done although it's rather necessary", so big kudos to everyone fixing it.
Firing hooks during major upgrades were always a problem and we discouraged it but now this will throw an exception (this was mostly my work). This is the second cornerstone on making the Drupal 7->8 upgrade path way more resilient than before, the first one are tests for use cases that were untestable before. Probably it'd been better if migrate happened this cycle, but it didn't so we cook with whatever ingredients we have.
The evented branch of the entity refactor is now dead and won't happen but a lot of that code now lives in the widely accepted "let's move as much logic as possible from storage controllers to entity classes" patch. It even passed once but due to the user entities conversion to the next generation controller it currently fails but I expect it to be fixed this weekend and it's possible it'll get committed soon. This will make replacing entity storage controllers much easier and supporting contrib entities much easier too.
Meanwhile, entity classes themselves are being changed to not be plugins. This makes some sense because, well, there's not much pluggability here you would want to do. I believe this leads to dependency injection of plugins which will be useful now that most of the logic is moving there.
Ps. Let me congratulate two people here. You can't even find msonnabaum among the Drupal 7 contributors core and he is rapidly becoming one of the shaping architects of Drupal, quite for the better. Tim Plunkett of the Views in Core team, the unstoppable coding machine, who can somehow balance like five ongoing patches over 100kb, had only 23 core mentions in the Drupal 7 cycle. I am so glad to see others taking lead.
.VDMi/Blog: Integrate Drupal with Zurmo
Zurmo is the open source, gamified, mobile and social Customer Relation Management suite, build on top of YII PHP Framework and RedBeanPHP. The development is test driven and has a fast release cycle. Zurmo is feature rich, without overloading your screen with options. The best part of Zurmo is that it is gamified, you earn points while doing your work. That makes the usage of Zurmo fun... you simply want to use it... and that solves one of the most heard complains from management of a company: "We have an expensive CRM, but no one uses it".
A Drupal ModuleWe developed a Drupal module to integrate the Drupal Webform Module with Zurmo. The module can be downloaded from the Zurmo project page on the Drupal website. The module builds a connection between Drupal and Zurmo, that utilize the webservices in Zurmo. With a sub-module you can send your webform submissions to Zurmo and create Contacts or Leads in Zurmo. In Zurmo you can send emails with the workflow tool to notify the right employee that a new Lead has come in.
The module is currently in development state. That means that it can be used, but is not complete yet. The Webform sub-module works and is tested in reallife. It needs more testing, more documentation to be released in Beta. In the comming months we will develop further to integrate Commerce (webshop) and Feeds (fetch any object from Zurmo and import it into Drupal).
You can help with the development of the Zurmo module by:
- Test the modules.
- Write documentation.
- Develop a sub-module.
- Supply patches.
Use the Drupal Issue Queue for this project to add your work to the module. You can also request to become co-maintainer for the Zurmo module for Drupal. Help us and let us build together a stable module, so that no one ever needs to copy/paste data!
Badzilla: Drupal 7 - Setting Up Piecemaker 3D Slider with InnoCompany Theme
Subsequent to my earlier blog on installing and setting up the commercial InnoCompany Drupal 7 theme, it is now time to do something useful - set up the Piecemaker 3D slider which is based on Flash animation, with an XML configuration file. Thankfully a lot of the work has already been bundled into the InnoCompany theme and there isn't that much to do. Principally the slider images, captions and links are included in theme configuration, and there are 10 available placeholders which means the carousel may have up to 10 images.
High Rock Media: Drupal 7 Preprocess Tricks: Tips From a Themer
In the course of a day as a Drupal Themer, I need to code a wide variety of functionality into a given site I'm working on. I try to follow Drupal best practices as well and this usually means implementing theme preprocess functions; these become key to a Themer's toolbox. This article is a selection of snippets I've picked up along the way that might help others out. When I first started building with and theming Drupal several years ago, at first I did not see the value of preprocessing but it's turned out to be quite valuable.
Add JS or CSS with a Pattern Match / WildcardThis method is ideal if you want to add special or custom JS or CSS files to a specific path with a pattern match or wildcard. For example I might have unlimited URLs associated with a Taxonomy with patterns such as:
/portfolio/cars/portfolio/trains
/portfolio/architecture
/portfolio/foo
… but then maybe I want to also add this code to some individual pages not associated with the Taxonomy. I can do something like this with a preprocess HTML function.
function MYTHEME_preprocess_html(&$vars) {$path = drupal_get_path_alias();
$pattern = "portfolio/*\nfoo-page";
if (drupal_match_path($path, $pattern)) {
drupal_add_js(drupal_get_path('theme', 'MYTHEME') . '/js/jquery.foo.js', 'file');
}
}
As you can see from the above code, we are adding JS to anything under the path or /portfolio/ but also to a page called foo-page. Note the newline character between the two pattern matches as well as the * which serves as a wildcard. We are also using the Drupal API function drupal_get_ path_alias which returns the alias of an internal path. You can imagine the possibilities using this method which could potentially cut back on the number of scripts being aggregated on any given part of your site.
A CaveatThe one caveat using this method is that you'll want to be sure to add a test to your script instantiation. So for example, in my scripts file, if I am instantiating the above script, I want to be sure to add an if clause, otherwise you run the risk of calling the script when it does not exist on various pages which would in turn throw a nasty error. So you'd want to do something like so:
if($().foo) {// your script foo here…
} Weight, Scope and Group
In certain cases, you need to load a script last or set a specific weight to place it at a specific point in relation to other scripts that are loading. This can be achieved by "weighting" and "scoping." Scope has two possible constants, header and footer which are self-apparent, you can either load the script in the head of the page or in the footer after everything else; there's lots of advantages to this in regard to Javascript. For grouping, typically a themer is going to use JS_THEME but there's also two other constants available, JS_DEFAULT and JS_LIBRARY which would commonly be used within a module context. Below is a sample
function MYTHEME_preprocess_html(&$vars) {drupal_add_js(path_to_theme() . '/js/foo.js',
array(
'group' => JS_THEME,
'preprocess' => TRUE,
'scope' => 'footer',
'weight' => '9999',
)
);
}
Note also that preprocess is set to TRUE as we want this internal script to be aggregated. However, there's some cases where you'd set this to FALSE perhaps in the case of an external JS file. The high weight number would most likely make it load last but it's a good idea to test this with aggregation off on your dev site so you can actually see where and how it loads. Play with the number if you don't get the desired result.
CSS ParametersYou can also weight CSS and it has three group constants available as per the Drupal API page:
- CSS_SYSTEM: Any system-layer CSS.
- CSS_DEFAULT: (default) Any module-layer CSS.
- CSS_THEME: Any theme-layer CSS.
It should be pointed out that the groups themselves have weight so you can really fine tune this using a numbered weight as well as a named group 'weight'.
Browsersif you still develop for older versions of Internet Explorer (gasp!), there's a nice add_css Parameter called browsers which combines with the drupal_pre_render_conditional_comments function. This is also a theme preprocess_html function. For example, if you want to render an IE8 fixes stylesheet conditionally, you could do something like this:
function MYTHEME_preprocess_html(&$vars) {// Add IE 8 fixes style sheet.
drupal_add_css(path_to_theme() . '/css/ie8-fixes.css',
array(
'group' => CSS_THEME,
'browsers' =>
array(
'IE' => 'lte IE 8',
'!IE' => FALSE),
'preprocess' => FALSE));
}
Here we say that we want this to be a conditional stylesheet for less than or equal to IE8 and false for all others and this would render as:
<!--[if lte IE 8]><link type="text/css" rel="stylesheet" href="http://example.com/sites/all/themes/MYTHEME/css/ie8-fixes.css" media="all" />
<![endif]-->
There's plenty of other parameters, options and constants for adding CSS and JS within theme preprocessing functions and I suggest checking out the Drupal API pages, links referenced below. There's your tips from a themer, I'll be back sometime soon with more!
Tags- Drupal
- Preprocess
- PHP
- CSS
- Tutorial
- Drupal Planet
Paul Booker: Write Drupal Logs to Rsyslog
Install rsyslog on your web server (should already be installed)
Enable the syslog module in Drupal
To allow Drupal to log to rsyslog, you have to enable the Drupal syslog module. Syslog is included with Drupal core.
Configure the Drupal module
In admin/config/development/logging , you can select one of the following prefixes for your website:
LOG_LOCAL0
LOG_LOCAL1
LOG_LOCAL2
LOG_LOCAL3
LOG_LOCAL4
LOG_LOCAL5
LOG_LOCAL6
LOG_LOCAL7
If you log one site, it really doesn’t matter which one you choose. If however you have multiple sites running on the same machine, and you want each site to log to a different file, the selection does matter. Eg: site A logs to LOG_LOCAL0, site B logs to LOG_LOCAL1, etc.
Configure rsyslog
vim /etc/rsyslog.d/50-default.confadd the line:
local0.* /var/log/drupal.logThis will log all local0 logs to /var/log/drupal.log
If you have selected local1 in the previous step, you have to replace “local0” by “local1″ in the configuration line, etc. You can name your log file different from “drupal.log”, in fact you can choose any name that hasn’t been taken yet.
Restart rsyslog
service rsyslog restartYou should now cause an error (eg. 404) and see if /var/log/drupal.log has been created and gets filled. If you're having problems check if /var/log/messages gets filled with Drupal log messages. If it does recheck the previous steps and see if you made any mistakes.
Disable dblog
At this point, Drupal will log to both the log system and the regular dblog. You can simply disable the module “database logging” in admin/modules .
Paul Booker: Write Drupal Logs to Rsyslog
Install rsyslog on your web server (should already be installed)
Enable the syslog module in Drupal
To allow Drupal to log to rsyslog, you have to enable the Drupal syslog module. Syslog is included with Drupal core.
Configure the Drupal module
In admin/config/development/logging , you can select one of the following prefixes for your website:
LOG_LOCAL0
LOG_LOCAL1
LOG_LOCAL2
LOG_LOCAL3
LOG_LOCAL4
LOG_LOCAL5
LOG_LOCAL6
LOG_LOCAL7
If you log one site, it really doesn’t matter which one you choose. If however you have multiple sites running on the same machine, and you want each site to log to a different file, the selection does matter. Eg: site A logs to LOG_LOCAL0, site B logs to LOG_LOCAL1, etc.
Configure rsyslog
vim /etc/rsyslog.d/50-default.confadd the line:
local0.* /var/log/drupal.logThis will log all local0 logs to /var/log/drupal.log
If you have selected local1 in the previous step, you have to replace “local0” by “local1″ in the configuration line, etc. You can name your log file different from “drupal.log”, in fact you can choose any name that hasn’t been taken yet.
Restart rsyslog
service rsyslog restartYou should now cause an error (eg. 404) and see if /var/log/drupal.log has been created and gets filled. If you're having problems check if /var/log/messages gets filled with Drupal log messages. If it does recheck the previous steps and see if you made any mistakes.
Disable dblog
At this point, Drupal will log to both the log system and the regular dblog. You can simply disable the module “database logging” in admin/modules .
thedavidmeister.info: Deconstructing the Flippy module to write a simple custom node pager
Mike Crittenden recently wrote an article on an "Invented Here" aversion in the Drupal community. One of the main points made there is that often a module will do what you want, but also introduce new things that you actually don't want that may outweigh or negate the good in the long term. It could be as innocent as a form alter adding a description to an existing form, or some CSS intended to make a module seem more appealing "out of the box", but if it doesn't match what your designer handed you (my experience with designers is that the chance of a design matching the styles of a contrib module the designer hasn't explicitly referenced is somewhere under one in a million) it means more work, and there's a huge range of how much more work it means depending on the current requirements and how the module is written.
A huge number of modules on d.o are very small (in terms of lines of code) and only aim solve a single, very specific use-case. I often find that, after reading over the code there's only 1-5 functions that "do the work" and the bulk of the code is just implementing Drupal hooks to provide a nice UI, default settings, page callbacks, permissions, etc...
Given that:
- installing a new module, any module, has some probability of introducing something new that I will have to painstakingly remove again with my own overrides
- this probability seems to be roughly inversely proportional to the number of users the module has listed on d.o and how actively maintained it is
- smaller modules that have less frequent commits are often (not always, obviously) written by less experienced developers and while they may do the job they're more likely to introduce unwanted side effects too
- modules that promise to enhance the front-end/theme of your site are more likely to introduce a public facing side effect than something like the Google Analytics integration module, for example
- Drupal is open source and I can read/write PHP
- Many decent modules are internally structured to first define their own private API before leveraging it to provide the functionality required
...when I see a new module that promises to add a new block or node links or javascript widget I approach it with a degree of caution and find myself wondering "Could I just copy/paste this module's API and then do/render exactly what I actually want directly?".
The Flippy moduleCheck out the Flippy module. It's a really simple way to get first/previous/next/last style pagination on your nodes based on node creation dates and the content type of the current node.
The module code is pretty easy to read over (the main module file is under 500 lines of code), so I thought it would be a nice example on how to deconstruct a Drupal module into "Internal API + Drupal API implementations".
Let's say that for our requirements we want exactly what Flippy provides but we also want:
- Our pagination functionality to just provide an associative array of nids rather than a fully rendered pager.
- Our pagination to be based on updated date rather than created date.
- Persistent caching of our calculated pager info on a per-node basis so we can avoid hitting the db wherever possible.
We're unlikely to find an existing contrib module that satisfies these requirements exactly without searching pretty hard, so let's see if we can just adapt Flippy to our needs.
Here's the list of all the functions in flippy.module with a summary of what they do:
Drupal Hooks:
- flippy_theme: Implements hook_theme() to make the flippy.tpl.php file work.
- flippy_form_node_type_form_alter: Implements hook_form_FORM_ID_alter() to add extra per-content-type settings for Flippy.
- flippy_field_extra_fields: Implements hook_field_extra_fields() to expose Flippy as a "pseudo-field" (which means you can use it on the "manage display" page).
- flippy_node_view: Implements hook_node_view() to actually render Flippy's "pseuedo-field" as a node is being viewed.
- template_preprocess_flippy: Implements hook_preprocess_HOOK() to prepare variables for use in flippy.tpl.php template files.
- flippy_block_info: Implements hook_block_info() to define a block for Flippy pagers.
- flippy_block_view: Implements hook_block_view() to be called by Drupal when a Flippy pager block is to be rendered.
Flippy internal API
- flippy_build_list: Generates a statically cached array of Flippy links, given a fully loaded node object.
- flippy_pager_block: Render the Flippy pager block. Called by flippy_block_view().
- _flippy_use_pager: Determine if a pager should be rendered on the current node based on settings for that content type.
- _flippy_add_head_elements: Handles adding pagination header links.
As you can see, the majority of the functions here are Drupal hooks: preparing variables for templates, exposing the pager to the Field API, implementing theme templates, defining and rendering blocks, etc.
Because we don't need a UI or any particular rendering implementation to satisfy our requirements we only need to appropriate one function here, flippy_build_list() which looks like this:
/** * Function that builds the list of nodes */ function flippy_build_list($node) { $master_list = &drupal_static(__FUNCTION__); if (!isset($master_list)) { $master_list = array(); } if (!isset($master_list[$node->nid])) { // Create a starting-point query object $query = db_select('node') ->fields('node', array('nid', 'title')) ->condition('nid', $node->nid, '!=') ->condition('status', 1) ->condition('type', $node->type, '=') ->range(0, 1); $first = clone $query; $list['first'] = $first ->condition(db_or() ->condition('created', $node->created, '<') ->condition(db_and() ->condition('created', $node->created, '=') ->condition('nid', $node->nid, '<'))) ->orderBy('created', 'ASC') ->execute()->fetchAssoc(); $list['current'] = array( 'nid' => $node->nid, 'title' => $node->title, ); $prev = clone $query; $list['prev'] = $prev ->condition(db_or() ->condition('created', $node->created, '<') ->condition(db_and() ->condition('created', $node->created, '=') ->condition('nid', $node->nid, '<'))) ->orderBy('created', 'DESC') ->execute()->fetchAssoc(); $next = clone $query; $list['next'] = $next ->condition(db_or() ->condition('created', $node->created, '>') ->condition(db_and() ->condition('created', $node->created, '=') ->condition('nid', $node->nid, '>'))) ->orderBy('created', 'ASC') ->execute()->fetchAssoc(); $last = clone $query; $list['last'] = $last ->condition(db_or() ->condition('created', $node->created, '>') ->condition(db_and() ->condition('created', $node->created, '=') ->condition('nid', $node->nid, '>'))) ->orderBy('created', 'DESC') ->execute()->fetchAssoc(); $random = clone $query; $list['random'] = $random ->orderRandom() ->execute()->fetchAssoc(); $master_list[$node->nid] = $list; } return $master_list[$node->nid]; } "Just the good bits" - Meeting our requirementsprovide an array of structured data rather than a fully rendered pager.
Because Flippy provides its own API before using it, in the form of flippy_build_list() our first requirement is really easy to satisfy - this function already returns a nice, simple array of data based on the queries it runs. All we need to do is renamespace the function to MODULENAME_get_pager_info() or similar and put it in one of our custom modules and we've done step 1.
pagination to be based on updated date rather than created date.
For this we have to update the queries within flippy_build_list() to sort by updated date rather than created date.
If you haven't used the OOP database queries available in D7 then have a look at Berdir's conversion guide to get you up to scratch on the differences between DBTNG queries and the "old style" db_query("SELECT * FROM ....", $var1, $var2, ...) Drupal db functions.
Firstly, Flippy creates a "base" query in $query with some conditions shared by all the pagination links:
- Get the nid and titles for the nodes returned by our query.
- Make sure not to return the node we're currently looking at.
- Only return published nodes.
- Only return nodes of the same content type as the node we're currently looking at.
- Only ever return one node for each query.
Flippy then clones this base query 6 times then extends (and actually executes) each new query object with the conditions specific to building that link:
- First: Get the first node created before this node, sorted by created date ascending.
- Previous: Get the first node created before this node, sorted by created date descending.
- Current: This node.
- Next: Get the first node created after this node, sorted by created date ascending.
- Last: Get the first node created after this node, sorted by created date descending.
- Random: Get a random node.
For first/previous/next/last we also check the order of nids in the case that the created times are identical.
All we need to do to achieve sorting by updated date is to edit the conditions and the sorts in the overrides in a consistent way.
We should end up with something like this (modifying "created" to "changed"):
function MODULENAME_get_pager_info($node) { $master_list = &drupal_static(__FUNCTION__); if (!isset($master_list)) { $master_list = array(); } if (!isset($master_list[$node->nid])) { // Create a starting-point query object $query = db_select('node') ->fields('node', array('nid', 'title')) ->condition('nid', $node->nid, '!=') ->condition('status', 1) ->condition('type', $node->type, '=') ->range(0, 1); $first = clone $query; $list['first'] = $first ->condition(db_or() ->condition('changed', $node->changed, '<') ->condition(db_and() ->condition('changed', $node->changed, '=') ->condition('nid', $node->nid, '<'))) ->orderBy('changed', 'ASC') ->execute()->fetchAssoc(); $list['current'] = array( 'nid' => $node->nid, 'title' => $node->title, ); $prev = clone $query; $list['prev'] = $prev ->condition(db_or() ->condition('changed', $node->changed, '<') ->condition(db_and() ->condition('changed', $node->changed, '=') ->condition('nid', $node->nid, '<'))) ->orderBy('changed', 'DESC') ->execute()->fetchAssoc(); $next = clone $query; $list['next'] = $next ->condition(db_or() ->condition('changed', $node->changed, '>') ->condition(db_and() ->condition('changed', $node->changed, '=') ->condition('nid', $node->nid, '>'))) ->orderBy('changed', 'ASC') ->execute()->fetchAssoc(); $last = clone $query; $list['last'] = $last ->condition(db_or() ->condition('changed', $node->changed, '>') ->condition(db_and() ->condition('changed', $node->changed, '=') ->condition('nid', $node->nid, '>'))) ->orderBy('changed', 'DESC') ->execute()->fetchAssoc(); $random = clone $query; $list['random'] = $random ->orderRandom() ->execute()->fetchAssoc(); $master_list[$node->nid] = $list; } return $master_list[$node->nid]; }We can now call this function wherever we have a loaded node object to get a list of simple pagination links and we didn't need to install a whole new module to achieve this.
Persistent caching of our calculated pager info on a per-node basis.
Flippy doesn't provide this at all so we have to implement it ourselves whether we want to use Flippy or our custom adaption.
I'm actually not going to show the code required for this here, as Lullabot already have the best introductory guide I've ever read on the topic so I don't feel the need to duplicate their efforts.
The point here is less about showing exact code examples and more that if you're on a deadline with a specific brief it would be easier (maybe not "better", but definitely easier) to just write the extra caching hooks around this one custom function in your own module than try to get a feature patch committed to a contrib module (Flippy is in "Maintenance fixes only" mode).
The end resultSo let's have a quick look at what we gain and lose by taking the "DIY" approach using an existing module as an example rather than a framework/turnkey solution.
Pros- We have one less module to keep track of. This is most important for iterative projects where more modules means more maintenance overhead in the long term and projects where every little bit of performance counts and we want as few modules in our site as possible.
- We may learn a lot and improve our own module writing skills in the process.
- We're much less likely to find ourselves implementing overrides and ripping stuff out that doesn't match a design/brief and spend more time building things we actually want.
- We have more control over the behaviour of the final product.
- Give yourself a chance to avoid the most common harmful shortcuts made by contrib module developers like overuse of variable_get() and variable_set() (has a proven negative performance impact) or half-finished install/update hooks.
- This approach can get you to the end result much faster in many cases (module is simple and would need a relatively large amount of overrides to make it behave).
- In the case where the module doesn't provide a theme implementation or something that's easily alterable, we have more control over the markup when we finally render it.
- Don't have access to security updates from the module maintainer without applying them yourself (that said, if you're writing any custom code anywhere you either have security holes in your code already or you have an idea of how to avoid them).
- This approach can be much slower than just using the module and implementing your own hooks to tweak it if the module doesn't have a decent API structure that can be teased out into separate components like Flippy can, or if the module is larger than a thousand lines or so.
- If the module has some big issues that you are capable of filing a patch to help fix then the community would obviously benefit from such a contribution as much as you would.
- Obviously we lose any functionality the module would have provided that we aren't porting/adapting to our custom codebase. In this Flippy example we lose the automatic addition of pagination links to our page <head> when we finally render the links, that could have helped our site's SEO.
In summary, I'm definitely not saying to always "do this" or "do that". I just wanted to show an example of going through a process that highlights an option that I have found new developers often don't realise they have, or they do realise it's something they could do but they overstate the amount of difficulty/effort involved in adapting some existing body of work vs. using it directly. The important thing for us developers is that we're always mindful of what's really in our toolkit and when/how to apply each of our tools to the task at hand.
Syndicate: planet drupalJulian Granger-Bevan: Why I didn't go to Drupalcon
Drupalcon is great fun (I'm sure), and (as importantly) a fabulous opportunity for the Drupal community to share, to learn, and to advance together.
I love Drupalcons. But I have never been to one.
How can that be?
Unlike many in the Drupal community, I do not get paid to "do Drupal".
I "do Drupal" because I enjoy the interactions with the community, seeing the work I do get appreciated and used, and to "scratch my own itch" for the websites I build.
With this context, you may understand why I don't go to Drupalcon. I cannot justify the time as well as cost, for (what is to me) but a hobby.
So why am I so complementary about Drupalcons?
How can I justify this when I have never been to a Drupalcon?
Whilst I've never been "in person", I follow along to sessions online during the conference and afterwards. I always tend to watch Dries' keynote, and other keynotes and sessions depending on the topic.
These videos massively increase the impact of Drupalcon because they allow the "long tail of contributors" like myself to follow.
A massive thank you to the Drupal Association and conference organisers for making that happen.
So even if you, like me, did not go to Drupalcon - I would encourage you to catch up with as much as you can. All videos are on the Drupal Association Youtube Channel.
Further to this, since I believe that it is the "long tail of contributors" that make open source software what it is, I encourage all organisers of camps and events to try to replicate the impact by making videos of sessions freely available online.
We share together, learn together, and advance together.
Category: WebsitesTags: DrupalDrupal PlanetDrupalconKristen Pol: Drupal misadventures configuring Private Message + Views + DBTNG
I'm working with the Private Message module and applied a patch for Views integration from dawehner (yay!). The patch works pretty well so I was able to create an admin dashboard that shows private messages and lets you filter the messages by subject text, body text, has been read, etc.
Memories of DrupalCon Portland
Every DrupalCon has its own personality. Before we let Portland slip away into the distant past, let’s revisit a few memories that made this Con unique, and a great addition to the storied history of DrupalCons.
The DrupalCon host city is always an important character in the cast and Portland was no exception. The weather turned on a dime from record high temperatures and sunshine at the beginning of the week to record low temperatures, wind and rain. The abrupt switch left visitors scratching their heads, while those who live in Portland simply shrugged, reciting the motto “If you don’t like the weather in Portland, just wait a few minutes.”
The Drupal Association sponsored a scavenger hunt, which sent participants around town taking pictures of chickens, bookstores, statues and food carts; just your typical Portland scenery. Congratulations to attendees Mike Hathaway and Jason Narog, who were able to capture pictures of all 15 iconic items and even a few bonus points. By the way, Portland was the first time the Drupal Association had an event booth, and we may have underestimated the demand for the blue mustaches we were giving away. We ran out on Wednesday!
Social media has always been a big component of DrupalCon and this year saw some really creative ideas. One idea called DrupalCon Stories, was designed to share the DrupalCon experience from a number of different attendee perspectives. Participants blogged, tweeted and shared images of their experience to give anyone around the world a flavor of the event. Expect to see DrupalCon Stories at future events.
Then there was the group of attendees who, driven by their desire to help, spontaneously decided to do something to assist tornado victims 1,800 miles away in Oklahoma. You may have seen the news stories about help4ok.org, the site designed and developed literally overnight. After tornadoes ripped through Oklahoma, taking lives and destroying homes and buildings, these DrupalCon attendees found a way they could aid those suffering from the devastation. The help4ok.org sprinters are a great example of what it truly means to be a member of the Drupal community.
These are just a few of our memories from Portland. What are yours?
Personal blog tags: DrupalCon Portlanddrupalconhelp4okDrupal Association News: Memories of DrupalCon Portland
Every DrupalCon has its own personality. Before we let Portland slip away into the distant past, let’s revisit a few memories that made this Con unique, and a great addition to the storied history of DrupalCons.
The DrupalCon host city is always an important character in the cast and Portland was no exception. The weather turned on a dime from record high temperatures and sunshine at the beginning of the week to record low temperatures, wind and rain. The abrupt switch left visitors scratching their heads, while those who live in Portland simply shrugged, reciting the motto “If you don’t like the weather in Portland, just wait a few minutes.”
Personal blog tags: DrupalCon Portlanddrupalconhelp4okDrupal Association News: Memories of DrupalCon Portland
Every DrupalCon has its own personality. Before we let Portland slip away into the distant past, let’s revisit a few memories that made this Con unique, and a great addition to the storied history of DrupalCons.
Personal blog tags: DrupalCon Portlanddrupalconhelp4okFuse Interactive: 8 Modules Commerce Kickstart Didn't Include But Should Be On Your Commerce Site
Here at Fuse, we’ve been working on a few commerce projects lately. We've been using Commerce Kickstart as the base for some projects and customize to meet client business requirements. By using Kickstart, we no longer need to download a long list of modules and configure them individually, it saves us many hours of work and save money for our clients. For users that are new to Drupal, and want to have an online store, Commerce Kickstart is great.
Propeople Blog: Projects Administration Interface
Working in a Drupal Agency, during everyday web development activity, very often you need to create new Drupal projects to test different Drupal modules and functionalities.
The installation of a new project takes time and knowledge not only of the operating system, but also specific to the software specialized on the server part like LAMP on GNU/Linux, WAMP on Windows, MAMP on Mac OS, etc.
What is a [WLM]AMP stack?The name “*AMP stack” comes from the abbreviation of software names, the first letter signifying the operating system it runs on: W - Windows, L - GNU/Linux, M - Mac OS. The next three letters represent the specialized software: A - Apache, M - MySQL, P - PHP. All of these applications (stack - a set of apps) work on the server part. While there are also other types of stacks, we will only be discussing these.
Setting up a new projectIn the example below I will explain how to set up a project in GNU/Linux, specifically Ubuntu 13.04, with the LAMP stack installed and configured for work. Usually I go through the following steps:
- Copy the project in the “wwwroot” folder of the LAMP stack, usually the path is “/var/www/”
- Add a new virtual host. Usually in “/etc/apache/sites-available”
- Execute the command that adds a virtual host “a2ensite [name_of_file]”
- Restart the Apache server
- Create a new database (we will not describe here how to add a new user and grant him permissions to the database, we’ll use the default “root” user)
- Accessing the address of the new website we can install the project, which will in turn generate the content of the database in case the project
Performing all these actions we prepare a stable work environment for the Drupal developer.
Use casesLet’s say we have to set up an existing project - we have to go through the following steps: checkout the sources from the repository and copy them into “wwwroot” on Apache, upload and install the database for the project, add a new record in the hosts file with the address of the project (domain), add the virtual host for the project, activate the virtual host, restart the Apache server. In the end, we will have this project for further development.
There are cases when we have to work with a generic module or a patch to a contrib module and we need to test the module/patch both in our work environment (existent project) and in a new Drupal instance (new project), in order to find the irregularities and eliminate them, to be sure that the specific module/patch will work in any conditions.
It’s another case when we work in a team and the project is versioned (git), with groups of developers working on different branches. Here we will need more than one Drupal environment (existent or new project), which implies going through the steps above more than once.
To make this work easier there are some sets of applications that can help us in managing servers and projects:
Webmin is a web-based interface for system administration for Unix. Using any modern web browser, you can setup user accounts, Apache, DNS, file sharing and much more.
Drush is a command line shell and scripting interface for Drupal, a veritable Swiss Army knife designed to make life easier for those of us who spend some of our working hours hacking away at the command prompt.
Vagrant provides easy to configure, reproducible, and portable work environments built on top of industry-standard technology and controlled by a single consistent workflow to help maximize the productivity and flexibility of you and your team.
The Aegir hosting system allows developers and site administrators to automate many of the common tasks associated with deploying and managing large websites. Aegir makes it easy to install, upgrade, deploy, and backup an entire network of Drupal sites.
Custom scripts
For me, personally, it’s an issue that occurs often - creating new or existing projects to test modules/patches or work in a team on different branches of a project. In everyday work I use Drush for downloading, synchronizing and cleaning the cache. Applications like Vagrant and Aegir that work with project management imply major handling to set up projects and also require resources from the computer, such tasks sometimes becoming more difficult than going through the steps above.
So, I decided to create a system that would be flexible in adding and managing Drupal projects, taking care of adding and modifying Apache, hosts files, creating a database and users, integrate Drush, with a fast and extensible interface for managing the projects. This is how the Project Application Interface got started.
Right now, it’s in the drupal.org sandbox, still requiring optimizations and improvements. It’s still in development, so you’re welcome to contribute. I hope to add more functions to this PAI, so follow the blog or our Social Media accounts (Twitter, Facebook) for more details about the evolution of the project.
Language English Tags: DrupalDevelopmentCheck this option to include this post in Planet Drupal aggregator: planet
