Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough
Aug 22 2016
Aug 22

The Drupal accessibility initiative started with some advancements in Drupal 7 to ensure that Drupal core followed the World Wide Web Consortium (W3C) guidelines: WCAG 2.0 (Web Content Accessibility Guidelines) and ATAG 2.0 (Authoring Tool Accessibility Guidelines).
Many elements introduced in Drupal 7 were improved and bugs discovered through intensive testing were addressed and integrated to Drupal 8 core as well. Let’s take a tour of the accessibility in Drupal 8 !

Contrasts improved

Drupal’s accessibility maintainers improved contrasts in core themes so people that suffer from colorblindness are able to visit websites clearly. It is also good when visiting the website under bright sunlight, on mobile for instance.

A screenshot that compares Bartik headers in Drupal 7.43 and Drupal 8

Color contrasts in Bartik theme in Drupal 7.43 and Drupal 8.

See the related WCAG 2.0 section about contrasts.

Alternative texts for images

The alternative text for images is really useful for blind people who use screen readers. They can understand the meaning of an image through short descriptive phrases. This alternative text is now by default a required field in Drupal 8.

A screenshot showing that the alternative text is required when uploading an image in Drupal 8.

The alternative text for an image is required by default in Drupal 8 content edition.

See the related WCAG 2.0 section about alternative texts.

More semantics

Many accessibility improvements are hard to see as it involves semantics. Drupal 8 uses HTML5 elements in its templates which add more meaning into the code. For instance, assistive technology such as screen readers can now interpret elements like <header>, <footer> or <form>.

Moreover, WAI-ARIA (Web Accessibility Initiative – Accessible Rich Internet Applications) additional markup really improved semantics using:

  • landmarks to identify regions in a page, for instance: role="banner" ;
  • live regions to indicate that an element will be updated, for instance: aria-live="polite";
  • roles to describe the type of widgets presented, for instance: role="alert";
  • properties: attributes that represent a data value associated with the element.

See the related WCAG 2.0 sections about semantics.

Tabbing order

Drupal 8 introduces the TabbingManager javascript feature. It enables to constrain tabbing order on the page and facilitates navigation with keyboards. It is really helpful to guide a non-visual user to the most important elements on the page and minimize confusion with screen readers.

See the related WCAG 2.0 section about keyboard operations.

Forms

Drupal 8 accessibility involves many improvements regarding forms in Drupal 8.

In Drupal 7, all errors were displayed by default on top of the form and fields were highlighted in red. It was not right for colorblind people to understand where the errors were.
In Drupal 8, there is an experimental option to enable form inline errors and an error icon is displayed next to the field. Nevertheless, note that this feature is not enabled by default as there are still some pending issues.

Screenshot of a password field highlighted in red with an error message below "The specified passwords do not match"

The error message is displayed below the field when the Form Inline Error module is enabled.

See the related WCAG 2.0 sections about error identification.

Regarding the form API, radios and checkboxes are now embedded in fieldsets to meet WCAG compliance. Indeed, grouping related elements will help screen readers to navigate in complex forms. Plus, all fields have a label associated with the right element using the “for” attribute.

Here is an example of HTML code for radio buttons:

Poll status

See the related WCAG technical section about fieldsets

Tables and views

As Views UI module is in core now, it became accessible.
The views tables markup is more semantic. Data cells are associated with header cells through “id” and “headers” attributes. It is also possible to add a <caption> element to explain the purpose of the table and a <summary> element to give an overview on how the data is organized and how to navigate the table.
Plus, the “scope” attribute enables to explicitly mark row and column headings.

Here is an example of a table HTML code generated by a view:

Content type <a href="https://blog.liip.ch/admin/structure/types/manage/article">Article</a> <a href="https://blog.liip.ch/admin/structure/types/manage/article">Article</a>

Content type

<a href="/admin/structure/types/manage/article">Article</a>

<a href="/admin/structure/types/manage/article">Article</a>

Details for the table

Description for details

See the related WCAG section about tabular information.

Hidden elements

Using "display:none;" CSS styling can be problematic as it will hide elements for both visual and non-visual users and consequently, screen readers will not be able to read them.
Drupal 8 accessibility maintainers decided to standardize in the naming convention of HTML5 Boilerplate using different classes to hide elements:

  • hidden“: hide an element visually and from screen readers;
  • visually-hidden“: hide an element only visually but available for screen readers;
  • invisible“: hide an element visually and from screen readers without affecting the layout.

Aural alerts

Users with visual impairment will not be able to see all visual updates of the page such as color changes, animations or texts appended to the content. In order to make these changes apparent in a non-visual way, Drupal provides the Drupal.announce() JavaScript method which creates an “aria-live” element on the page. This way, text appended to the node can then be read by a screen reading user agent.

Here is an example of a code using the aural alert:

Drupal.announce('Please fill in your user name', 'assertive');

1

Drupal.announce('Please fill in your user name', 'assertive');

The first parameter is a string for the statement, the second is the priority:

  • polite“: this is the default, polite statements will not interrupt the user agent;
  • assertive“: assertive statements will interrupt any current speech.

See the related WCAG technical section about how to use live regions to identify errors.

CKEditor WYSIWYG accessibility

Drupal community helped improving CKEditor accessibility.
First of all, the WYSIWYG editor now comes with keyboard shortcuts which are beneficial for both power users and keyboard-only users.
Drupal 8 implements more semantic elements. For instance, the user can create HTML tables with headers, caption and summary elements. <figure> and <figcaption> HTML5 tags are also available to add captions to images.
Moreover, every image added through CKEditor are required by default, as it is on image fields.

CKEditor module also introduces a language toolbar button so that users can select a part of text and specify the language used. Screen readers will be able then to choose the appropriate language for each content.

See the related WCAG technical section about language attributes.

Finally, there is an accessibility checker plugin for CKEditor. It is not in core yet as a CKEditor issue blocks its integration, you can find more information on the related Drupal issue queue. However, you will find a module that implements it currently: ckeditor_a11checker.

All these options will definitely help users to generate accessible contents.

Conclusion

Drupal core maintainers accomplished great enhancements regarding accessibility in Drupal 8. These accessibility features will definitively be beneficial to keyboard-only users, low-vision users and colorblind people but will also be good for the usability and the SEO of your website.
Nevertheless, there is still work to be done to make Drupal 8 core fully accessible and Drupal needs contributors to tackle the remaining issues.

If you want to learn more about Drupal 8 accessibility, you can watch the presentation about “How Drupal 8 makes your website more easily accessible” given by Mike Gifford, one of the main accessibility core maintainer for Drupal.

Aug 17 2016
Aug 17

Last week Drupalaton 2016 took place. With about 150 registrations this was the largest Drupalaton so far. The organizers did an amazing job in coping with this mass. There were two session threads and a sprint room. Of the many interesting presentations I would like to mention Fabian Bircher’s “Configuration Management: theory and practice” (a must for everyone who gets lost while trying to work in a team on a Drupal8 project) , Pieter Frenssen’s “Working with REST APIs”  (it was good to see how simple it is in Drupal8) and “Drupal 8 Media” from Pónya Péter, Szanyi Tamás and Rubén Teijeiro (seems we have a huge step forward in media handling since Drupal7!). I held a session on caching in Drupal 8 which was the shortened version the one I did on Drupal Developer Days in Milan.

Liip was a silver sponsor of the event.

Finally, some pictures on the Friday ship cruise. Thanks to Brainsum for sponsoring it!

Jul 11 2016
Jul 11

Many people have now heard of the EFF-backed free certificate authority Let's Encrypt. Not only is it free of charge, it has also introduced a fully automated mechanism for certificate renewals, eliminating a tedious chore that has imposed upon busy sysadmins everywhere for many years.

These two benefits - elimination of cost and elimination of annual maintenance effort - imply that server operators can now deploy certificates for far more services than they would have previously.

The TLS chapter of the RTC Quick Start Guide has been updated with details about Let's Encrypt so anybody installing SIP or XMPP can use Let's Encrypt from the outset.

For example, somebody hosting basic Drupal or Wordpress sites for family, friends and small community organizations can now offer them all full HTTPS encryption, WebRTC, SIP and XMPP without having to explain annual renewal fees or worry about losing time in their evenings and weekends renewing certificates manually.

Even people who were willing to pay for a single certificate for their main web site may have snubbed their nose at the expense and ongoing effort of having certificates for their SMTP mail server, IMAP server, VPN gateway, SIP proxy, XMPP server, WebSocket and TURN servers too. Now they can all have certificates.

Early efforts at SIP were doomed without encryption

In the early days, SIP messages would be transported across the public Internet in UDP datagrams without any encryption. SIP itself wasn't originally designed for NAT and a variety of home routers were created with "NAT helper" algorithms that would detect and modify SIP packets to try and work through NAT. Sadly, in many cases these attempts to help actually clash with each other and lead to further instability. Conversely, many rogue ISPs could easily detect and punish VoIP users by blocking their calls or even cutting their DSL line. Operating SIP over TLS, usually on the HTTPS port (TCP port 443) has been an effective way to quash all of these different issues.

While the example of SIP is one of the most extreme, it helps demonstrate the benefits of making encryption universal to ensure stability and cut out the "man-in-the-middle", regardless of whether he is trying to help or hinder the end user.

Is one certificate enough?

Modern SIP, XMPP and WebRTC require additional services, TURN servers and WebSocket servers. If they are all operated on port 443 then it is necessary to use different hostnames for each of them (e.g. turn.example.org and ws.example.org. Each different hostname requires a certificate. Let's Encrypt can provide those additional certificates too, without additional cost or effort.

The future with Let's Encrypt

The initial version of the Let's Encrypt client, certbot, fully automates the workflow for people using popular web servers such as Apache and nginx. The manual or certonly modes can be used for other services but hopefully certbot will evolve to integrate with many other popular applications too.

Currently, Let's Encrypt's certbot tool issues certificates to servers running on TCP port 443 or 80. These are considered to be a privileged ports whereas any port over 1023, including the default ports used by applications such as SIP (5061), XMPP (5222, 5269) and TURN (5349), are not privileged ports. As long as certbot maintains this policy, it is generally necessary to either run a web server for the domain associated with each certificate or run the services themselves on port 443. There are other mechanisms for domain validation and various other clients supporting different subsets of them. Running the services themselves on port 443 turns out to be a good idea anyway as it ensures that RTC services can be reached through HTTP proxy servers who fail to let the HTTP CONNECT method access any other ports.

Many configuration tasks are already scripted during the installation of packages on a GNU/Linux distribution (such as Debian or Fedora) or when setting up services using cloud images (for example, in Docker or OpenStack). Due to the heavily standardized nature of Let's Encrypt and the widespread availability of the tools, many of these package installation scripts can be easily adapted to find or create Let's Encrypt certificates on the target system, ensuring every service is running with TLS protection from the minute it goes live.

If you have questions about Let's Encrypt for RTC or want to share your experiences, please come and discuss it on the Free-RTC mailing list.

Jul 11 2016
Jul 11

After having contributed to the official styleguide of the Swiss Federal Government and having implemented it on a couple of websites, we decided to go further and bring these styleguide into a theme for Drupal, a well-known, pluripotent and robust CMS we implement regularly at Liip.

Screenshot of Drupal theme for the Swiss Confederation

The current result is a starterkit providing the essential bricks to start in a snap a website project for the federal government running with Drupal 8, based on the version 3 of the official styleguide.

Navigation modules, multilingual environnement per default (German, French, Italian, Rumantch and English), responsive layout following the Web Content Accessibility Guidelines, we threw the fundamental stones for bootstraping a web platform for the Confederation.

con~foederatio : to build a league, together.

In other words, joining forces, to support a common cause. From the very start of the project we decided to opensource the code, as a participatory initiative.
Learn more about this intent.

Any developer working on a new website for the swiss government can now quickly start developing with this Drupal starterkit, then modify, contribute and improve it collegially. Pulling requests and opening issues on GitHub is the recommended way to help us extend further the project.

What’s inside the box

The Bund-Starterkit provides theme and elements based on the official styleguide (version 3.0.0) of the Swiss Federal Administration.

This starterkit also contains a base to quickly implement a website running on Drupal 8 for the Swiss Federal Administration. Currently, it provides the following Drupal and frontend elements:

  • Multilingual main navigation blocks
  • Multilingual service navigation blocks
  • Multilingual footer service navigation blocks
  • Logo block
  • Language switcher block with German, French, Italian, Rumantsch enabled
  • All the assets (CSS, SASS. JS files) provided by the official styleguide
  • A ready-to-use SASS workflow

Installation process, an overview

Please check the Readme file to quickly start your project. But let’s have a look at the details of the installation process. First of all, Composer (a PHP dependencies manager) is binding together for us the following repositories:

After downloading the sources with Composer and setting your vhost and hosts files, you have two options. Continuing with a few drush commands to run the Drupal installation process, or following the installation wizard in the browser. If you choose this last option, don’t forget to select the «Bund profile» option when the wizard ask you to choose a profile:

Chose a profile for the Drupal theme for the Swiss Confederation

Continue with the last steps of the wizard and that’s it. you should be able to see an empty Drupal 8 website, painted with the swiss administration’s corporate sauce.

Inserting menus content

With the help of a .CSV file and some drush commands, you can quickly import your menu structure. Once done, create and assign your content the the freshly created menu items through the Drupal administration interface.

Theming

Don’t forget to create a personal Drupal sub-theme from the bund_drupal_starterkit_theme, as a Drupal best practice.  Don’t edit the existing theme directly or you could loose your changes after a future update.

Frontend

This starterkit use the official styleguide (version 3.0.0) as a submodule. All existing CSS/JS files and assets are imported and available per default, but not necessary integrated as a drupal module at the moment. We highly encourage you to check the official styleguide before adding any new CSS style or JS files to your project. Based on the existing styles, it should be possible to create a lot of Drupal templates without modifying or extending any CSS. And as already said, we invite you to share any Drupal template matching the styleguide you would develop for your project.

Further reading

Jul 07 2016
Jul 07

Eight months ago Drupal 8.0.0 was released. Exciting news for drupalists. Since then comparing D8’s features to its predecessor is a topic in daily business. «Can drupal 8 do what we can do now with 7 today?”. After playing around with D8 i get the feeling some crucial features are missing. Dries invited people to tell ”why we not use or migrate to drupal 8” – and got a clear answer: A majority of drupalist (60%) are waiting for certain modules. So the follow up question would be what are these modules.

On the fly my top 10 wishlist would be:

  • pathauto
  • token
  • webform
  • metadata
  • views_bulk_operations
  • flag
  • rules
  • xmlsitemap
  • redirect
  • search_api

Today it seems quite difficult to get a good overview of D8 ecosystem. Also because some module development moved to github to have a better collaboration tool. I was irritated to see no D8 version of the webform module in the download section on drupal.org  – That’s a module with 1/2 million downloads for D7. Comments on this issue gives some answers. Without committed maintainers from the beginning the porting takes much longer. A highly complex module like webform probably needs almost complete rewrite to fit into the new core of D8. Porting module from D7 to D6 was much easier. For forms we could use in some cases the core Form API, core contact forms or the eform module. But our clients would most likely miss out on the experience of D7s webform module.

Under the hood Drupal 8 core changed significantly. Symfony2 for example is now playing its music to give us new possibilities. I guess in some cases there are new solutions we have yet to discover. From a suitebuilder point of view, D8 is delightfully similar to what we know from D7. However, it will take some getting used to not trying to add the old modules we know to this new architecture.

In the end the importance of a variety of mature modules that play together nicely is crucial when it comes to efficiency, maintainability and stability of a project…

“I am confident that Drupal 8 will be adopted at “full-force” by the end of 2016.”
Dries Buytaert

Drupal is a registered trademark of Dries Buytaert.

Jun 27 2016
Jun 27

DDD is mostly for – surprise! – Drupal developers. This year it took place between 21 and 26 of June in Milan. People were on code sprints all week long and on Thursday, Friday and Saturday there were sessions and workshops as well.

I went to 2 sessions. The keynote of Bojan Živanović was about building reusable php libraries. Bojan is the architect behind Drupal Commerce 2 which is a prominent example of adopting the “leave the Drupal island” principle. They are not only advocating the usage of external solutions in Drupal but also creating libraries that are usable outside Drupal.

The session of Major Zsófi about organizing Drupal events could not have come from a more authentic source. She shared her experience about the practical aspects of building a community and the importance of providing coffee.

All session recordings are or will be available online.

I attended three workshops. A really excellent one by Florian Loretan was about the trending search solution, elasticsearch. Pieter Frenssen had a workshop about Automated testing in Drupal 8. For me this proved to be the most valuable one since I could not keep up with the changes in this field since Drupal 7 and I need it in my contrib work. All my respects to Pieter who was able to present for 3.5 hours in a way that noone fell asleep even though we were just after lunch.

The third workshop I attended was my own 2 hours workshop about Caching in Drupal 8. I learnt a lot about this important topic during preparation and since only around one person left the room it might have been useful for the audience as well.

In the sprint room I joined the Commerce team. The team seemed to have been cursed. A laptop was stolen from the sprint site on Wednesday. Then on Thrusday night Bojan’s MacBook got also stolen from a restaurant with days of uncommitted work. Fortunately, with the effective help of the organizers (which among others included providing the victims a spare laptop and taking them to the police to file a report) they could participate in the sprints only with a minimal amount of delay. As a result we could finish several issues in the Commerce, Commerce Migrate, Token and Address modules.

Sightseeing with drupalists

Sightseeing with drupalists

But the most important part of DDD was the social aspects. I met old friends and got to know new interesting people. Wednesday evening there was a quantitywise challenging dinner for speakers. On other nights we visited several parts of the beautiful city of Milano. Huge thanks to all the organisers, you did an amazing job! Hope to see you next year!

WebRTC and communications projects in GSoC 2016

Jun 20 2016
Jun 20
Jun 20 2016
Jun 20

It has been nearly 7 months since Drupal 8 first release and as a developer, I am still in the learning process. It can be hard sometimes to know what is going wrong with your code and how to solve it. I will tell you about few things to know on how to develop and debug Drupal 8 projects and continue learning, learning and learning !

Disabling cache

First of all, to avoid having a crazy terminal with thousands of drupal cr hits, you can disable Drupal caching during development. You need to copy and rename sites/example.settings.local.php file to sites/default/settings.local.php. Then uncomment/update some values:

  • uncomment this line to enable the “null cache service”:

    $settings['container_yamls'][] = DRUPAL_ROOT . '/sites/development.services.yml';

    1

    $settings['container_yamls'][] = DRUPAL_ROOT . '/sites/development.services.yml';

  • uncomment these lines to disable CSS/JS aggregation:

    $config['system.performance']['css']['preprocess'] = FALSE; $config['system.performance']['js']['preprocess'] = FALSE;

    $config['system.performance']['css']['preprocess'] = FALSE;

    $config['system.performance']['js']['preprocess'] = FALSE;

  • uncomment these lines to disable the render cache and the dynamic page cache:

    $settings['cache']['bins']['render'] = 'cache.backend.null'; $settings['cache']['bins']['dynamic_page_cache'] = 'cache.backend.null';

    $settings['cache']['bins']['render'] = 'cache.backend.null';

    $settings['cache']['bins']['dynamic_page_cache'] = 'cache.backend.null';

  • you can allow test modules and themes to be installed if needed with:

    $settings['extension_discovery_scan_tests'] = TRUE;

    1

    $settings['extension_discovery_scan_tests'] = TRUE;

To include this file as part of Drupal’s settings file, open sites/default/settings.php file and uncomment these lines:

if (file_exists(__DIR__ . '/settings.local.php')) { include __DIR__ . '/settings.local.php'; }

if (file_exists(__DIR__ . '/settings.local.php')) {

    include __DIR__ . '/settings.local.php';

}

Then, to disable Twig caching, open sites/development.services.yml file and add the following settings:

parameters: twig.config: debug: true auto_reload: true cache: false

parameters:

    twig.config:

        debug: true

        auto_reload: true

        cache: false

Finally, rebuild the Drupal cache and it is done !

Displaying errors

As in Drupal 7, you can set different levels of errors display (by visiting this page: /admin/config/development/logging in the administration interface):

  • None
  • Errors and warnings
  • All

In Drupal 8, there is a fourth level called “All messages, with backtrace information”. This is native to Drupal core and it allows to display the error backtrace in the message area.

You can also adjust the level of errors in your local setting file:

$config['system.logging']['error_level'] = 'verbose';

1

$config['system.logging']['error_level'] = 'verbose';

Creating log messages

Developers from Drupal 7 know the Database Logging module that allows to log messages in the database using the famous watchdog() function.
Well, this module still exists in Drupal 8 but the function has a replacement : the Drupal 8 logger class.
It looks like this:

// Logs an error \Drupal::logger('my_module')->error($message);

// Logs an error

\Drupal::logger('my_module')->error($message);

Let’s have a look at the different parts of the code:

  • \Drupal::logger(‘my_module’) is the helper method that quickly creates a copy of the logger service. As a parameter, it takes the module name from where we log the information;
  • ->error: this is the severity-level method (it can be debug, info, notice, warning, error, critical, alert, emergency);
  • $message is the log message. It can be a simple string or it can contain some placeholders. In this case, you can pass the associative array (placeholders as keys) as a second parameter.

All the messages created with the logger service can be viewed in the reports page of the administration interface in /admin/reports/dblog as it used to be in Drupal 7.

Debugging Twig templates

Drupal core comes with a theming debug mode that is really helpful for local environments. To enable it, copy-and-paste the following code into sites/default/services.yml file (if you haven’t already added these lines in development.services.yml):

parameters: twig.config: debug: true

parameters:

    twig.config:

        debug: true

Thanks to this mode, it will be easier to find out which portion of HTML code has been written in which templates: in the source code, you will see each part of Twig templates (pages, nodes, blocks, menus etc.) surrounded by HTML comments that contain the matching suggestion templates. The one in current use will be checked.
Make sure to enable comments in your web browser debug tab and also note that this feature has been backported to Drupal 7.

Twig debug in comments

Used templates mentioned in HTML comments

Inside a Twig template, you will also be able to use {{ dump(my_variable) }} syntax to print a variable content.
If you have Devel Kint module installed, you can use {{ kint(my_variable) }} to dump the variable in a nice formatted structure: you can hide/show levels of arrays/objects which is very helpful as Drupal variables can have many levels inside. Kint is indeed the successor of the krumo() function from Drupal 7.

content_attributes printed with kint function

Print content_attributes variable with kint from node.html.twig template

Profiling pages

When dealing with performance issues, profiling a website will help finding what is the root cause.
The well-known debugging module Devel offers in its latest version a Webprofiler. It is actually a (partial) port of the Symfony profiler and it displays a footer bar on every page with useful data collectors such as:

  • Drupal current version;
  • PHP configuration (current version, loaded modules);
  • route and controller name;
  • page load timeline and memory use;
  • query time and number of queries;
  • number of blocks loaded and rendered;
  • number of views;
  • number of modules and themes available;
  • cache statistics.
Webprofiler bar in Drupal 8

Webprofiler bar in Drupal 8

By clicking on each section, you will be redirected to a specific page with more details about the collected data. For instance, if you look at the page request details, you will see which was the matching route, the route object with the passed parameters, response headers etc. Well, all the information needed to debug requests.

Webprofiler - details of a page request

Details of a page request provided by Webprofiler

Using command line tools

The Drupal Console project is a powerful command line tool that makes use of the Symfony Console and other third-party components. It is complementary to Drush and allows you to generate code to build modules and themes (code scaffolding), to interact with your Drupal installation and help to debug your code.

Once the Drupal Console installed, you can run the drupal list command to show all available commands.

Here are some useful commands for debug:

  • drupal check: check system requirements;
  • drupal site:status: show current Drupal installation status (Versions of Drupal, PHP, MySQL and libraries, updates status, cron last run, database connection etc.);
  • drupal database:table:debug: show all tables of the database;
  • drupal database:table:debug my_table: show columns of my_table table;
  • drupal config:debug: list all configurations;
  • drupal config:debug image.settings: show configuration for image.settings;
  • drupal config:settings:debug: display current key:value from the settings file;
  • drupal container:debug: display all services ID with the matching class name;
  • drupal router:debug: display all route names with the matching paths;
  • drupal router:debug dblog.overview: display route information about dblog overview page;
  • drupal database:log:debug: display current log events;
  • drupal database:log:debug 107: display one log event in details;
  • drupal site:statistics: show some statistics about the website (number of modules enabled/disabled, number of users and comments etc.).

The Drupal Console is already used by many companies well known in the Drupal community such as Acquia, Amazee Labs, Phase2 and Commerce Guys and is becoming the standard command line tool for Drupal 8.
But note that the current version is not yet fully compatible with Drupal 8.1.x, for instance, there are still some issues with migration commands. The team of the project is currently looking for some financial support and more contributors to get a full stable release.

For more information about the project, check out the official website and the documentation.

Jun 16 2016
Jun 16

This is part 2 of a series on using XHProf for profiling Drupal modules.

After you’ve installed XHProf, what’s next? How can you make use of its recommendations to tune a module or a website? Unfortunately there are no hard-and-fast rules for optimizing code to run more efficiently. What I can offer here is my own experience trying to optimize a D8 module using XHProf.

Understanding an XHProf run report

The XHProf GUI displays the result of a given profiler run, or a group of runs. It can even compare 2 runs, but we’ll get to that in a minute. If you followed my previous post, you should have the xhprof_html directory symlinked into the root web directory for your Drupal site; so visiting <my-local-site>/xhprof/ should give you a list of all available stored run IDs, and you can click through one of those to view a specific run report.

You can also go directly to a specific run report via the URL <my-local-site>/xhprof/index.php?run=<run-id>&source=<source-id> (which you should have been logging already via an echo statement or dblog if you followed the last post).

Header of an XHProf run report

The core of the run report is a table of each function or method which your code called while it was being profiled, along with a set of statistics about that function. This allows you to understand which parts of your code are most resource-intensive, and which are being called frequently in the use case that you’re profiling. Clicking on any one of the column headers will sort the list by that metric. To understand this report, it’s helpful to have some terminology:

  • Incl. Wall Time - The total clock time elapsed between when a function call started and when the function exited. Note that this number is not a great basis for comparisons, since it can include other processes which were taking up CPU time on your machine while the PHP code was running, from streaming music in the background to PHPStorm’s code indexing, to random web browsing.
  • Incl. CPU Time - In contrast to wall time, CPU time tracks only the time which the CPU actually spent executing your code (or related system calls). This is a more reliable metric for comparing different runs.
  • Excl. Wall/CPU Time - Exclusive time measurements only count time actually spent within the given method itself. They exclude the time spent in any method/function called from the given function (since that time will be tracked separately).

In general, the inclusive metrics (for CPU time and memory usage) will give you a sense of what your expensive methods/functions are – these are the methods or functions that you should avoid calling if possible. In contrast, the exclusive metrics will tell you where you can potentially improve the way a given method/function is implemented. For methods which belong to Drupal Core or other contrib modules, inclusive and exclusive metrics are basically equivalent, since you don’t usually have the option of impacting the implementation details of a function unless you’re working on its code directly. Note also that because your overall parent method and any other high-level methods in your code will always show up at the top of the inclusive time chart, you may have better luck really understanding where your performance hits come from by sorting by exclusive CPU time.

Take a step back and plan your test scenarios

Before digging in to optimizing your module code, you need to take a step back and think about the big picture. First, what are you optimizing for? Many optimizations involve a tradeoff between time and memory usage. Are you trying to reduce overall run-time at the expense of more memory? Is keeping the memory footprint of a given function down more important? In order to answer these questions you need to think about the overall context in which your code is running. In my case, I was optimizing a background import module which was run via cron, so the top priority was that the memory usage and number of database optimizations were low enough not to impact the user-facing site performance.

Second, what use case for your code are you profiling? If this is a single method call, what arguments will be passed? If you’re profiling page loads on a website, which pages are you profiling? In order to successfully track whether the changes you’re making are having an impact on the metrics you’re concerned about, you need to be able to narrow down the possible use cases for your code into a handful of most-likely real world scenarios which you’ll actually choose to track via the profiler.

Keep things organized

Now it’s time to get organized. Write a simple test script so that you can easily run through all your use cases in an automated way – this is not strictly necessary, but it will save you a lot of work and potential error as you move through the process. In my case, I was testing a drush command hook, so I just wrote a bash shell script which executed the command three times in each of two different ways. For profiling page loads, I would recommend using Apache JMeter - and you’ll need to consider whether you want to force an uncached page load by passing a random dummy query parameter. Ideally, you should be running each scenario a few times so that you can then average the results to account for any small variations in run-time.

Keeping your different runs organized is probably the most important part of successfully profiling module code using XHProf! Each run has a unique run ID, but you are solely responsible for knowing which use case scenario and which version of the codebase that run ID corresponds to. I set up a basic spreadsheet in OpenOffice where I could paste in run numbers and basic run stats to compare (but there’s almost certainly a nicer automated way to do this than what I used).

Screenshot of an OpenOffice spreadsheet summarizing XHProf results for various runs

Once you have a set of run IDs for a given use case + codebase version, you can generate a composite xhprof report using the query string syntax http://<your-local-site>/xhprof/index.php?run=<first-run-id>,<second-run-id>,<third-run-id>&source=<source-string> Averaging out across a few runs should give you more precise measurements for CPU time and memory usage, but beware that if parts of your code involve caching you may want to either throw out the first run’s results in each version of the code base, since that’s where the cache will be generated, or clear the cache between runs.

Go ahead and test your run scripts to make sure that you can get a consistent baseline result at this point – if you’re seeing large differences in average total CPU times or average memory usage across different runs of the same codebase, you likely won’t be able to compare run times across different versions of the code.

Actually getting to work!

After all this set-up, you should be ready to experiment and see what the impact of changes in your code base are on the metrics that you want to shift. In my case, the code I was working on used a streaming JSON parser class, and I noticed that one of the top function calls in the inital profiler report was the consumeChar method of the parser.

Image of XHProf profiler report with the method consumeChar highlighted in yellow

It turns out that the JSON files I was importing were pretty-printed, thus containing more whitespace than they needed to. Sine the consumeChar method gets called on each incoming character of the input stream, that added up to a lot of unnecessary method calls in the original code. By tweaking the JSON file export code to remove the pretty print flag, I cut down the number of times this method was called from 729,099 to 499,809, saving .2 seconds of run time right off the bat.

That was the major place where the XHProf profiler report gave me insights I would not have had otherwise. The rest of my optimizing experience was mostly testing out some of the common-sense optimizations I had already thought of while looking at the code – caching a table of known Entity IDs rather than querying the DB to check if an entity existed each time, using an associative array and is_empty() to replace in_array() calls, cutting down on unnecessary $entity->save() operations where possible.

It’s useful to mention that across the board the biggest performance hit in your Drupal code will probably be database calls, so cutting down on those wherever possible will save run-time (sometimes at the expense of memory, if you’re caching large amounts of data). Remember, also, that if DB log is enabled each logging call is a separate database operation, so use the log sparingly – or just log to syslog and use service like Papertrail or Loggly on production sites.

The final results

As the results below show, using XHProf and some thoughtful optimizations I was able to cut total run time significantly in one use case (Case 2) and slightly in another use case (Case 1). Case 1 was already running in a reasonable amount of time, so here I was mostly interested in the Case 2 numbers (assuming I didn’t make anything too much worse).

Bar chart comparing the run time of various runs

Think of the big picture, part 2

Remember that controlled experimental metrics are just a means to understanding and improving real-world performance (which you can also measure directly using tools like blackfire, but that’s another blog post). In my case, at the end of the day we decided that the most important thing was to ensure that there wasn’t a performance impact on the production site while this background import code was running; so one of the optimizations we actually ended up implementing was to force this code to run slower by throttling $entity->save() operations to maximally 1 every 1/2 second or so, as a way to minimize the number of requests MySQL was having to respond to from the importer. XHProf is a powerful tool, but don’t lose the forest for the trees when it comes to optimization.

Working to pass GSoC

Jun 08 2016
Jun 08
May 26 2016
May 26

XHProf is a profiling tool for PHP code – it tracks the amount of time and memory your code spends in each function call, allowing you to spot bottlenecks in the code and identify where it’s worth spending resources on optimization. There are have been a number of PHP profilers over the years, and XDebug has a profiler as well, but XHProf is the first one I’ve successfully managed to configure correctly and interpret the output of.

I had run across a number of blog posts about using XHProf + Drupal, but never actually got it to work sucessfully for a project. Because so much of the documentation online is incomplete or out-of-date, I thought it would be useful to document my process using XHProf to profile a Drupal 8 custom module here. YMMV, but please post your thoughts/experiences in the comments!

How to find documentation

I find the php.net XHProf manual entry super-confusing and circular. Part of the problem is that Facebook’s original documentation for the library has since been removed from the internet and is only accessible via the WayBack Machine.

If there’s only one thing you take away from this blog post, let it be: read and bookmark the WayBack machine view of the original XHProf documentation, which is at http://web.archive.org/web/20110514095512/http://mirror.facebook.net/facebook/xhprof/doc.html.

Install XHProf in a VM

If you’re not running DrupalVM, you’ll need to install XHProf manually via PECL. On DrupalVM, XHProf is already installed and you can skip to the next step.

sudo pecl install xhprof-beta

Note that all these commands are for Ubuntu flavors of linux. If you’re on Red Hat / CentOS you’ll want to use the yum equivalents. I had to first install the php5-dev package to get PECL working properly:

sudo apt-get update
sudo apt-get install php5-dev

And, if you want to view nice callgraph trees like the one below you’ll need to install the graphviz package sudo apt-get install graphviz

Image of a sample XHProf callgraph

Configure PHP to run XHProf

You need to tell PHP to enable the xhprof extension via your php.ini files. Usually these are in /etc/php5/apache2/php.ini and /etc/php5/cli/php.ini. Add the following lines to the bottom of each file if they’re not there already. You will also need to create the /var/tmp/xhprof directory if it doesn’t already exist.

[xhprof]
extension=xhprof.so
;
; directory used by default implementation of the iXHProfRuns
; interface (namely, the XHProfRuns_Default class) for storing
; XHProf runs.
;
xhprof.output_dir="/var/tmp/xhprof"

Lastly, restart Apache so that the PHP config changes take effect.

Set up a path to view the XHProf GUI

The XHProf GUI runs off a set of HTML files in the xhprof_html directory. If you’ve been following the install steps above, you should be able to find that directory at /usr/share/php/xhprof_html. Now you need to set up your virtual host configuration to serve the files in the xhprof_html directory.

I find the easiest way to do this is just to symlink the xhprof_html directory into the existing webroot of whatever site you’re working on locally, for example:

ln -s /usr/share/php/xhprof_html /var/www/my-website-dir/xhprof

If you’re using DrupalVM, a separate vhost configuration will already be set up for XHProf, and the default URL is http://xhprof.drupalvm.dev/ although it can be changed in your config.yml file.

Hooking XHProf into your module code

Generally, the process of profiling a chunk of code using XHProf goes as follows:

  1. Call xhprof_enable()
  2. Run the code you want profiled
  3. Once the code has finished running, call xhprof_disable(). That function will return the profiler data, which you can either display to the screen (not recommended), or…
  4. Store the profiler data to a file by creating a new XHProfRuns_Default(); object and calling its save_run method.

In the case below, I’m profiling a module that implements a few Drush commands from the command line which I’d like to optimize. So I created _modulename_xhprof_enable() and _modulename_xhprof_disable() functions – the names don’t matter here – and then added a --profile flag to my Drush command options which, when it is set to true, calls my custom enable/disable functions before and after the Drush command runs.

Here’s what those look like in full:

<?php
/**
 * Helper function to enable xhprof.
 */
function _mymodule_enable_xhprof() {
  if (function_exists('xhprof_enable')) {
    // Tell XHProf to track both CPU time and memory usage
    xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY,
      array(
        // Don't treat these functions as separate function callss
        // in the results.
        'ignored_functions' => array('call_user_func',
          'call_user_func_array',
        ),
      ));
  }
}

/**
 * Helper function to disable xhprof and save logs.
 */
function _mymodule_disable_xhprof() {
  if (function_exists('xhprof_enable')) {
    $xhprof_data = xhprof_disable();

    //
    // Saving the XHProf run
    // using the default implementation of iXHProfRuns.
    //
    include_once "/usr/share/php/xhprof_lib/utils/xhprof_lib.php";
    include_once "/usr/share/php/xhprof_lib/utils/xhprof_runs.php";

    $xhprof_runs = new XHProfRuns_Default();

    // Save the run under a namespace "xhprof_foo".
    //
    // **NOTE**:
    // By default save_run() will automatically generate a unique
    // run id for you. [You can override that behavior by passing
    // a run id (optional arg) to the save_run() method instead.]
    // .
    $run_id = $xhprof_runs->save_run($xhprof_data, 'xhprof_mymodule');

    echo "---------------\nAssuming you have set up the http based UI for \nXHProf at some address, you can view run at \nhttp://mywebsiteurl.dev/xhprof/index.php?run=$run_id&source=xhprof_mymodule\n---------------\n";
  }
}

The echo command here works fine for a Drush command, but for other tasks you could log the run url using watchdog.

Note: Another way to run XHProf on a Drupal site is using the XHProf module, but I haven’t had great luck with that.

Viewing profiler results

If everything is configured correctly, when you run your module you should get a run ID output either to the screen (via echo, as above, or however you’ve configured this logging). Visit the URL you configured above for xhprof, and you should see a list of all the stored runs. Clicking on a run will bring up the full profiler report.

Sample screenshot of an XHProf profiler report

Now what?

Now you’ve got all this data – how to make sense of it? What to do with it? Stay tuned for more discussion of how to interpret XHProf results and a real-world example of profiling a D8 module, next week!

May 25 2016
May 25

Today I will describe a way to handle multiple teams with their own private file folders using the IMCE module.

Let’s pretend that we have to develop a website called awesome-website.com which consists of three (or more) different teams. The team structure could look as followed:

Website Groups

Every team should only be allowed to edit their own pages but no page from any other team. Therefore it would also make sense to separate the team’s file folders so that the files can be stored separately to secure its privacy.
Of course, we could simply add three IMCE profiles and define their folder access rights individually there. But what about when working with 10 teams? Or 50? Or even more? Then we definitely would prefer a more flexible solution.
Thankfully, IMCE ships with the ability to define user folders by PHP execution, how awesome! But in order to achieve this, we’ll have to set up teams as taxonomy terms first and reference them from our user entities.

Setting up the “Teams” taxonomy vocabulary

First things first: Let’s create a new taxonomy vocabulary called “Teams”. For every team that we will have on our website, we have to create a new taxonomy term in this vocabulary.
Before adding any teams as taxonomy terms though, we’ll have to add a new field called “FTP Folder” to the taxonomy vocabulary.
This field will specify the name of every team’s root folder. So, naturally it shouldn’t contain any spaces or other wicked special characters and it should be URL readable.
In order not to face any unusual results later, it is recommended to configure this field as required.

Afterwards, we can add our three terms, “Team Alpha”, “Team Beta” and “Team Gamma”.
As value for their FTP Folders, we use “team-alpha”, “team-beta” and so on.

That’s it for the taxonomy part! Now let’s link this information to the team’s users.

Adding a taxonomy term reference field to the user entity

In my case, I didn’t have multiple roles for the teams. I only had one, called “Team member”. Because every team has exactly the same rights as the others, maintaining only one role suited me best.
For really special cases, I could always just create a new role with the special permissions.

So, how do we link users to their teams the easiest? Exactly, by just adding a taxonomy term reference field to the user entity!
Let’s call this field “Team” and reference our previously created taxonomy vocabulary “Teams” with it.

Now, when adding a new user, we can select it’s team belonging and IMCE will be able to grab the needed information from there.
Yes, IMCE will be able to do that but it’s not doing it yet.
Getting the teams ftp folder for the current user is still something we have to code, so let’s proceed to the next step.

Writing a custom function to provide the accessible directories for an user

Now we need to provide IMCE the information that we’ve set up before.
We’ve created users belonging to teams, which hold the FTP root folder name for the teams.
What’s left to do, is to write a function (ideally in a custom module, in my example the module is called “awesome_teams”), that combines all information and returns it to IMCE.
Following function would do that for us:

function awesome_teams_imce($user) { $user_folders = array('cms/teams/all'); $user_wrapper = entity_metadata_wrapper('user', $user); $user_teams = $user_wrapper->field_team->value(); foreach ($user_teams as $user_team) { $user_team_wrapper = entity_metadata_wrapper('taxonomy_term', $user_team); array_push($user_folders, 'cms/teams/' . $user_team_wrapper->field_ftp_folder->value()); } return $user_folders; }

1

2

3

4

5

6

7

8

9

10

11

12

13

function awesome_teams_imce($user) {

  $user_folders = array('cms/teams/all');

  $user_wrapper = entity_metadata_wrapper('user', $user);

  $user_teams = $user_wrapper->field_team->value();

  foreach ($user_teams as $user_team) {

    $user_team_wrapper = entity_metadata_wrapper('taxonomy_term', $user_team);

    array_push($user_folders, 'cms/teams/' . $user_team_wrapper->field_ftp_folder->value());

  }

  return $user_folders;

}

The function expects an user object as argument and will return an array of strings containing all the folder names an user is allowed to access.
Our folder structure would look like this:

  • sites/default/files/cms
  • sites/default/files/cms/teams
  • sites/default/files/cms/teams/all
  • sites/default/files/cms/teams/team-alpha
  • sites/default/files/cms/teams/team-beta
  • sites/default/files/cms/teams/team-gamma

Note: The folder “cms/teams/all” is a special folder and every user is allowed to access it.
It will be used to save files which are used globally over multiple or even all teams.

What our code does, is actually looping over all assigned teams for the given user (yes, an user can be in multiple teams!), and adding the teams ftp folder names to the array of accessible folders.

There is no “hook_imce” hook, the “_imce” in the function name does nothing till now. You can also name your function differently. The link from IMCE to our function is something we have to set up in an IMCE profile.
Let’s proceed to the last step then, shall we?

Creating the IMCE profile “Team member”

Now, as the last step, let’s create an IMCE profile called “Team member”. You’re free to define any settings as you like, there’s only one thing that will be special about this profile: The accessible directories path.

Instead of writing something constant as “cms/teams/team-alpha”, we’ll write “php: return awesome_teams_imce($user);” here.
So, the setting should look like this:

imce-profile-settings

Now save the profile and you are done!

As soon as one team member now accesses the IMCE page (either via /imce or by the configured file/image fields), he will only see his team’s directories and the special directory “all” which is meant for exchange.

This wasn’t that difficult, was it?

I hope I was able to give you an insight on how to solve more complicated file permission issues with IMCE.
Don’t forget to give feedback, ask questions and follow our blog if you want to read more about our Drupal experiences at Liip!

May 11 2016
May 11

tl;dr Keep your site modules up-to-date.

Drupal is famous for its security and it also does not miss a chance to boast about it. However, security does not come automatically, steps need to be taken to ensure it. One of the most important of these steps is keeping site modules and core updated. Failing to do so can lead to incidents like the recent Panama papers incident where an outdated WordPress and Drupal site might have played a role in the data leak.

So what does Drupal do for you to make security easier?

The Drupal Security Team was set up in 2005. It has around 40 security experts from all around the globe who communicate through private channels.

When a security issue is discovered in Drupal (let it be a contributed module, a theme or core itself) an issue is created in the security issue tracker. The issue is visible only for a small group of people (usually the security team, the maintainer of the affected module and the reporter of the issue) to prevent the vulnerability to be exploited before a fix is created. When a fix is ready, the security team issues a public Security Advisory that has informations on the affected module, the security risk level and the solution for the issue (which is usually updating the module).

Security issues are reported almost daily to the security team but some of these are non valid. For example, only modules with a stable release (i.e. non-dev/alpha/beta/rc) are considered by the Security Team. Still, 2015 saw 160 security advisories. The most frequent issues are related to XSS.

Security updates are released on Wednesdays. For core that’s usually the third Wednesday of the month, for contrib it can be any Wednesday. This does not mean that a security release appears on every Wednesday, only that site administrators should look out for them.

In Drupal 8 there are several security improvements. One of them is Twig autoreplacing which drastically decreases the chances for a piece of code to have a XSS vulnerability. Another source of insecurities, the PHP filter module has been removed from core. Also, the routing system now has support for protection against CSRF attacks by providing tokens to urls.

After learning what Drupal does for security, it’s time to see what site administrators should make sure of. Keeping the following 3 things in mind you as site admin should be fine for 95% of the cases.   (These are only the Drupal-specific aspects, we won’t go into general security principles.)

  1. To make sure you have an up-to-date site follow at least one of the security news channels. There are some RSS feeds, a twitter account and also a newsletter. Update your site as soon as a security update is released.
  2. There are several modules improving security or helping in finding security issues. A few of these are Security review, Paranoia and Two factor authentication.
  3. A Drupal-specific hosting provider can also have its benefits. For example, in the case of the infamous 2014 Drupalgeddon security advisory Pantheon and Acquia Cloud sites were protected against attacks without any action taken by the site administrator.

If you have not done it yet go and check your module update status page right now.

This blog post is heavily based on the Lullabot podcast on Drupal Security.

For further links we recommend the Barcelona presentation of scor and klausi.

May 04 2016
May 04

We at Liip AG believe, that the migration API is the best and most efficient way to import data into Drupal. Here are some reasons, why you should use migrate instead of the feeds module or any other custom importer modules:

  • Since Drupal 8, Migrate API is part of Drupal core
  • Migrate will be maintained and supported as long as Drupal 8 exists as it provides the upgrade path for older Drupal versions to Drupal 8
  • Migrate is sponsored by Acquia and mainly supported by Mike Ryan, a well-known and skilled Drupal developer.
  • Migrate has out of the box support for all important Drupal objects such as nodes, users, taxonomy terms, users, files, entities and comments.
  • Migrate has a Drush integration, that allows you, to run import tasks from command-line or via cron job
  • Migrate maintains a mapping-table, has rollback functionality and even supporting a highwater field, that allows to import only new or changed datasets.
  • Migrate is well documented and there is an example module.

Getting started with Drupal 8 Migrate Module

The Migrate 8 module in core is only an API. There is no user interface. This makes it difficult for new developer to start with Migrate 8.

I suggest you, to install the below listed extension modules right away before you start developing if you want to realize the full potential of migrate:

Migrate Plus (https://www.drupal.org/project/migrate_plus)

  • Extends the migration framework with groups
  • Delivers a well documented example module

Migrate Tools (https://www.drupal.org/project/migrate_tools)

  • Provides Drush commands for running and managing migrations in Drupal 8

Migration Source Plugins

Installing a fully working Drupal 8 Migrate setup using composer

Instead of starting now to download in install all the module mentioned above, you can use my installation profile based on a composer.json file. Because a lot of modules with specific version are involved, I have prepared a fully working migrate example environment for a Liip hackday.

If you want to quickly start with the migrate module, head over to my github repository and install Drupal 8 Migrate using composer and drush. You just need to follow the instruction in the README.md

https://github.com/ayalon/drupal8-migrate

Comparing Drupal Migrate 7 with Drupal Migrate 8

Some of you might already have used Migrate 7. A traditional mapping was done in the constructor of a Migration class:

public function __construct($arguments) { parent::__construct($arguments); $this->description = t('Page Placeholder import'); // Set up our destination - nodes of type migrate_example_beer. $this->destination = new MigrateDestinationNode('page'); $this->csvFile = DRUPAL_ROOT . '/docs/navigation.csv'; $this->map = new MigrateSQLMap($this->machineName, array( 'ID' => array( 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, ), ), MigrateDestinationNode::getKeySchema() ); $this->source = new MigrateSourceCSV($this->csvFile, array(), array('header_rows' => 1, 'delimiter' => ';')); // Force update. $this->highwaterField = array(); // Mapped fields. $this->addFieldMapping('title', 'name'); $this->addFieldMapping('uid')->defaultValue(1); $this->addFieldMapping('status')->defaultValue(1); $this->addFieldMapping('promote')->defaultValue(0); $this->addFieldMapping('sticky')->defaultValue(0); $this->addFieldMapping('language')->defaultValue('de'); // Unmapped destination fields. $this->addUnmigratedDestinations(array( 'body:format', 'changed', 'comment', 'created', 'is_new', 'log', 'revision', 'revision_uid', 'tnid', 'translate', )); }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

public function __construct($arguments) {

  parent::__construct($arguments);

  $this->description = t('Page Placeholder import');

  // Set up our destination - nodes of type migrate_example_beer.

  $this->destination = new MigrateDestinationNode('page');

  $this->csvFile = DRUPAL_ROOT . '/docs/navigation.csv';

  $this->map = new MigrateSQLMap($this->machineName,

    array(

      'ID' => array(

        'type' => 'int',

        'unsigned' => TRUE,

        'not null' => TRUE,

      ),

    ),

    MigrateDestinationNode::getKeySchema()

  );

  $this->source = new MigrateSourceCSV($this->csvFile, array(), array('header_rows' => 1, 'delimiter' => ';'));

  // Force update.

  $this->highwaterField = array();

  // Mapped fields.

  $this->addFieldMapping('title', 'name');

  $this->addFieldMapping('uid')->defaultValue(1);

  $this->addFieldMapping('status')->defaultValue(1);

  $this->addFieldMapping('promote')->defaultValue(0);

  $this->addFieldMapping('sticky')->defaultValue(0);

  $this->addFieldMapping('language')->defaultValue('de');

  // Unmapped destination fields.

  $this->addUnmigratedDestinations(array(

    'body:format',

    'changed',

    'comment',

    'created',

    'is_new',

    'log',

    'revision',

    'revision_uid',

    'tnid',

    'translate',

  ));

}

In Migrate 8 this format has been replaced with yaml files. The same mapping as above looks like that in Drupal 8:

# Migration configuration id: page_node label: Dummy pages migration_group: liip source: plugin: page_node # Full path to the file. Is overridden in my plugin path: public://csv/navigation_small.csv # The number of rows at the beginning which are not data. header_row_count: 1 # These are the field names from the source file representing the key # uniquely identifying each node - they will be stored in the migration # map table as columns sourceid1, sourceid2, and sourceid3. keys: - ID destination: plugin: entity:node process: type: plugin: default_value default_value: page title: name uid: plugin: default_value default_value: 1 sticky: plugin: default_value default_value: 0 status: plugin: default_value default_value: 1 promote: plugin: default_value default_value: 0 'body/value': body 'body/summary': excerpt #Absolutely necessary if you don't want an error migration_dependencies: {}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

# Migration configuration

id: page_node

label: Dummy pages

migration_group: liip

source:

plugin: page_node

# Full path to the file. Is overridden in my plugin

path: public://csv/navigation_small.csv

# The number of rows at the beginning which are not data.

header_row_count: 1

# These are the field names from the source file representing the key

# uniquely identifying each node - they will be stored in the migration

# map table as columns sourceid1, sourceid2, and sourceid3.

keys:

   - ID

destination:

plugin: entity:node

process:

type:

   plugin: default_value

   default_value: page

title: name

uid:

   plugin: default_value

   default_value: 1

sticky:

   plugin: default_value

   default_value: 0

status:

   plugin: default_value

   default_value: 1

promote:

   plugin: default_value

   default_value: 0

'body/value': body

'body/summary': excerpt

#Absolutely necessary if you don't want an error

migration_dependencies: {}

Understanding the new mapping with yaml files in Migrate 8

The mapping is quite straightforward.

  • First you have to define your Migrate source. In the example we have used a CSV source plugin. (https://www.drupal.org/node/2129649)
  • Then you can map the source fields to a Migrate destination. In our case, we use a node destination (https://www.drupal.org/node/2174881)
  • You can map now all source fields to destination fields, for example you map a column of the CSV file to the node title field
  • Every field can be processed and modified before it is passed to the final node field. There are a lot of useful process plugins like “default_value”, “callback” or “skip_if_empty”
  • You can find a list of all process plugins here: https://www.drupal.org/node/2129651
  • Of course you can easily write your own plugins and use them while migrating data

Example: Importing a menu tree and create dummy nodes using Drupal Migrate 8

For demonstration purpose I created a small module, that reads a menu tree from a CSV file and imports into Drupal.

The module is split into 2 tasks:

  1. Creating a page node for every row in the csv file
  2. Create a menu item for every row and attach it to the correct node

Migrate handles these dependencies in the yaml file:

config/install/migrate_plus.migration.menu_item.yml

1

config/install/migrate_plus.migration.menu_item.yml

Dependancies

migration_dependencies: required: - page_node

migration_dependencies:

required:

   - page_node

See the full module code here:

https://github.com/ayalon/drupal8-migrate/tree/master/web/modules/custom/migrate_menu

Developing your own Drupal 8 migrate modules and fighting caching issues

You have learned, that the whole migration mapping is now done in yaml files. But how about writing your own migration yaml files?

Unfortunately, there are some pitfalls for new Drupal 8 developers. Because of the Configuration Management Interface (https://www.drupal.org/documentation/administer/config) of Drupal 8, all yml files in the “config/install” directory are only imported when installing the module.

This is very impractical if you want to develop new configuration files. To address this, a module “Configuration Development” (https://www.drupal.org/project/config_devel) which resolves the caching issues can be installed. It is possible to import certain yml files on every request. But unfortunately drush commands are not supported yet. So we need to add all yaml files we want to import into a new section in our module.info.yml.

config_devel: install: - migrate_plus.migration.page_node - migrate_plus.migration.menu_item - migrate_plus.migration_group.liip

config_devel:

install:

   - migrate_plus.migration.page_node

   - migrate_plus.migration.menu_item

   - migrate_plus.migration_group.liip

Then we can run the following commands after updating the yml file. This will import the new configuration file into CMI.

drush cdi <module_name> drush cr

drush cdi <module_name>

drush cr

In short:

You always have to remember, that you have to import the yaml files and clear the cache after changing the mapping before executing the migration again.

Testing and running your migration

If your module is set up correctly, you can run “drush ms” to see if the migration is ready:

drush-1

Now you can run the migration using

drush mi <migration_name>

1

drush mi <migration_name>

drush-2

If you want to update already imported items you can use the –update option:

drush-3

Advanced example importing a JSON source into Drupal nodes

During the last hackday at Liip I wrote a small module that is consuming a JSON source from

http://jsonplaceholder.typicode.com importing some postings and comments to a Drupal 8 website.

The module is split into 3 tasks:

  1. A feature module that installs the node type and fields called “migrate_posts”
  2. A migration importing post nodes
  3. A migration importing comments and attaching them to the already imported nodes

You can find the feature module here:

https://github.com/ayalon/drupal8-migrate/tree/master/web/modules/custom/migrate_posts

The migration module itself is here:

https://github.com/ayalon/drupal8-migrate/tree/master/web/modules/custom/migrate_json_hackday

Migration Process Plugin

Inside the migration module from above you will find a simple process plugin. The subject field of a comment in drupal only accepts a certain number of chars by default. Therefore I wrote a small process plugin, that truncates the incoming subject string:

subject: plugin: truncate source: name

subject:

plugin: truncate

source: name

The process plugin needs an annotation (https://api.drupal.org/api/drupal/core%21core.api.php/group/annotation/8.2.x) to be picked up by the migration API. You can later refer to the id in the yaml file:

<?php namespace Drupal\migrate_json_hackday\Plugin\migrate\process; use Drupal\migrate\MigrateExecutableInterface; use Drupal\migrate\ProcessPluginBase; use Drupal\migrate\Row; use Drupal\Component\Utility\Unicode; /** * * @MigrateProcessPlugin( * id = "truncate" * ) */ class Truncate extends ProcessPluginBase { /** * {@inheritdoc} */ public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { return Unicode::truncate($value, 64); } }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

<?php

namespace Drupal\migrate_json_hackday\Plugin\migrate\process;

use Drupal\migrate\MigrateExecutableInterface;

use Drupal\migrate\ProcessPluginBase;

use Drupal\migrate\Row;

use Drupal\Component\Utility\Unicode;

/**

*

* @MigrateProcessPlugin(

*   id = "truncate"

* )

*/

class Truncate extends ProcessPluginBase {

  /**

   * {@inheritdoc}

   */

  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {

    return Unicode::truncate($value, 64);

  }

}

You can see the whole module code under:

https://github.com/ayalon/drupal8-migrate/tree/master/web/modules/custom/migrate_json_hackday

Final Words

At the moment, there migration is under heavy development. For Drupal 8.1 a lot of changes have been made and these changes were breaking a lot of helper modules. Especially the Migrate UI is not working since several months now under Drupal 8.1. You can see more information under https://www.drupal.org/node/2677198.

But nevertheless, Migrate 8 is getting more and more mature and stable. And it’s time to learn the new yaml syntax now! If you have any suggestions, just drop a comment.

Apr 28 2016
Apr 28
      This blog describes about how to create a new node programmatically in Drupal 7. If you want to add a new node, you can done at node/add by default. In Drupal, you can also add a node programmatically. Let see the below code.

<?php
// create object
  $node = new stdClass();
  // set title for a node
  $node->title = t('Created node programmatically');
  // set node type
  $node->type = 'article';
  // set node language
  $node->language = LANGUAGE_NONE;
  // set value to node body
  $node->body[LANGUAGE_NONE][0]['value'] = t('This node has been created programmatically in Drupal 7');
  // set value to node body summary
  //$node->body[LANGUAGE_NONE][0]['summary'] = text_summary(t('This node has been created programmatically in Drupal 7'));
  // set node body format like plain_text, filtered_html, full_html
  $node->body[LANGUAGE_NONE][0]['format'] = 'filtered_html';
  node_object_prepare($node);
  // author for a node
  $node->uid = 1;
  // status of node  0 - unpublished, 1 - published
  $node->status = 1;
  // promoted to front page or not
  $node->promote = 0;
  // sitcky at top of tha page
  $node->sticky = 0;
  // comments 0 - hidden, 1 - closed, 2 - opened
  $node->comment = 1;

  // add term
  $node->field_tags[$node->language][]['tid'] = 1;

  // get the file path
  $file_path = drupal_get_path('module', 'phponwebsites') . '/Desert.jpg';
  // create file object
  $file = (object) array(
    'uid' => 1,
    'uri' => $file_path,
    'filemime' => file_get_mimetype($file_path),
    'status' => 1,
  );
  // Save the file to the public directory. You can specify a subdirectory, for example, 'public://images'
  $file = file_copy($file, 'public://');
  // assign the file object into image field
  $node->field_image[LANGUAGE_NONE][0] = (array)$file;
  // Prepare node for a submit
  $node = node_submit($node);
  //save the node
  node_save($node);


    After ran this code, you can see newly created node at admin/content. When you view that node, it looks like below image:

Create a new node programmatially in Drupal 7 at Phponwebsites

     Now I’ve hope you know how to create a new node programmatically in Drupal 7.
Related articles:
Add new menu item into already created menu in Drupal 7
Add class into menu item in Drupal 7
Create menu tab programmatically in Drupal 7
Add custom fields to search api index in Drupal 7
Clear views cache when insert, update and delete a node in Drupal 7
Create a page without header and footer in Drupal 7
Login using both email and username in Drupal 7
Redirect users into any page after logged into a site in Drupal 7
Apr 21 2016
Apr 21
  This blog describes about how to add class into menu item that is created programmatically  using hook_menu() in drupal 7.
        We know how to add custom menu item into already created menu in drupal7. Is it possible to add class to that menu item in durpal 7? Yes you can add custom classes into menu item using hook_menu() in drupal 7.

Add class into menu item in drupal 7:

     
       Consider below program to add class into menu item in drupal 7.

/**
 * Implement hook_menu()
 */
function phponwebsites_menu() {
  $items['sample'] = array(
    'title' => t('Sample page'),
    'type' => MENU_NORMAL_ITEM,
    'menu_name' => 'main-menu',
    'page callback' => 'samplepage',
    'access callback' => TRUE,
    'options' => array(
      'attributes' => array(
        'class' => array('drupal-menu-class')
      )
   ),
  );

  return $items;
}

/**
 * Implement samplepage()
 */
function samplepage() {
  $str = t('Hi this is sample page');
  return $str;
}


Where,
     type – MENU_NORMAL_ITEM
     menu-name – name of the menu to add new link
     options – add any attributes like class, id

You need to clear cache to see created menu items with custom class into main menu.

Add multiple classes into menu item in drupal 7:


     Similarly you can add multiple classes into menu item using hook_menu() in drupal 7. Consider below program to add multiple
classes into menu item.

function phponwebsites_menu() {
  $items['sample'] = array(
    'title' => t('Sample page'),
    'type' => MENU_NORMAL_ITEM,
    'menu_name' => 'main-menu',
    'page callback' => 'samplepage',
    'access callback' => TRUE,
    'options' => array(
      'attributes' => array(
        'class' => array('drupal-menu-class' ,  'drupal-menu-new-class')
      )
   ),
  );

  return $items;
}


       Now i’ve hope you know how to add class programmatically to menu item using hook_menu() in drupal 7.
Related articles:
Add new menu item into already created menu in Drupal 7
Create menu tab programmatically in Drupal 7
Add custom fields into apache solr search api index in Drupal 7
Redirect users after login in Drupal 7
Apr 21 2016
Apr 21

     This blog describes about how to add a new menu item into menu like main menu, user menu in drupal 7.

drupal 7 - add link into menu programmatically using hook_menu()
     
We can create a menu item using hook_menu in drupal 7. Can we add menu item into already created menu in drupal7? Yes you can add a link into menu using hook_menu().

Add new menu item into main menu in drupal 7:


       Consider below program to add new menu item into main menu in drupal 7.
/**
 * Implement hook_menu()
 */
function phponwebsites_menu() {
  $items['sample'] = array(
    'title' => t('Sample page'),
    'type' => MENU_NORMAL_ITEM,
    'menu_name' => 'main-menu',
    'page callback' => 'samplepage',
    'access callback' => TRUE,
  );

  return $items;
}

/**
 * Implement samplepage()
 */
function samplepage() {
  $str = t('Hi this is sample page');
  return $str;
}



Where,      type – MENU_NORMAL_ITEM      menu-name – name of the menu to add new link         You need to clear cache to see created new menu item in main menu. Now i’ve hope you know how to add new link programmatically to already created menu in drupal 7.

Related articles:
Add class into menu item in Drupal 7
Create menu tab programmatically in Drupal 7
Add custom fields into apache solr search api index in Drupal 7
Redirect users after login in Drupal 7
Apr 21 2016
Apr 21
    This blog describes about how to create menu tab programmatically in drupal 7. We can create menu items using hook_menu().

Create menu tab programmatically

Menu tab creation in Drupal 7:


     Consider below code snippet to create menu tab in drupal 7.

/**
 * Implement hook_menu()
 */
function phponwebsites_menu() {

  $items['test'] = array(
    'title' => t('Create Menu Tab'),
    'page callback' => 'testpage_tab1',
    'access callback' => TRUE,
  );

  $items['test/tab1'] = array(
    'title' => t('First Tab'),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'page callback' => 'testpage_tab1',
    'access callback' => TRUE,
  );

  $items['test/tab2'] = array(
    'title' => t('Second Tab'),
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'testpage_tab2',
    'access callback' => TRUE,
  );

  $items['test/tab3'] = array(
    'title' => t('Third Tab'),
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'testpage_tab3',
    'access callback' => TRUE,
  );

  return $items;
}

/**
 * Implement testpage_tab1()
 */
function testpage_tab1() {
  $str = t('Hi this is first tab');
  return $str;
}

/**
 * Implement testpage_tab2()
 */
function testpage_tab2() {
  $str = t('Hi this is second tab');
  return $str;
}

/**
 * Implement testpage_tab3()
 */
function testpage_tab3() {
  $str = t('Hi this is third tab');
  return $str;
}

Where,
   In hook_menu,
      Title – page title
      Type – type of menu item,
       MENU_CALLBACK, MENU_DEFAULT_LOCAL_TASK, MENU_LOCAL_TASK, MENU_LOCAL_ACTION, MENU_NORMAL_ITEM, MENU_SUGGESTED_ITEM are types of
menu item in drupal 7.

MENU_CALLBACK – register path for a menu item
MENU_DEFAULT_LOCAL_TASK – default tab for a menu
MENU_LOCAL_TASK – additional tabs for menu
MENU_LOCAL_ACTION – actions for menu items
MENU_NORMAL_ITEM – add menu item into any menus like main_menu, user_menu
MENU_SUGGESTED_ITEM – module may suggest menu items

Page callback – callback for a menu
Access callback – who can access the page

        Now i’ve hope you should know how to create menu tab programmatically in drupal 7.

Related articles:
Add new menu item into already created menu in Drupal 7
Add class into menu item in Drupal 7
Add custom fields into apache solr search api index in Drupal 7
Redirect users after login in Drupal 7
Apr 21 2016
Apr 21
     This is blog describes about how to add custom field to Search API solr index in Drupal 7.

     Suppose we need a new field, we can add new fields for a content type at /admin/structure/content/ types. Then all fields are showed at /admin/config/search/search_ api/index/default_node_index/ fields. Now you can add desired fields to solr index.

     Suppose you want to show custom field to Search API results but that field is not created for any specific content types. Is it possible in Search API? Yes you can done this with use of hook_entity_property_info_ alter().

/**
 * Implement phponwebsites_get_nodecountviews_nid()
 *
 * Return count of views by particular nid
 */
function phponwebsites_get_nodecountviews_nid($ nid) {
  $result = db_query("SELECT COUNT(*) as count FROM {nodeviewcount} WHERE nid=:nid", array(':nid' => $nid))->FetchAssoc();
  return $result['count'];
}

/**
 * Implements hook_entity_property_info_ alter().
 */
function phponwebsites_entity_property_info_ alter(&$info) {
  $info['node']['properties'][' is_nodeviewcount'] = array(
    'type' => 'integer',
    'label' => t('Node view count'),
    'description' => t("Number of views."),
    'sanitized' => TRUE,
    'getter callback' => 'phponwebsites_get_is_nodeviewcount_ callback',
  );
}

/**
 * Implement phponwebsites_get_is_nodeviewcount_ callback()
 */
function phponwebsites_get_is_nodeviewcount_ callback($item) {
  $count = phponwebsites_get_nodecountviews_nid( $item->nid);
  $total = (int) $count;
  return $total;
}


          After added above code into your custom module, go to /admin/config/search/search_ api/index/default_node_index/ fields. Now you could see new custom field is displayed as in below images.

Add custom fields to search API solr index in Drupal 7

         Now you can add your custom field into search api solr index and index that field. The custom field is listed in views add field section. Now you can add custom field into search results.

Related articles:
Add new menu item into already created menu in Drupal 7
Add class into menu item in Drupal 7 Create menu tab programmatically in Drupal 7
Redirect users after login in Drupal 7
Apr 21 2016
Apr 21
    This blog describes how to hide "Promoted to front page" and "Sticky at top of lists" options from node form in drupal 7. When adding or editing a node, you can see "Publishing options" at bottom of the page which contains 'Published', 'Promoted to front page' and 'Sticky at top of lists' checkbox options. It should look like below image:

Promoted to front page & Sticky at top of lists in Drupal 7
       The "Published" option is used to publish the content. The "Promoted to front page" option is used to display content in the front page. The 'Sticky at top of lists' option is used to keep the content sticked to the top of front page. If you don't want to show "Promoted to front page" and "Sticky at top of lists" options, then you can hide those options using hook_form_alter(), hook_form_FORM_ID_alter() and hook_form_BASE_FORM_ID_alter().

Hide Promoted to front page & Sticky at top of lists options in single node form:


       If you want to hide "Promoted to front page" and "Sticky at top of lists" options only in single node form, then you can remove those options from node form using either hook_form_alter() or hook_form_FORM_ID_alter() in drupal 7.  For example, we go to hide those options from article node form.
/**
 * Implement hook_form_alter().
 */
function phponwebsites_form_alter(&$form, &$form_state, $form_id) {
  // to hide promoted to front page option
  if (isset($form['options']['promote'])) {
    $form['options']['promote']['#access'] = FALSE;
  }

  // to hide sticky at top of lists option
  if (isset($form['options']['sticky'])) {
    $form['options']['sticky']['#access'] = FALSE;
  }
}


     Now you go to article node form and check whether "Promoted to front page" and "Sticky at top of lists" options are hidden or not. You couldn’t see those options in article node form. It should look like below image:
Hide Promoted to front page & Sticky at top of lists in drupal 7

Hide Promoted to front page & Sticky at top of lists options in multiple node forms:


     If you want to hide "Promoted to front page" and "Sticky at top of lists" options in all node forms, then you can remove those options using  hook_form_BASE_FORM_ID_alter() in drupal 7.
/**
 * Implement hook_form_BASE_FORM_ID_alter().
 */
function phponwebsites_form_node_form_alter(&$form, &$form_state, $form_id) {
  // to hide promoted to front page option
  if (isset($form['options']['promote'])) {
    $form['options']['promote']['#access'] = FALSE;
  }

  // to hide sticky at top of lists option
  if (isset($form['options']['sticky'])) {
    $form['options']['sticky']['#access'] = FALSE;
  }
}


     Now you could not see those options in all node forms. Now you know how to hide "Promoted to front page" and "Sticky at top of lists" options from node form in drupal 7. Related articles:
Add new menu item into already created menu in Drupal 7
Add class into menu item in Drupal 7
Create menu tab programmatically in Drupal 7
Add custom fields to search api index in Drupal 7
Redirect users after login in Drupal 7
Apr 21 2016
Apr 21
   This blog describes about how to login using both email and username in Drupal 7. All of you know we could login using only username in Drupal 7.

Login using mail address and usename in Drupal 7

       I've tried to login using email without any contrib modules. Finally i got the code. First alter form to add custom form validation. In custom form validation, get the name from user table by email and set that value into name field in form.  Let see the code:

<?php
/**
 * Implement hook_form_alter().
 */
function phponwebsites_form_alter(&$form, &$form_state, $form_id) {

  if ($form_id == "user_login" || $form_id == "user_login_block") {
    $form['name']['#title'] = t('Username or E-mail Address');
    // Ensure a valid validate array.
    $form['#validate'] = is_array($form['#validate']) ? $form['#validate'] : array();
    // login using username or email address
    array_unshift($form['#validate'],'phponwebsites_user_login_validate');
  }
}

 /**
 * Implement phponwebsites_user_login_validate()
 *
 * Return name by its email address
 */
function phponwebsites_user_login_validate($form, &$form_state) {
  if (isset($form_state['values']['name']) && strpos($form_state['values']['name'], '@') !== false) {
      $name = db_query("SELECT name FROM {users} WHERE LOWER(mail) = LOWER(:name)", array(':name' => $form_state['values']['name']))->fetchField();
    }
  if (isset($name)) {
    form_set_value($form['name'], $name, $form_state);
  }
}


   Now you can login using both username and email. I've hope you know how to login using both username and email in Drupal 7.

Related articles:
Add new menu item into already created menu in Drupal 7
Add class into menu item in Drupal 7
Create menu tab programmatically in Drupal 7
Add custom fields to search api index in Drupal 7
Clear views cache when insert, update and delete a node in Drupal 7
Create a page without header and footer in Drupal 7
Redirect users after login in Drupal 7
Apr 21 2016
Apr 21
This blog describes how to clear views cache while inserting, updating and deleting a node in Drupal 7. If we want to improve site performance, then views caching is one of the options.

   For example, you have views which display list of records. It will update occasionally. Then we can render views data from cache rather than server if we set cache for views. We can set views cache at its settings page. Suppose you have cached views for 5 mins. Then it didn't display updated data until 5 mins even if new node is added to that views. It displays updated data only after 5 mins because the views is cached for 5 mins. In that situation, the user can't view new data in cached views. So we need to clear views cache when add , update and delete a node. So only we can see new data in views and also data is rendered from cache.

Clear views cahce when insert, update and delete a node in drupal 7

Clear views cache when insert a new node in Drupal 7:

   The newly added node has not been displayed in views list if the cache is applied to a views. So we need to clear views cache when insert a new node using hook_node_insert(). Lets see the code for clear views cache while inserting a node:

 <?php
 /**
  * Imeplement hook_node_insert().
  */
 function phponwebsites_node_insert($node) {
   if ($node->type == 'tasks') {
     //clear views cache
     $viewsname = 'activity';
     cache_clear_all($viewsname, 'cache_views_data', TRUE);
   }
 }

Clear views cache when update a node in Drupal 7:

   When you tried to update a node, the updated data in that node has not been displayed in views. So we need to clear views cache when update a node using hook_node_update(). Lets see the code for clear views cache while updating a node:

 <?php
 /**
  * Imeplement hook_node_update().
  */
 function phponwebsites_node_update($node) {
   if ($node->type == 'article') {
     //clear views cache
     $viewsname = 'articles';
     cache_clear_all($viewsname, 'cache_views_data', TRUE);
   }
 }

Clear views cache when delete a node in Drupal 7:

   After delete a node, you could see the deleted node is displayed in the views. So we need to clear views when delete a node using hook_node_delete(). Lets see the code for clear views cache while deleting a node:

 <?php
 /**
  * Imeplement hook_node_delete().
  */
 function phponwebsites_node_delete($node) {
   if ($node->type == 'article') {
     //clear views cache
     $viewsname = 'articles';
     cache_clear_all($viewsname, 'cache_views_data', TRUE);
   }
 }
   You can see the performance of views page will be increased and you can see changes in your views. Now I've hope you know how to clear views cache when insert, update and delete a node in Drupal 7.

Related articles:
Add new menu item into already created menu in Drupal 7
Add class into menu item in Drupal 7
Create menu tab programmatically in Drupal 7
Add custom fields to search api index in Drupal 7
Login using both email and username in Drupal 7
Create a page without header and footer in Drupal 7
Redirect users after login in Drupal 7
Apr 21 2016
Apr 21
    This blog describes about create only page contents without header and footer in Drupal 7. All of you know almost all of the pages in Drupal have header and footer. Suppose you want to create a page without header and footer in Drupal 7. Is it possible? Yes, it is possible in Drupal 7. You can create a page without header and footer using 'delivery callback' in hook_menu.

Render a page without header and footer in Drupal 7:


     Drupal provide a option to create page without header and footer. Let see the below code for render a page without header and footer in Drupal 7.

/**
 * Implement hook_menu().
 */
function phponwebsites_menu() {
  $items['sample-wo-header-footer'] = array(
    'title' => 'A page without header and footer in Drupal 7',
    'access callback' => TRUE,
    'page callback' => 'phponwebsites_without_header_footer',
    'type' => MENU_CALLBACK,
    'delivery callback' => 'deliver_plain',
  );
  return $items;
}

function deliver_plain($page_callback_result) {
  print $page_callback_result;
}

/**
 * Implement phponwebsites_without_header_footer().
 */
function phponwebsites_without_header_footer() {
  return 'This is the page without header and footer';
}

   You could see the page without any header and footer when you view page in a browser. Now I've hope you how to render a page without header and footer in Drupal 7.


Related articles:
Add new menu item into already created menu in Drupal 7
Add class into menu item in Drupal 7
Create menu tab programmatically in Drupal 7
Add custom fields to search api index in Drupal 7
Login using both email and username in Drupal 7
Clear views cache when insert, update and delete a node in Drupal 7
Redirect users after login in Drupal 7
Apr 21 2016
Apr 21
    This blog describes about how to redirect users after logged into a site in Drupal 7. By default, Drupal redirects users to user page after logged into a site.  Suppose you want to redirect users into any other pages as you want. Then you can done that in Drupal 7.
  You can redirect users after login in Drupal using the following two ways:
1. Redirect users after logged into a site using hook_user_login()
2. Redirect users after logged into a site using custom form submit

Redirect users after form submit in Drupal 7

Redirect users after logged into a site using hook_user_login:


     Drupal provides hook called hook_user_login to make changes while user login successfully. Let see the below code.

/**
 * Implement hook_user_login()
 */
function phponwebsites_user_login(&$form, &$form_state) {
 //add page here to where you want redirect users after login
  $form['redirect'] = '<front>';
}
    Now you can check whether you redirect to front page or not after login. Now  Drupal will be redirect you to front page.

Redirect users after logged into a site using custom form submit:


   Drupal have alternate method to redirect users after login. Ie, You need to add custom form submit handler to a form using hook_form_alter(). Then add a page to redirect users in that custom form submit handler in Drupal 7. Let see the below code.


/**
 * Implement hook_form_alter().
 */
function phponwebsites_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == "user_login" || $form_id == "user_login_block") {
    $form['#submit'][] = 'phponwebsites_custom_login_submit';
  }
}  
function phponwebsites_custom_login_submit(&$form, &$form_state) {
  //page to be redirect
  $form['redirect'] = '<front>';
}

Now you will be redirect to front page after logged into a drupal site. Now I’ve hope you should know how to redirect users after logged into a site in Drupal 7.

Related articles:
Add new menu item into already created menu in Drupal 7
Add class into menu item in Drupal 7
Create menu tab programmatically in Drupal 7
Add custom fields to search api index in Drupal 7
Clear views cache when insert, update and delete a node in Drupal 7
Create a page without header and footer in Drupal 7
Login using both email and username in Drupal 7
Apr 21 2016
Apr 21

The two biggest players in the Drupal 7 webshop field are Drupal Commerce (also known as DC1) and Übercart. DC1 actually started as an Übercart rewrite to make use of Drupal 7 APIs. After the split Übercart was ported to Drupal 7 too but it was still using Drupal 6 technologies.

Although still very much in development, it seems something similar will be true for Drupal 8 as well. The developers of DC2 (the Drupal 8 version of Drupal Commerce), lead by Bojan Živanović rewrote the whole system from scratch to make use of the huge changes in Drupal 8. They are active members of the Drupal developer community so they not only know but also form the actual best practices. While working on DC2 they have fixed many dozens of Drupal 8 core issues and much more in other contributed modules (such as Entity, Inline Entity Form, Profile).

A great realisation when rewriting Commerce was that several components of a webshop could be reused by other (not even necessarily webshop or Drupal) systems. Some typical examples are address formats, currencies and taxes. These components are usually a huge pain to maintain because of the small differences from country to country. So they have created standalone PHP libraries usually using authorative third party datasets such as CLDR for currency or Google’s dataset for address formats. Some of them are already used by other webshop solutions like Foxycart and developers even outside the Drupal community are giving feedback which makes maintaining them easier.

In the DC2 development process UI and UX has got a big emphasis already from the beginning. Based on research of existing webshop solutions the shop administration and checkout process has been redesigned by UX specialists. For example, the product creation process is quite confusing in DC1 and there’s not even a recommended way to do it. In DC2 this happens now in one single form which makes it super easy.

A new concept in DC2 is Stores. Stores represent billing locations and products can belong to one ore more stores. One use case is the need for different billing for customers from different countries. Another one is having a shop where sellers can open an account and sell their own products. In this case each seller has their own store.

There are many other new features and improvements like a new and flexible tax system (you can say things like: “from Jan 1st 2014 the VAT changes from 21% to 19%”), a redesigned checkout flow, different workflows for different order types etc.

DC2 is still in alpha phase and is not recommended for production use yet. Beta releases will already have upgrade paths between them and so can be considered for starting real sites with. Beta1 is expected for May.

Drupal Commerce is the most popular e-commerce solution for Drupal 7. Given the high quality code and responsiveness to developer, shop maintainer and customer needs I do not expect this to change in Drupal 8 either.

Sources:
Drupal Commerce 2 blog
Modules Unraveled podcast on Commerce

Drupal 8 Entity Validation and Typed Data Demonstration

Mar 31 2016
Mar 31

Drupal 8 Entity Validation and Typed Data Explained

Mar 29 2016
Mar 29

Theming Views in Drupal 8 – Custom Style Plugins

Mar 23 2016
Mar 23

GSoC 2016 opportunities for Voice, Video and Chat Communication

Mar 23 2016
Mar 23
Feb 24 2016
Feb 24

This is part 4 of a series investigating what to do with your Drupal 6 site as EOL has now come.

Part 1 - overview | Part 2 - the risks | Part 3 - the options | Part 4 - Drupal 7 or Drupal 8?

Should I upgrade my Drupal 6 site to 7 or 8?

Today Drupal 6 reaches retirement, or end of life (EOL) which means the Drupal community and security team will no longer officially support active development, security and bug fixing for the platform. We instead must focus our resources on maintaining Drupal 7 and 8.

Of course, since Drupal is open source software, even though EOL for Drupal 6 is today, some organizations will continue to operate their live websites on Drupal 6 for some time. Upgrading to a current, supported version of Drupal is recommended, but whether Drupal 7 or Drupal 8 is the best fit for your needs, is conditional. Drupal 8 provides enhanced multi-lingual capabilities, responsiveness out-of-the-box, improved UX with WYSIWYG out-of-the-box, in-place content editing, improved UX AND DX (developer experience) with staging and deployment improvements to name just a few advantages over Drupal 7. However, there are cases in which Drupal 8 may not be able to satisfy your organization’s needs cost-effectively in the short-term, before the community has matured around it. You may liken this comparison to deliberating on your next car purchase: a Tesla vs. the Honda Civic you’re accustomed to. The Tesla promises new and shiny features, but you may have to adjust to things like refueling with electricity, not gas, and automatic steering. With the Civic, you know you’re getting a reliable, solid machine that will get the job done, though it may not turn heads. Drupal 7 is the Civic; Drupal 8, the Tesla.

The if it ain’t broke don’t fix it perspective is valid as the enterprise-level websites that put Drupal on the map such as Whitehouse.gov and Weather.com are Drupal 7 sites.

Having said that, we’ll share the main factors to weigh in deciding which platform makes the most sense for your organization.

Top considerations in selecting Drupal 7 or 8

How familiar with OO PHP is the development team?

The mantra of Drupal 8 is getting off the island which means leveraging well-vetted systems and processes that already exist in the broader PHP and web development community. This is in contrast to the preceding Drupal approach which relied on custom, esoteric design decisions familiar only to the Drupal community, contributing to the steep learning curve for Drupal development. To that end, Drupal 8 was rewritten in object oriented PHP, a major architectural change. Although this point may be a bit Greek to non-technical site owners it is important to know how versed the team working on the upgrade is in object oriented programming, and is worth asking when vetting prospective development teams.

How complex is the site?

One of Drupal’s greatest strengths is its extensibility, and robust design to facilitate writing and maintaining custom code for specific client needs. Relatedly, the very large contributed module repository (33,289 as I write this) enables developers a wide array of “plug-and-play” functionality for free which enables fairly complex sites at a relatively low cost.

If your site has fairly complex functionality, it is likely that the development team(s) who have worked on it have installed many contributed modules, and/or written custom modules themselves.

A quick peek into your Drupal website files should shed some light on this:

 /sites/all/modules

Ideally previous developers would have had the foresight to distinguish directories between community contributed modules and custom modules written by them with the by implementing this simple directory structure:

 /sites/all/modules/contrib/
 /sites/all/modules/custom/

E.g. Tilthy Rich Compost

The fewer modules in those directories, the more you should lean toward Drupal 8 as the stronger candidate. If there are many, which there often are, the decision gets more complicated. You’ll likely need an estimate from a development team on the custom module functionality and you’ll want to consider if the previous customization you’ve requested is still relevant. In general, upgrading is a good time to trim the fat and focus on true organizational needs to simplify the process and limit future costs.

As far as contributed modules go, the reality is in the early months of Drupal 8 the quality and coverage of contributed modules will lag Drupal 7. The good news is there are public resources that can help you understand the status and availability of each module you require for your site which will help you make the determination of Drupal 8’s readiness for your needs.

Here are a few

In some cases a mission critical contributed module will not have an official Drupal 8 release, which may force the decision for Drupal 7 for budget reasons. We recently bid on a project that we recommended Drupal 7 since the project heavily relied on the Salesforce suite which Kosta has played a role in the development of.

The shibboleth authentication module is another module that some universities rely on, which has no official Drupal 8 path at the moment partially due to how complex the module is.

A final important point is that some of the most popular contributed Drupal 7 modules have been folded into Drupal 8 core. Therefore, a much higher threshold of sophistication can be achieved by out-of-the-box functionality with Drupal 8 than was previously possible with Drupal 7

What is my timeline to upgrade?

If you take the security concerns of unsupported software seriously, you want to upgrade as soon as possible. However, sometimes website upgrades are more beholden to internal budgeting than security threats. A general rule of thumb is the more time that goes by from today, the more you should lean toward Drupal 8. The contributed module community will continue to improve in the weeks, and months to come, and you’ll want to take advantage of the more mature and modern platform that is Drupal 8. Don’t forget, Drupal 7 was released in early 2011 so its foundation is over 5 years old as I write this.

What are my future website goals and budget?

The less your organization will budget for future site improvements, the stronger the pull for Drupal 8 to ensure your maximizing the time before you have to read an article like this again. The truth is, by the time we’re honestly talking about Drupal 10 (that which would phase out Drupal 8 if things continued as they have thus far) the upgrade path for a Drupal site, and the web in general will look very different. Predicting the web 5-7 years ahead of time is a tall task. Having said that, the total cost of ownership, and specifically upgrade burden for Drupal projects is something the community is looking at closely. A Drupal 6 to Drupal 8 upgrade path does exist and a lot of thought has been given to simplifying the release cycle which should ease the upgrade process as well. If your typical redesign cycle is within 2 to 4 years, you’re likely safe with a Drupal 7 site for at least that long.

Should I even be using Drupal at all?

A worthy partner should be knowledgeable enough to know when Drupal is not the right fit, and honest enough to share that information for you. We pride ourselves on being one of those. As broadly applicable as the Drupal platform is for modern web projects, it isn’t always the right choice. It’s worth asking the question of prospective teams if you should even be on Drupal.

Where does that leave me?

Ultimately, site owners should communicate clearly their needs and desires for the new and improved vision of their website, and lean on their web partner to guide them in the right direction with open and honest conversations. On new site builds leading up to and after the official release of Drupal 8.0.0 we have deferred the decision to the tail-end of our discovery phase. We have found making the most informed decision requires thorough analysis which takes time. I encourage you to take some time with your partners weighing the pros and cons.

Part 1 - overview | Part 2 - the risks | Part 3 - the options | Part 4 - Drupal 7 or Drupal 8?

Feb 03 2016
Feb 03

As of 1:23pm GMT on Febuary 3rd 2016 there are no dependencies in the Drupal 8.1.x git branch.

Why?

There was no reason to have them there. It makes the repository bigger, and therefore takes longer to download. No one else does it.

What does it mean?

Not much really.

If you download Drupal via the zip file or tarball then the drupal.org packager will run composer so the dependencies will be there for you.

If you submit a Drupal core patch on drupal.org then DrupalCi testbot will run composer install.

It’s only if you clone Drupal 8.1.x or higher directly from git, you will need to run composer install in the Drupal root directory

Top tip: composer install --prefer-source --no-interaction can often be quicker.

Please enable JavaScript to view the comments powered by Disqus.

blog comments powered by
Jan 25 2016
Jan 25

This is part 3 of a series investigating what to do with your Drupal 6 site as EOL approaches.

Part 1 - overview | Part 2 - the risks | Part 3 - the options | Part 4 - Drupal 7 or 8?

The options of operating Drupal 6 after EOL

Though some advisers (solicited or not) might tell you otherwise, you always have options as to what to do with your website if you’re running Drupal. One of them is of course nothing, and the others require more thought.

What is the right thing to do can be a hot-button topic. Some technologists are purists, and believe it sacrosanct to even consider running software beyond EOL. However, we appreciate that although ideally site owners would always have been prepared and budgeted for software upgrades, sometimes life happens, and other business priorities and budget availability don’t perfectly align with timing for a software upgrade.

Ben Affleck in the Hollywood movie Armageddon

One end of the spectrum is well captured by the following quotation from the Drupal security team that some (certainly not us :wink: alarmist reaction to one of the most prolific Drupal security vulnerabilities to-date colloquially referred to as “Drupalgeddon.”

You should proceed under the assumption that every Drupal 7 website was compromised unless updated or patched before Oct 15th, 11pm UTC, that is 7 hours after the announcement.

This is of course meant to err on the side of caution, but can also be very anxiety provoking. Similarly, when hearing of the 3 month window for Drupal 6 support after Drupal 8.0.0 was released “Le dendrite” is not wrong to say:

someone please settle my stomach

I was shocked to read this. it might seriously wreck me, i really hope i’m misunderstanding this

will my sites just die?

That entire thread is a pretty good exposé into the various perspectives and preparedness on upgrading Drupal 6 sites. If you’ve got a spare half-day, give it a once-over.

On the other hand, we have had at least two clients in 2015 who continued to run Drupal 5 (not a typo) websites in production successfully.

Neither end is entirely wrong, they just represent difference of opinion and available options.

The primary options for Drupal 6 site owners are

  1. Do not upgrade your website to a newer Drupal version
  2. Do upgrade your website to a newer Drupal version
  3. Use the time to seriously revision your website altogether

But how do you decide which option is best for you, the site owner?

That decision mostly rests on your organization’s

  • risk tolerance
  • likelihood to be targeted by hackers
  • budget to improve your website

Considerations to not upgrade

Choosing not to upgrade your website is typically dictated by your organization having neither internal technical expertise or sufficient funds to hire a partner. When this is the case, if a small budget can be afforded, a site audit is usually the next best option to have some peace of mind that you’re taking the measures you’re able to mitigate the risk of running outdated, unsupported software. A qualified partner can perform a thorough assessment of risk and make budget-conscious recommendations on how to best harden the site against outside threats short of doing the more costly full-site upgrade.

Primary attack vectors that we focus on lie in custom code, and under-supported contributed modules since core code represents much lower risk having been well vetted by years in the wild.

wolves in the wild

One other consideration independent of your specific website configuration is your server autonomy dictated by your hosting provider. Running old software is usually tolerated in a shared hosting environment for only so long before upgrades are enforced. Warning emails are often (not always) sent out in advance of server-level upgrades, however, it’s easy to miss those emails, and it hurts to be caught by surprise that some PHP version upgrade renders your site completely useless. Understanding the level of control you as the site owner has over your server is crucial.

Considerations to upgrade

Going through a typical Drupal upgrade (the update.php method) is usually the right option for you if you

  • Want to mostly preserve the site to be upgraded as-is.
  • Have a fairly straightforward site using few, and mainly popular contributed modules .
  • Decide you ought to be using supported software because you
    • have sensitive information within your site that would be disastrous if compromised.
    • have financial transactions or information housed within your site.
  • Have sufficient budget to do the work.

Upgrading a Drupal website is a non-trivial investment. We’ve definitely had conversations with clients to the tune of

“oh, upgrading is not a click of a button? Phooey!”

which is very understandable. The Drupal community’s stance of not retaining backwards compatibility for the sake of keeping pace with modern web development is solid and quite defensible as well. Acquia does a good job of addressing that in the “Support” section (unfortunately not directly linkable) in this informative article about total cost of ownership of a Drupal site, which is in itself a topic worth a lot of consideration and analysis.

When prospective clients are committed to the time-line and investment of an upgrade, we always advise them to seriously consider a comprehensive rebuild/redesign as it can often present the path of most value. It is uncommon that a client desires to invest significantly to create an exact replica of the site they built 3, 4, or 5 years ago.

In many cases, legacy content, functionality and style has outlived it’s relevancy after 3 years and it becomes more burdensome than valuable, in the same way a poorly maintained house on a nice lot may be less valuable than if the lot were empty.

Another consideration is whether or not Drupal is the right platform for your organization’s needs. The answer to this is usually yes, especially after there is organizational investment and familiarity, but for less complicated sites that are open to revisioning, there are other options that, for example, are free to host and have no upgrade requirements like the static site generator Jekyll from which our own web site is built. An honest consultant will tell you when Drupal is a good fit and when it’s not.

But alas, if you’re determined to stick with Drupal and you’d like to upgrade, the final consideration is what platform to upgrade to, Drupal 7 or 8?

Ah-ah-ah…

Dennis Nedry from Jurassic Park saying ah ah ah

that’s for the next episode!

Part 4 and beyond

Next, we’ll discuss Drupal 7 vs. Drupal 8 decision-making (or not Drupal at all!).

Part 1 - overview | Part 2 - the risks | Part 3 - the options | Part 4 - Drupal 7 or 8?

Jan 22 2016
Jan 22

Drupal 8 is out, Drupal 8.1 will be out before we know it, but it seems contrib is still catching up. One question that seems to keep coming up is around installing a Drupal 8 module. In this post I will look at the different ways to install a module and resolving dependencies.

Deploy, Search API Solr Search, and Commerce are just a few of the modules with Drupal 8 releases that require dependencies loaded via composer. This means you can’t just download the module’s ZIP file, unzip it in your modules directory, and enable it. You need to install the dependencies.

One simple way to do this is the Composer Manager module. This module has extensive documentation on how to use it. Essentially what it does it merge the composer.json that ships with core and the composer.json files from all the contrib module you have, then downloads the dependencies. You may notice that this will also update core dependencies, but this is a good thing! The core composer.json has been written in such a way that it won’t introduce API breaking dependencies and only uses stable releases. So you will benefit from any bug fixes or security fixes rolled out in these dependencies before they’re rolled out with Drupal.

The two other ways we’re going to look at involve directly using Composer.

Just the dependencies
It’s possible to carry on installing Drupal modules exactly as you always have, download the zip or tarball, then unzip it into your modules directory. As mentioned earlier this will not install the dependencies, therefore you will need to look inside the module’s composer.json file, see what the dependencies are, and install them manually. Let’s take Deploy module as an example, this depends on the dev-master version of relaxedws/replicator. So, go to your Drupal docroot and run the command composer require relaxedws/replicator:dev-master. This will add relaxedws/replicator to Drupal’s composer.json, download it, and put it in the vendor directory ready for the module to make use of. This will not change any other other dependencies you have. Then to update the dependencies you can either run composer update to update relaxedws/replicator and all core dependencies or composer update relaxedws/replicator to just update the relaxedws/replicator package.

The module too
If you install all you Drupal modules via composer, all of the dependencies will automatically be installed too. First you will need to add a new repository, so run composer config repositories.drupal composer https://packagist.drupal-composer.org in your Drupal docroot, this will add the https://packagist.drupal-composer.org repository to your drupal composer.json. Now you can install any module from Drupal.org via composer. So going back to the example of Deploy you can run composer require drupal/deploy:8.1.0-alpha5 in your Drupal docroot and it will install Deploy in the modules directory. It will also install key_value, multiversion, and relaxed, which are all Drupal modules required by Deploy. Furthermore it will install relaxedws/replicator as we know is a PHP package needed for Deploy, and doctrine/couchdb which is a PHP package needed for releaxedws/replicator.

I hope this helps those confused what to do with Drupal 8 modules that have composer dependencies. Now go do the smart thing, rebuild your site using Composer!

Please enable JavaScript to view the comments powered by Disqus.

blog comments powered by
Jan 22 2016
Jan 22

Overview

In my last blog post, I wrote about the virtues of Composer Manager and how it allows modules to depend on PHP libraries managed via Composer. Basically, Composer Manager allows us to easily use PHP libraries that exist outside of the Drupal ecosystem within our own projects.

In this post, I’ll show you how we:

Custom vs. Contrib?

But first, why didn’t we just use the MailChimp contributed module? Contributed modules are often a great option and offer many benefits, such as security, maintenance, and flexibility.

But there is a cost to installing all those contributed modules. As The Definitive Guide to Drupal 7 explains “The more modules you install, the worse your web site will perform.”

With each installed module comes more code to load and execute, and more memory consumption. And in some cases, contributed modules add complexity and features that just aren’t necessary for the required task.

In our case, the decision to go with a custom solution was easy:

  • The MailChimp contributed module had many features we didn’t need.
  • We were already using Composer Manager on the project to manage other module dependencies.
  • The custom module we were building already included logic to determine when to subscribe users to mailing lists (don’t worry, we made sure they opted in!)

All we needed was a simple, lightweight method for subscribing a given user to a specific MailChimp mailing list.

Implementation

We were able to achieve this by adding the MailChimp PHP library as a dependency of our custom module. We were then able to make a simple call using the API to subscribe a user to the mailing list. We implemented this via the following code.

First, in our module’s root directory we created a composer.json file that specified the MailChimp PHP library as a dependency:

{
  "require": {
    "mailchimp/mailchimp": "*"
  }
}

We then installed the Mailchimp API using the Composer Manager drush commands:

$ drush composer-json-rebuild
$ drush composer-manager install

As explained in my last post, the first command builds (or rebuilds) the consolidated project wide composer.json file and the second command installs the dependencies.

Next, we created a function in our custom module to subscribe a user to a MailChimp mailing list.

<?php
/**
 * Add an email to a MailChimp list.
 *
 * @param string $api_key
 *   The MailChimp API key.
 * @param string $list_id
 *   The MailChimp list id that the user should be subscribed to.
 * @param string $email
 *   The email address for the user being subscribed to the mailing list.
 */
function my_module_subscribe_user($api_key, $list_id, $email) {

  $mailchimp = new Mailchimp($api_key);

  try {
    $result = $mailchimp->lists->subscribe($list_id, array('email' => $email));
  }
  catch(Exception $e) {
    watchdog('my_module', 'User with email %email not subscribed to list %list_id', array('%email' => $email, '%list_id' => $list_id), WATCHDOG_WARNING);
  }
}

With that function defined, we could then subscribe any user to any mailing list by simply calling

<?php
my_module_subscribe_user($api_key, $list_id, $email);

Conclusion

That’s it! A nice, simple, and clean approach to subscribing users to a MailChimp mailing list that doesn’t require installation of the MailChimp contributed module.

We hope you’re as excited as we are at the opportunities Composer and Composer Manager afford us to take advantage of PHP libraries and projects that exist outside of the Drupal ecosystem.

Tutorial on Using Drupal 8 Plugin Derivatives Effectively

Jan 19 2016
Jan 19

Want to use free software to communicate with your family in Christmas 2016?

Jan 06 2016
Jan 06

Real-Time Communication in FOSDEM 2016 main track

Dec 29 2015
Dec 29

TADHack Paris - 12-13 December 2015

Dec 12 2015
Dec 12

Pages

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