Skip to Content

Planet Drupal

Syndicate content - aggregated feeds in category Planet Drupal
Updated: 11 hours 49 min ago

Daniel Pocock: Lumicall's 3rd Birthday

6 February 2015 - 1:33pm

Today, 6 February, is the third birthday of the Lumicall app for secure SIP on Android.

Happy birthday

Lumicall's 1.0 tag was created in the Git repository on this day in 2012. It was released to the Google Play store, known as the Android Market back then, while I was in Brussels, the day after FOSDEM.

Since then, Lumicall has also become available through the F-Droid free software marketplace for Android and this is the recommended way to download it.

An international effort

Most of the work on Lumicall itself has taken place in Switzerland. Many of the building blocks come from Switzerland's neighbours:

  • The ice4j ICE/STUN/TURN implementation comes from the amazing Jitsi softphone, which is developed in France.
  • The ZORG open source ZRTP stack comes from PrivateWave in Italy
  • Lumicall itself is based on the Sipdroid project that has a German influence, while Sipdroid is based on MjSIP which comes out of Italy.
  • The ENUM dialing logic uses code from ENUMdroid, published by Nominet in the UK. The UK is not exactly a neighbour of Switzerland but there is a tremendous connection between the two countries.
  • Google's libPhoneNumber has been developed by the Google team in Zurich and helps Lumicall format phone numbers for dialing through international VoIP gateways and ENUM.

Lumicall also uses the reSIProcate project for server-side infrastructure. The repro SIP proxy and TURN server run on secure and reliable Debian servers in a leading Swiss data center.

An interesting three years for free communications

Free communications is not just about avoiding excessive charges for phone calls. Free communications is about freedom.

In the three years Lumicall has been promoting freedom, the issue of communications privacy has grabbed more headlines than I could have ever imagined.

On 5 June 2013 I published a blog about the Gold Standard in Free Communications Technology. Just hours later a leading British newspaper, The Guardian, published damning revelations about the US Government spying on its own citizens. Within a week, Edward Snowden was a household name.

Google's Eric Schmidt had previously told us that "If you have something that you don't want anyone to know, maybe you shouldn't be doing it in the first place.". This statement is easily debunked: as CEO of a corporation listed on a public stock exchange, Schmidt and his senior executives are under an obligation to protect commercially sensitive information that could be used for crimes such as insider trading.

There is no guarantee that Lumicall will keep the most determined NSA agent out of your phone but nonetheless using a free and open source application for communications does help to avoid the defacto leakage of your conversations to a plethora of marketing and profiling companies that occurs when using a regular phone service or messaging app.

How you can help free communications technology evolve

As I mentioned in my previous blog on Lumicall, the best way you can help Lumicall is by helping the F-Droid team. F-Droid provides a wonderful platform for distributing free software for Android and my own life really wouldn't be the same without it. It is a privilege for Lumicall to be featured in the F-Droid eco-system.

That said, if you try Lumicall and it doesn't work for you, please feel free to send details from the Android logs through the Lumicall issue tracker on Github and they will be looked at. It is impossible for Lumicall developers to test every possible phone but where errors are obvious in the logs some attempt can be made to fix them.

Beyond regular SIP

Another thing that has emerged in the three years since Lumicall was launched is WebRTC, browser based real-time communications and VoIP.

In its present form, WebRTC provides tremendous opportunities on the desktop but it does not displace the need for dedicated VoIP apps on mobile handsets. WebRTC applications using JavaScript are a demanding solution that don't integrate as seamlessly with the Android UI as a native app and they currently tend to be more intensive users of the battery.

Lumicall users can receive calls from desktop users with a WebRTC browser using the free calling from browser to mobile feature on the Lumicall web site. This service is powered by JSCommunicator and DruCall for Drupal.

Categories: Drupal

Dries Buytaert: Growing Drupal in Latin America

6 February 2015 - 12:45pm

When I visited Brazil in 2011, I was so impressed by the Latin American Drupal community and how active and passionate the people are. The region is fun and beautiful, with some of the most amazing sites I have seen anywhere in the world. It also happens to be a strategic region for the project.

Latin American community members are doing their part to grow the project and the Drupal community. In 2014, the region hosted 19 Global Training Day events to recruit newcomers, and community leaders coordinated many Drupal camps to help convert those new Drupal users into skilled talent. Members of the Latin American community help promote Drupal at local technology and Open Source events, visiting events like FISL (7,000+ participants), Consegi (5,000+ participants) and Latinoware (4,500+ participants).

You can see the results of all the hard work in the growth of the Latin American Drupal business ecosystem. The region has a huge number of talented developers working at agencies large and small. When they aren't creating great Drupal websites like the one for the Rio 2016 Olympics, they are contributing code back to the project. For example, during our recent Global Sprint Weekend, communities in Bolivia, Colombia, Costa Rica, and Nicaragua participated and made valuable contributions.

The community has also been instrumental in translation efforts. On, the top translation is Spanish with 500 contributors, and a significant portion of those contributors come from the Latin America region. Community members are also investing time and energy translating Drupal educational videos, conducting camps in Spanish, and even publishing a Drupal magazine in Spanish. All of these efforts lower the barrier to entry for Spanish speakers, which is incredibly important because Spanish is one of the top spoken languages in the world. While the official language of the Drupal project is English, there can be a language divide for newcomers who primarily speak other languages.

Last but not least, I am excited that we are bringing DrupalCon to Latin America next week. This is the fruit of many hours spent by passionate volunteers in the Latin American local communities, working together with the Drupal Association to figure out how to make a DrupalCon happen in this part of the world. At every DrupalCon we have had so far, we have seen an increase in energy for the project and a bump in engagement. Come for the software, stay for the community! Hasta pronto!

Categories: Drupal

Aten Design Group: Removing Duplicate Content Across Multiple Drupal Views

6 February 2015 - 11:31am

Views is an indispensable and powerful module at the heart of Drupal that you can use to quickly generate structured tables or lists of consistently formatted content, and filter and group that content by simple or complex logic. But in pushing Views to do ever more complex and useful things, we can sort of paint ourselves into a corner sometimes. For instance, I have many times created multiple Views displays on a single page that contain overlapping content. My homepage has a Views display of manually curated content, using Nodequeue or a similar module. On the same homepage, I have a Views display of news content that shows the most recent content. Since the two different Views displays pull from the same bucket of content, it is very possible to have duplicate content across the displays. Here is an example:

Notice the underlined duplicate titles across the two Views displays.

This is what we want:

Notice the missing featured titles from the deduped Views display.

By creating a custom Drupal module and utilizing a Views hook, we can remove the duplicate content across the two Views displays. We programmatically check exactly which pieces of content are in one View, and we feed that information to a filter in the second View that excludes it.

Before diving into my example, I want to cover a few assumptions I’m making about you.
  • You are using Drupal 7
  • You are familiar with Views module
  • You know how to install modules
  • You know at least a touch of PHP
Steps to Follow Along

View Example Code on Github

Step 1

My example code assumes that you have created two Views displays.

  • Featured - A View display of manually curated content. This display will be used to generate a list of content to exclude from our automated Views display.
  • Automated - A View display of news content that shows the most recent content. This display will accept a list of content to be excluded.

You can of course adapt the Views displays to your exact needs.

After creating the Views you wish to use, you’ll need to know the machine name of the View and View display.

One way to retrieve these names is from the view edit URL. While editing your view, notice the URL:


In my case, automated_news is the view name and block is the view display name.

Make a note of your machine names for Step 3

Step 2

On the view you wish to dedup or exclude content from, you’ll need to add and configure a contextual filter.

  1. Navigate to edit the automated content view
  2. Under “Advanced” & “Contextual Filters”, click add and select “Content: Nid (The node ID.)”
  3. Select “Provide default value” and choose “Fixed value”.
  4. Leave the Fixed value empty as we’ll provide this in code
  5. Under “More” select “Allow multiple values” and “Exclude”
  6. Save the view
Step 3

Enable your custom module that contains the deduping code. You are welcome to download the example module on Github and use it, or add the code to an existing custom module if it makes more sense. In any case, you’ll need to customize the module a little bit to work with your Views.

  1. Update the machine name variables from Step 1. See $featured_view_name, $featured_view_display, $automated_view_name and 2. $automated_view_display
  2. Save your module
  3. Enable your module
  4. Clear your Drupal cache

If everything was configured correctly, you should see your Views displays properly deduped.

Code Explained

View Example Code on Github

The code relies on hook_views_pre_view(), a Views hook. Using this hook, we can pass values to the Views display contextual filter set in Step 2. Here is a version where content IDs (NIDs) 1, 2, 5 & 6 are manually being passed to a view for exclusion.

/** * @implements hook_views_pre_view(). * * */ function hook_views_pre_view(&$view, &$display_id, &$args){ // Check for the specific View name and display if ($view->name == ‘automated_news’ && $display_id == ‘block’) { $args[] = 1+2+5+6; } }

There are many ways you could dynamically build a list of NIDs you wish to exclude. In my example, we are loading another Views display to build a list of NIDs. The function views_get_view() loads a Views display in code and provides access to the result set.

// Load the view // $view = views_get_view('automated_news'); $view->set_display('block'); $view->pre_execute(); $view->execute();   // Get the results $results = $view->result;

Drupal Views is a powerful module and I like the ability to extend it even further using the extensive Views hooks API. In the case of my example, we can keep using Views with writing complex database queries.

Categories: Drupal

Annertech: 5 Tips for a Responsive Website

6 February 2015 - 10:36am
5 Tips for a Responsive Website

Last month I wrote about why we care about responsive websites, and why you should too. This month I'm going to brush the surface of how one might achieve such a goal.

Responsive Buzzword Bingo

I'm not about to go knee-deep into the semantics of the various jargon words surrounding this topic and their pros and cons, but here are broad descriptions of some of the approaches.

Categories: Drupal

Dcycle: Two tips for debugging Simpletest tests

6 February 2015 - 7:52am

I have been using Simpletest on Drupal 7 for several years, and, used well, it can greatly enhance the quality of your code. I like to practice test-driven development: writing a failing test first, then run it multiple times, each time tweaking the code, until the test passes.

Simpletest works by spawning a completely new Drupal site (ignoring your current database), running tests, and destroying the database. Sometimes, a test will fail and you're not quite sure why. Here are two tips to help you debug why your tests are failing:

Tip #1: debug()

The Drupal debug() function can be placed anywhere in your test or your source code, and the result will appear on the test results page in the GUI.

For example, if when you are playing around with the dev version of your site, things work fine, but in the test, a specific node contains invalid data, you can add this line anywhere in your test or source code which is being called during your test:

... debug($node); ...

This will provide formatted output of your $node variable, alongside your test results.

Tip #2: die()

Sometimes the temporary test environment's behaviour seems to make no sense. And it can be frustrating to not be able to simply log into it and play around with it, because it is destroyed after the test is over.

To understand this technique, here is quick primer on how Simpletest works:

  • In Drupal 7, running a test requires a host site and database. This is basically an installed Drupal site with Simpletest enabled, and your module somewhere in the modules directory (the module you are testing does not have to be enabled).
  • When you run a test, Simpletest creates a brand-new installation of Drupal using a special prefix simpletest123456 where 123456 is a random number. This allows Simpletest to have an isolated environment where to run tests, but on the same database and with the same credentials as the host.
  • When your test does something, like call a function, or load a page with, for example, $this->drupalGet('user'), the host environment is ignored and temporary environment (which uses the prefixed database tables) is used. In the previous example, the test loads the "user" page using a real HTTP calls. Simpletest knows to use the temporary environment because the call is made using a specially-crafted user agent.
  • When the test is over, all tables with the prefix simpletest123456 are destroyed.

If you have ever tried to run a test on a host environment which already contains a prefix, you will understand why you can get "table name too long" errors in certain cases: Simpletest is trying to add a prefix to another prefix. That's one reason to avoid prefixes when you can, but I digress.

Now you can try this: somewhere in your test code, add die(), this will kill Simpletest, leaving the temporary database intact.

Here is an example: a colleague recently was testing a feature which exported a view. In the dev environment, the view was available to users with the role manager, as was expected. However when the test logged in as a manager user and attempted to access the view, the result was an "Access denied" page.

Because we couldn't easily figure it out, I suggested adding die() to play around in the environment:

... $this->drupalLogin($manager); $this->drupalGet('inventory'); die(); $this->assertNoText('denied', 'A manager accessing the inventory page does not see "access denied"'); ...

Now, when the test was run, we could:

  • wait for it to crash,
  • then examine our database to figure out which prefix the test was using,
  • change the database prefix in sites/default/settings.php from '' to (for example) 'simpletest73845'.
  • run drush uli to get a one-time login.

Now, it was easier to debug the source of the problem by visiting the views configuration for inventory: it turns out that features exports views with access by role using the role ID, not the role name (the role ID can be different for each environment). Simply changing the access method for the view from "by role" to "by permission" made the test pass, and prevented a potential security flaw in the code.

(Another reason to avoid "by role" access in views is that User 1 often does not have the role required, and it is often disconcerting to be user 1 and have "access denied" to a view.)

So in conclusion, Simpletest is great when it works as expected and when you understand what it does, but when you don't, it is always good to know a few techniques for further investigation.

Tags: blogplanet
Categories: Drupal

OpenLucius: A robot in your Drupal social intranet / extranet – why and how?

6 February 2015 - 2:15am

If you work with a team on projects, then there are (obviously) tasks to share. Including tasks to be followed up by your clients.

For example: the delivery of a design in Photoshop/fireworks for their new social intranet.

Now it can happen that somebody does not follow-up on his/her task in time resulting in problems for your planning. Usually this is not on purpose, often they simply 'forgot'.

Categories: Drupal

Drupal core announcements: Princeton Critical Sprint Recap

5 February 2015 - 6:35pm

At the end of January, 2015, sprinters gathered in Princeton, NJ, USA for a focused D8 Accelerate sprint designed to accelerate work on critical and upgrade-path-blocking issues related to menus, menu links, and link generation.

The sprint was coordinated with the 4th annual DrupalCamp NJ. pwolanin, dawehner, kgoel, xjm, Wim Leers, mpdonadio, YesCT, effulgentsia, and tim.plunkett participated onsite. (In addition to the D8 Accelerate Group, local Drupalists davidhernandez, cilefen, crowdcg, wheatpenny, ijf8090, and HumanSky joined the sprint primarily to work on Drupal 8 Twig and theme issues, and EclipseGC and evolvingweb dropped in too.)

The sprint benefitted from pre-sprint planning meetings and discussion with the sprinters and a broader group of contributors (including webchick and catch, as well as amateescu, larowlan, Gábor Hojtsy, Bojhan, and Crell), and daily support from webchick to track, summarize, and unblock progress with issue posts and commits so the sprinters could move on to the next steps.

Thanks to the pre-sprint planning, sprint focus, and the tremendous experience of the participants and their history of working together on hard issues in the past, this sprint achieved a very high level and breadth of success. Sprinters worked on a total of 17 critical issues (14 of which are now fixed) as well as 27 other related bugs and DX fixes. All the issues opened or worked on during the sprint can bee seen under the tag D8 Accelerate NJ.

Take-away lessons

Identifying key issues in advance made the sprint more productive, as did meeting via video chat and in IRC to discuss possible solutions ahead of time. The pending deadline of the sprint helped push contributors to forge consensus and begin work on the issues before the event even happened. Never underestimate the value of a hard deadline!

As always, having the group in the same room (and timezone) with a whiteboard allowed resolution of discussions that would have taken weeks via issue comments and online meetings. We also were able to scale our progress with occasional pair programming and pair code review - very effective for ramping up skilled sprinters to unfamiliar and difficult problem spaces.

In addition, while the sprint was happening at the same time as DrupalCamp NJ activities (and for 2 days in the same building), the sprinters deliberately avoided the presentations or general Drupal mentoring they might have done in other circumstances. This relative lack of distractions was part of what we learned made the prior Ghent sprint a success and it helped maintain the focus at this sprint as well.

The sprinters stayed in 2 adjoining hotels, which made coordination easy.

Changing the sprint room each day initially seemed like it might be a drawback, but instead seemed to keep things a bit fresher. Note, however, that every room had windows and natural light - especially important the first days as people were dealing with jet lag.

It's off-season for New Jersey in January, so the low flight costs that allowed us to fund many more people to come and also accommodated people who made travel plans as late as a week prior to the event. This allowed us to recruit more participants even with a very short time frame to plan. (When the sprint was first given the D8 Accelerate Grant at the end of December, we had only 3 confirmed attendees and just a rough idea of the issues and goals to be addressed.)


The sprint was sponsored by a Drupal Association grant and by Princeton University Web Development Services providing space and logistical support.

In addition, Black Mesh sponsored all travel costs for YesCT, Forum One provided time off for kgoel, Night Kitchen Interactive provided time off for mpdonadio, and Acquia provided several employees' time (pwolanin, effulgentsia, xjm, tim.plunkett, and Wim Leers).

Daily sprint updates from webchick

These daily issue summaries were originally provided by webchick on [meta] Finalize the menu links system.

January 27

A very hyped snow storm leads to the cancelation of all 3 flights coming from Europe - but the snow fell further North and East, so all 3 participants were able to reschedule for the next day.

January 28

Most participants arrived in Princeton and settled in.

January 29

Day one of the sprint! Occupying the lounge at the NE corner of 701 Carnegie, part of the facilities of Princeton University.

Dinner plans were inspired by the DrupalCamp NJ theme for 2015 - a New Jersey diner! Just reading the menu was an exotic treat for the Europeans.

January 30

Occupying a multi-purpose room at the SE Corner of 701 Carnegie.

At the same time, about 70 people participated in 4 Drupal training courses in other rooms on the ground floor.

Thanks to the prompting of Tim Plunkett, dinner was real New Jersey pizza at Nino's Pizza Star in Princeton (a local favorite among the Central NJ Drupal meetup regulars). EclipseGC even treated the group to a Nutella pizza for dessert!

January 31

Occupying room 111 at the Friend Engineering Center, on the campus of Princeton University. In the neighboring rooms the sessions and BoFs were happening for the 4th annual DrupalCamp NJ. The sprinters were counted among the 257 registered attendees.

February 1

Occupying a (paid) meeting room at the hotel where most sprinters were staying.

Apparently there was some football game going on too.

While most people are headed home tomorrow, there are a few stalwart hangers-on who are staying through to Tuesday.

February 2

People worked together at the hotel or remotely. A Farewell lunch in Princeton was followed by a brief look at the Princeton University campus as a scenic amount of snow fell again.

Categories: Drupal

Mediacurrent: Introducing the Mediacurrent Dropcast!

5 February 2015 - 2:03pm

Our inaugural episode. Team Kool-Aide starts a podcast and we talk about a variety of topics taken from The Weekly Drop.

Your browser does not support the audio element.
Episode 0 Audio Download Link


Categories: Drupal

more onion - devblog: Stale static cache - you're likely to have seen this bug!

5 February 2015 - 1:18pm

This week I've finally found the core of several issues that I've had in the past. Are you using install-profiles or features? Then this bug is likely to have affected you too.

Categories: Drupal

Drupal core announcements: All the sprints at and around Drupal Dev Days Montpellier France

5 February 2015 - 11:57am
Start:  2015-04-13 09:00 - 2015-04-19 09:00 Europe/Zurich Sprint

We have a great tradition of extended sprints around big Drupal events including DrupalCons and Drupal Dev Days. Given that a lot of the Drupal core and contrib developers fly in for these events, it makes a lot of sense to use this opportunity to start sooner and/or extend our stay and work together in one space on the harder problems.

Drupal Dev Days Montpellier France is next up! Monday April 13 2015 to Sunday April 19 2015. The host event is looking for sponsors to help make the sprints happen, so you have a comfortable environment with internet, coffee, tea and maybe food. There are already various sprints signed up including Multilingual, Drupal 8 critical burndown, documentation, and Frontend. We are really friendly and need all kinds of expertise!

Now is the time to consider if you can be available and book your travel and hotel accordingly!

Join the sprinters -- sign up now! Practical details
April 13 to April 19
Times and locations
Day/Time Location April 13-19, 09:00 to 18:00. TBA, TBA April 13-19, 18:00 to 24:00. Hotel lobby, TBA, TBA.
Looking for sponsors

We are looking for more sponsors to be able to pay for extra expenses on the sprint too. If you are interested sponsoring or if you need sponsors to cover expenses, please contact me at

Frequently asked questions What is a sprint?

Drupal sprints are opportunities to join existing teams and further Drupal the software, our processes, and so on.

Do I need to be a pro developer?

No, not at all. First of all sprints include groups working on user experience, designs, frontend guidelines, software setup, testing improvements, figuring out policies, etc. However you can be more productive at most sprints if you have a laptop.

How come there are 7 consecutive days of sprints?

We are all travel to the same place. We try to use this time to share our knowledge as well as further the platform in all possible ways. Therefore there is almost always an opportunity and a place to participate in moving Drupal forward.

What if I'm new to Drupal and/or sprinting, how can I join?

There will be no formal mentoring, but there will be a place for you. Once you get there, hopefully someone will introduce themselves and help you find your place in the sprint. If not, please reach out and say, "Hi, I'm new to sprinting, but I want to help." And then, someone will find you a group of sprinters to join. Expect your first day to be mostly about finding a group or a couple issues, reading them, understanding, and getting set up to work on them. Your second day you will probably get some progress on things. And then your third, forth, etc day *you* will be getting things done (and maybe helping people who are there for their first day.)

I worked on Drupal before, which sprints are for me?

If you have experience with Drupal issues and maybe already know a team/topic, jump right in, but of course if you have questions, there are always plenty of friendly people to help you.

Why do I have to sign up?

These sprints are broken down to teams working on different topics. It is very important that you sign up for them, so we know what capacity to plan with, so we have enough space (and maybe food/coffee).

Further questions?

Ask at, I am happy to answer.

#node-427578 .picture, #node-427578 h3 { display: none; } #node-427578 .field-type-datestamp { margin: 0 0 2em 0; } #node-427578 dl { margin-bottom: 1em; } #node-427578 dd { margin-top: 0.5em; } #node-427578 h3.content { display: block; }
Categories: Drupal

Last Call Media: The Drupal Throbber Returns (Comic)

5 February 2015 - 10:17am
Categories: Drupal

Drupal Watchdog: Baby Steps

5 February 2015 - 9:39am

Ronnie is on the phone. Vanessa is on her iPad, chuckling.

RONNIE: (into phone) Jeremy, calm down – I understand. You need the "Baby Steps" column in three days. No problem, man, I'm on it... Okay. Cool. (he disconnects) Damn. (to Vanessa) What’s so funny?

VANESSA: This comic I follow on Twitter.

RONNIE: Did you know that Twitter is built on Drupal? So is YouTube and Facebook and –

VANESSA: – Glad to see you’re getting somewhere.

RONNIE: Hey, I already downloaded a WAMP server.

VANESSA: A what?

RONNIE: WAMP. It’s an acronym for Windows... Apache... MySQL... PHP.

VANESSA: Which means?

RONNIE: I have no idea.

VANESSA: You’re procrastinating.

RONNIE: No! Yeah. I didn't realize, when I proposed writing the column, how complicated Drupal is. You want coffee?

VANESSA: It's eleven o'clock. I'm going to bed.

RONNIE: I'll just watch one of the instructional videos they have.

VANESSA: Like last night?


VANESSA: You watched Pulp Fiction for the hundredth time.

RONNIE: Pulp Fiction has always been an inspiration.

VANESSA: And the night before that, American Psycho.

RONNIE: Also inspiring.

VANESSA: Goodnight. Don't wake me up.

She’s gone. Ronnie grabs the remote.

ON TV: The Big Lebowski.


Ronnie, gloomy, sits at the bar. Polo pours a martini and removes the empty glass.

POLO: She broke up with you?

RONNIE: No, man, we're just... we're on, like... a hiatus. Until I have my website built.
As Ronnie slurps down most of his drink –

Categories: Drupal

Merge: What we learned by building our new company site in Drupal 8

5 February 2015 - 9:25am

Last week, we launched our new company site built in Drupal 8. Previously, it was a Drupal 6 site built ages ago and in high need of a redesign anyway. So, with Drupal 8 around the corner, what better way to learn Drupal 8 and help development at the same time?

We started exploring around the time of the first beta, and decided to write up all the bumps in the road we encountered along the way. Overall, it was a great experience and we even managed to squeeze in a patch or two to fix some bugs. Drupal 8 clearly has a lot of improvements for everybody: developers will enjoy the solid framework, frontend people get Twig, and I can't wait to show clients the quick edit functionality.

Categories: Drupal

Chocolate Lily: Customizing the Open Outreach distribution: A case study

5 February 2015 - 9:15am

As part of a long-term collaborative partnership with the University of Victoria's Geography Department, Chocolate Lily has been working on producing a customized version of Open Outreach suitable for community mapping. In a nutshell, we have been able to take the work we produced on a customized site build in 2013 and bundle those features into a new distribution called StoriedMaps.

Categories: Drupal

about seo