Skip to Content

Planet Drupal

Syndicate content
Drupal.org - aggregated feeds in category Planet Drupal
Updated: 1 day 18 hours ago

marvil07.net: On re-writing drupal contribution analyzer scripts

3 January 2015 - 10:56am

In the last months I have been incrementally rewriting the scripts behind the stats I weekly publish about drupal core contributions.

This post is about some background on why and how it happened.

tldr; see conclusion below and the link above with produced data.

Why Developer experience

The way scripts worked in past was really different from what a (drupal) web developer is used to.
It used many languages and script tools to be able to produce its output, increasing the difficulty to start modifying it.

Raw data extracted/processed was stored on CSV files, but interacting with it has proven to be tricky and error-prone.

New/better tools available

Since the time I originally wrote this better tools have been created or stabilized enough to be used.

Buggy

There was some places where the approach was just too fragile and bug prone, specially around the two places where message typos were tried to be corrected by guessing.

Also a Makefile was orchestrating how things were created, but it was just too big to be maintained manually.

How Restructure

This project is about a lot of little pieces working together, a.k.a. glue code FTW, so when I wanted to rewrite several pieces in php, I was needing a way to help structure the code. Also, it is mainly about CLI scripts, so using Symfony Console component sounded like a good idea.
And naturally I also wanted a dependency manager now that I was going to use an external code, I ended up with composer.

The target was then clear: to convert scripts piece by piece to php with a symfony console application containing several commands, one for each piece.

Extracting/processing information form git history was the first step. Doing the same than the python git wrapper library plus extra logic did not look like a good option: it is expensive in general because OS process forking is it as well.
Now I'm using libgit2 library php binding and some git forking for non-trivial operationsvia the library, which improves a little performance given we have mainly C speed there.

After that I wanted to introduce some customization for end user, so I used configuration files in YAML, and used symfony yaml component.
Three configuration files were added: a general configuration file, a mail to username mapping file and a commit message overrides file based on commit hashes.
Overriding commit messages lets fix commits without replacing matching based on the message, but instead relays on commit hashes, making error-prone replacing disappear, mostly.

In early development it was clear automation was going to be a key part for this project to work because of the inter-dependencies during the run.
The makefile orchestrating the run was good enough at the start, but it was really hard to maintain, so it is now dynamically generated, based on configuration files.

CSV files are now replaced with one table in a sqlite database per scenario, so it can be queried easily.

Drupal developers should now see more familiarity in this project: php is the main language used together with a database.

Generalization

This project has been about drupal core for a while, but the way used to extract information is not drupal core specific, but drupal community specific.
There was not really anything apart from scripts flexibility preventing to use it for non-core drupal projects, so I added missing abstraction pieces in order to do it.

New features

This restructure has proven useful for me: I could add several more pieces relatively easy.

CSV and files with username:score are useful, but html is more natural to show in a browser, so I added variations to generate html files of scoreboards. I started using twig to handle related templates.

Data can be seen in context better when shown in a plot. I added some extra generation code using also the flot library to draw some indicators.

It also adds some inter-scenario comparisons, e.g. core's 7.x vs 8.0.x branches indicators.

Automation

This set of scripts historically has been tricky to setup/install.
In the new runner branch I am now maintaining a script to automate the process even more.
It is mainly about calling the right commands in the right environment and hopefully also works as always updated documentation on how to run them.

Conclusion

In conclusion, this rewrite:

  • Improves developer experience and maintainability in general: less languages/tools, more unification on overall process with clear points to override data.
  • Generalizes the target to be usable by any drupal project, e.g. contribute modules, so not only core benefits with it.
  • Restructures logic using new/better tools for some tasks: libgit2, composer, symfony components, flot and twig.
  • Stores extracted information in a sqlite database table, allowing new indicators to be extracted more naturally through queries.
Future

This code is far from perfect, but I am glad of how it end up, it's cleaner and easier to develop/maintain/improve.

Hopefully someone else finds this as useful as me. If you want to help/fix/request a feature please use the relevant issue queue, where I try to keep pending stuff, patches there are welcome!

Etiquetas:
Categories: Drupal

Doug Vann: Why I'm supporting Drupal Camp Karachi 2015

3 January 2015 - 4:03am

To help out Drupal Camp Karachi visit drupalcamps.pk/contact and Azmat will get back with you promptly!

If I tell you that Drupal is growing like crazy, that's not news. If I tell you that Drupal is growing all over the world at an amazing rate, that also is not news. But, what is news, is this. Sometimes these camps need a little help to keep things going. Unlike so many U.S. based camps where large Drupal shops are expected to sponsor every year after year, some non-US camps are victims of their own success. Many will come and many more want to come, but the per attendee price is too high for many.

DrupalCamp KARACHI is January 9-11 : http://www.drupalcamps.pk/drupal-camp-karachi/jan2015
The price is $50 US per attendee which covers the the two paid days of the camp. The training day is free.
The venue is the famous Institute of Business Administration in Karachi 
The deal with the venue is that all students get in free. THIS is a serious hit to the budget and THIS is why I'm helping out and blogging.
 
My buddy, Azmat Shah is one of the chief organizers. He has kept me up to date with the finances, and now I'm blogging to raise some support! :-) Below is some information he put together to wrap this story in some context.

------

Pakistan is 6th most populous country in the World with a population of more than 180 Million. In Pakistan’s largest City and World’s 7th Largest city by population(23 Millions) named Karachi, Drupak ,a Drupal based web Development and Training company  is spearheading a Drupal Camp on Jan 9,10 and 11 ,2015.  Karachi is home to one of the country’s oldest universities and one of them, Institute of Business Administration Karachi, established in 1955 which remains the oldest business school in South Asia, is the host for Drupal Camp Karachi. Drupak are stationed in Peshawar, a city 1400 miles away from Karachi. First two days of the Camp are full-fledged Drupal Trainings and the third day will be the main camp day with sessions from different speakers. This model is how Drupal Camps should be, as I mentioned them in my recent blogpost. Jibran Ijaz https://www.drupal.org/u/jibran , a favorite son of the Drupal Community in Pakistan is keynote speaker of the Camp. He has ample contributions in D8 and D7.

Drupak has dedicated website for Drupal Camps in Pakistan. They have arranged 4 separate Camps so far. On December 1,2013 in Islamabad, Drupak and Figover arranged Drupal Camp in Abasyn University Islamabad. https://groups.drupal.org/node/357553.

In Lahore, at Information Technology University Lahore, Drupak , YASGLOBAL and MughalTech arranged Drupal Camp on May 3rd 2014. Around 105 participants attended the one day Camp. http://www.drupalcamps.pk/drupal-camp-may3/lahore2014.

Drupal Camp Islamabad http://www.drupalcamps.pk/drupal-camp-may10/islamabad2014 was held on May 10,2014 participated by 120 plus participants. Figover , a Drupal based firm were partners with Drupak for the Camp. Drupal Association provided grant for these two camps.

Drupak also arranged Drupal Camp in Dubai on October 26,2013, at SZABIST,Dubai Campus, a Pakistani University in Dubai. The camp website is www.drupalcamps.ae . Drupal Association granted some amount for the Camp which helped some of the expenditures of the CAMP. There have been other camps in Pakistan spearheaded by other companies and we have been part of most of them.

Just recently, as a great move to promote Drupal Culture in Pakistan , Drupak has initiated Drupal Career Development Program in Peshawar Pakistan which will train outgoing students of Universities for free . The program http://www.drupak.com/dcdp2015 is a move to introduce Drupal to the talented students and increasing the number of Drupal Developers from Pakistan. The DCDP is a 4 months long Drupal Mastery Training program and then two months of paid internships which will be given to 12 Students of Computer Science of Universities. The program has been divided into phases of 3 universities each..Thus 36 students will get Drupal trainings in a span of 4 months absolutely free.

Countries like Pakistan, with so much of Drupal talent, the community must look forward to them and support them. But this is only possible, if these emerging  Drupal communities are sponsored and well backed. This upcoming camp in a hugely populated city will train 120 Students in Drupal and already some of them are narrating problems because of the very nominal fee put in i.e 50 Dollars for two days with Lunch.

View the discussion thread.

Categories: Drupal

groups.drupal.org frontpage posts: MidCamp

2 January 2015 - 7:07am
Start:  2015-03-19 (All day) - 2015-03-22 (All day) America/Chicago Drupalcamp or Regional Summit Organizers:  kthull ZenDoodles evilehk YesCT froboy craychee akucharski

http://2015.midcamp.org/

The Heartland. America’s Breadbasket. Middle America. No matter what you call it, the midwest is the heart of the United States — a cultural crossroads that connects the urban east coast with the free-spirited west coast. It’s this epicenter of innovation where great minds from both sides collide and amazing Drupal work is born. We know each other; we respect each other; we collaborate on Drupal sprint and Drupal Association work; we attend and support the events of our local meetups.

It’s time we convene as a superpower to celebrate our mutual passion: Drupal.

This is MidCamp. Three days of Drupally brilliance in the Drupal camp format. Learn, discuss, argue, party, sprint, support and become inspired. United, we can make Drupal even better and become better with Drupal.

Whether you are new to Drupal or you’re an expert, you’ll enjoy all that MidCamp has to offer:

  • One day of intense training within three tracks: Beginner, Intermediate and Advanced
  • Access to a dizzying array of Drupal “go to” resources and session presenters
  • Sessions to address key issues in building great Drupal sites
  • Network and collaboration building
  • Food, beer and fun!

2015 MidCamp will take place March 19 - 22 at the University of Illinois Chicago campus, on the 3rd floor Conference Center in the Student Center East building located at 750 South Halsted.

Come camp in one of the world’s most incredible cities with some of the world’s most incredible Drupal minds. See you at MidCamp!

Please email us if you have any questions about MidCamp or find us on Twitter, Facebook, and Google+.

Categories: Drupal

Bluespark Labs: Content or Commerce? Who's the boss?

2 January 2015 - 6:45am

Selling online is easy, right?

Get a shopping cart and a merchant account, and... Done! Well, hold on a bit. You’ll want discount codes and special offers. You want to accept Paypal, too. Probably need to add bundles. And maybe you should list your products on Amazon’s storefront? Channel diversity is good, right? Speaking of Amazon, can we make our shopping cart work exactly like theirs?

Okay, so maybe the technology side of e-commerce is not actually that easy. The sad truth is that technology is actually the easier part of e-commerce. Getting people to buy your product ... now that’s the tricky bit. Selling online is much more than just providing a great checkout experience, yet so few e-tailers are putting in the time and effort to see truly revolutionary results.

Even if you’re a successful offline chain, making the transition to online can be daunting. Even big retailers struggle. Take Walmart, for example. Walmart’s online store makes up just 2% of its total revenue. What’s more, Walmart.com makes about 1/10 of what Amazon.com does. And Walmart is an 800-pound gorilla. Walmart should know how to sell, right? If they are struggling to compete online, then you know it’s not an easy mountain to climb.

Content is king

I don’t mean the click-bait you see passed around Twitter and Facebook like hot potatoes. I mean real, genuine, amazing, content. Content that informs, educates, or entertains. It is this kind of content that is scarce on the Internet.

But great content takes time to create and consume. It’s subjective. It excites and sometimes offends. It takes risks to connect with the audience. This is something that many corporations have a difficult time doing. I mean, the whole purpose of most entrepreneurial ventures is to dial risk down as low as possible.

But what does content have to do with commerce? As it turns out, a lot.

Product and Content are yin and yang. They are complementary halves that, together, can make an incredibly effective e-commerce site.

Bottom line: Content-driven commerce, if done right, can be a central part of your product showcase—from the homepage down to the product pages themselves.

The new king...same as the old king

The crazy thing about great content? People have been craving it for years. There’s nothing new here. Just take a look at the original saying, “Content is King.” This goes all the way back to 1996 and who, of all people uttered these words? Bill Gates. That’s right. The Tech Titan himself.

“Content is where I expect much of the real money will be made on the Internet...” --Bill Gates

So if Bill Gates saw this back in 1996, what’s taken so long for e-commerce to get here?

What do we mean by content?

If you read Bill Gates’ original piece, you’ll see that he didn’t just mean videos or articles or audio recordings, though all those are content.

When it comes to an interactive network such as the Internet, the definition of "content" becomes very wide. For example, some forms of computer software are a form of content.

So if we think about an e-commerce site, what do we mean by content?

The product page itself is a type of content. It’s not the product. It’s a representation of the product and on that page we have product specifications, descriptions, images, videos of the product in use.

And of course, there are user reviews. This is the OG of e-commerce content and one that nearly all stores have come to embrace.

This is all low hanging fruit, though. We’ve been doing this kind of content since the dawn of e-commerce. But there’s something missing here. Let’s step back a second and look at an in-store purchase experience.

Taking cues from old school

Say you need a suit. Maybe you go to a specialty store like Brooks Brothers. As soon as you walk into the store, you are greeted by someone who asks you a few questions about what you’re looking for, types of occasions you’ll need it for, and a host of other great questions before they start pulling jackets off the rack. Yes, it’s a sales process, and you know it, but it’s incredibly useful. The person is asking the right questions and has some great recommendations.

All these things are ways that offline organizations are engaging their audience and helping them make better, more informed decisions. And it works.

Sadly, this isn’t translating to their online stores. Rather, the stores expect that you know what you need and they focus on their latest sale or their free shipping offer. Or they direct you to categories of suits—categories that might not mean anything to you if you don’t purchase suits regularly.

When you walked into Brooks Brothers, the associates romanced you. They took the time to get to know you and your needs. Online, you need content to romance your customers. People will buy if they feel romanced.

Don’t misunderstand, special offers are an important part of doing business (online and off), but so many e-commerce sites treat them like it’s the only thing that drives sales. Just take a look at any homepage—even brands that spend time creating engaging content use special offers. (Lowe’s and  Home Depot come to mind here.)

But, but, but...

“Isn’t the Internet saturated with content?” you might ask.

Yep. The Internet is flooded with new content every day. Anyone can publish content on the Internet—and millions of people do it every day.

There’s only one thing to do about this concern: Ignore it.

Seriously. Ignore it. There is always a market for great content.

Most of the content published on the Internet is poorly written, poorly thought through, and just plain bad. You’re not going to create that kind of content. You’re going to create amazing, well-written content that rises above the chaff.

You are going to inspire and excite people with your content.

What does content-driven commerce look like?

Alright! You’re ready to start creating content for your online storefront.

Wait! What does that look like?

There are many ways to do it, but the best ways are simply to talk about the ways your products help people. If you sell suits, then educational content on the different styles of suits or how a suit is made and tailored. Photo spreads on the latest trends. Instructional content on how to knot neckties—or how to pair different style neckties with shirts. Or how to change the style of a suit by changing your shirt. Or even just honest expert opinion about why they should pick one brand of tie over a different brand.

Once you’ve written the content, the next step is to make it an integral part of your storefront. Give it the same billing you give your special offers. Don’t make this content a second-class citizen. Your customers will notice. It makes your content feel less authoritative.

For some inspiration on how you can integrate content and product, let’s step out of the world of commerce and a look at how ItalyMagazine.com has accomplished this. (Full disclosure: Italy Magazine is owned and operated by Bluespark.) Italy Magazine offers informative, educational, and entertaining content. Mixed throughout the site are opportunities to buy recipe ingredients, Italian-made products, and vacation services (like hotels and tours).

Take a look at their Food & Wine section. They have some great stories (“My Family Christmas Eve in Sicily and The Feast of 24 Treats” is a great read) and mixed in with the great personal stories or product recommendations (e.g. “Five Italy Foodie Items Everyone Needs in Their Kitchen.”) Each product has a great picture, a description that explains its inclusion on the list, and a purchase option. After all, why wouldn’t you want to purchase one of these authentic Italian products after reading about it from a trusted source? It’s a solid piece of content that will appeal to their audience.

Therein lies the power of content-driven commerce: Build trust as an expert and make recommendations as the trusted expert.

There are plenty of lessons to learn from site like Italy Magazine. From the way they romance their readers to the way they fit product right into the content.

Content-driven commerce drives sales

Content-driven commerce is about making your expertise and story just as important as your products and special offers. It’s about taking the time to build a relationship with your audience and form a genuine connection with them so you can make recommendations that resonate with them.

It’s a bold step. One that takes time and effort to pull off and one that cannot be programmed. If done right, it’s also one that will reap huge rewards.

Tags: Content StrategyDrupal CommerceDrupal Planet
Categories: Drupal

Cocomore: Cocomore AG is Drupal Commerce Delivery Partner

2 January 2015 - 6:23am

For eight years now, Cocomore builds large but also smaller websites with Drupal. Since then the team, the amount of projects and the possibilities have become more extensive. There were special solutions for special requirements in the past, for example content management systems (CMS) for managing content in websites (Wordpress, Jooma, Drupal) or e-Commerce systems for managing online shops like Magento. But those times are gone. In future, the challenge will be to integrate all these standalone systems to “rich-content-systems”, which will offer all the above mentioned functions within one big full service solution.

The integration and the interaction of these systems with each other offer the opportunity to use all the convenient functions of the different components together. That way, it will be possible to present the users a much better online solution with much more and much better functions. Just displaying a photo and some text for a product in a shop is not sufficient anymore today. The modern user expects more, especially concerning interactions with the shop itself like videos, custom reports, votings or a discussion board where he can get much more information and where he can interchange with others about the products. A pioneer in this sector is Amazon, where the user can comment and vote for a product - so users can get feedback from other users. Moreover, videos and further information is shown. This is today’s standard. This is what the user expects and every other shop in the web needs to come up to this mark.

read more

Categories: Drupal

Conocimiento Plus: Testing accessibility of Drupal 8

1 January 2015 - 7:09pm
The limit of bad usability is the lack of accessibility - Sergio Lujan Mora Accessibility refers to the design of products, devices, services, or environments for people with disabilities (How great wikipedia!) Web accessibility means that people with disabilities can use the Web. More specifically, Web accessibility means that people with disabilities can perceive, understand, […]
Categories: Drupal

Michael J. Ross: RESTful Web Services Module Basics

1 January 2015 - 8:01am
A Solution for Working with Entities over the Network

By

This article was published in the print magazine Drupal Watchdog, Volume 4 Issue 2, 2014-09, on pages 30-33, by Tag1 Publishing. The magazine was distributed at Drupalcon Amsterdam 2014, 2014-09-29.

Drupal 7 does not have built-in support for representational state transfer (REST) functionality. However, the RESTful Web Services module is arguably the most efficient way to provide resource representations for all the entity types, by leveraging Drupal's powerful Entity API. Unmodified, the module makes it possible to output the instances of the core entity types — node, file, and user — in JSON or XML format. Further entity type resources and formats are possible utilizing hooks in added code.

As with any REST solution, the RESTful Web Services module supports all four of the fundamental operations of data manipulation: create, read, update, and delete (CRUD). The corresponding RESTful API HTTP methods are POST, GET, PUT, and DELETE, respectively.

Anyone hoping to learn and make use of this module — especially for the first time — will likely be frustrated by the current project documentation, which is incomplete, uneven, and lacking clear examples. This article — a brief overview — is intended to introduce what is possible with this module, and help anyone getting started with it.

We begin with a clean Drupal 7 installation (using the Standard profile) running on a virtual host with the domain name "drupal_7_test". After installing and enabling the module, we find that it does not have the configuration user interface one might expect. In the demonstration code below, we focus on the node entity type for brevity.

Nabbing a Node

The simplest operation — reading an entity instance — is performed using a simple GET request containing the machine name of the entity type and the entity's ID.

To allow an anonymous user to read the node using REST, it is insufficient to grant that role the "View published content". Moreover, the "Bypass content access control" permission has no effect. Rather, the module establishes an "Access the resource" permission for each entity type, which also must be enabled for anonymous users. (When testing anonymous access from a web page, be certain you're not logged into the website in the same browser, because then you are already authenticated.)

In this example, we read the fields in the first page in the Drupal database (node/1), which has a title of "Page Title" and a body field of only "Body text". To display the information in JSON format, in a web browser, the URL would be: http://drupal_7_test/node/1.json

The results are, as expected, in JSON:

{"body":{"value":"\u003Cp\u003EBody text.\u003C\/p\u003E\n","summary":"","format":"full_html"},"nid":"1","vid":"1","is_new":false,"type":"page","title":"Page Title","language":"und","url":"http:\/\/drupal_7_test\/node\/1","edit_url":"http:\/\/drupal_7_test\/node\/1\/edit","status":"1","promote":"0","sticky":"0","created":"1402821494","changed":"1403394617","log":"","revision":null,"comment":"1","comments":[],"comment_count":"0","comment_count_new":"0"}

If you get an HTTP 403 error ("Forbidden"), verify the two required permission settings for anonymous users accessing nodes.

To display the same information as XML, we need only alter the path extension: http://drupal_7_test/node/1.xml

The information is the same, but in a substantially different format:

<node> <body> <value> Body text </value> <summary/> <format>full_html</format> </body> <nid>1</nid> <vid>1</vid> <is_new/> <type>page</type> <title>Page Title</title> <language>und</language> <url>http://drupal_7_test/node/1</url> <edit_url>http://drupal_7_test/node/1/edit</edit_url> <status>1</status> <promote>0</promote> <sticky>0</sticky> <created>1402821494</created> <changed>1403394617</changed> <log/> <comment>1</comment> <comments/> <comment_count>0</comment_count> <comment_count_new>0</comment_count_new> </node>

To get all of the nodes — or at least the first 100, by default — remove the entity ID: http://drupal_7_test/node.json

If we again want to access that first node only, but not use a URL, then we can employ cURL on the commandline:

curl -X GET http://drupal_7_test/node/1.json

More descriptive options, such as --request GET, can be chosen. To see details of operations that may be helpful for debugging, add the -v option (a.k.a. --verbose).

If you do not want anonymous REST requests to have access to your website's content, use HTTP authorization. Apparently, the simplest way to do so is to enable the Basic Authentication Login submodule (restws_basic_auth). Connect by utilizing a username that has the appropriate "Access the resource" permission and begins with the (mandatory lowercase) string "restws". Assuming that the user "restws_user" has a password of "password", then we can display the first node with this command:

curl -X GET http://drupal_7_test/node/1.json -u restws_user:password

The aforesaid username pattern could be represented as the regular expression /^restws.*/. That pattern can be customized in the website's settings.php file. For instance, to grant authorization only to those usernames beginning with the string "WS_", one adds the following (case-sensitive) regex to the settings.php file:

$conf[ 'restws_basic_auth_user_regex' ] = '/^WS_.*/';

Authentication is a substantial topic unto itself, and here we will not delve into cookie and session handling.

Creating or Changing a Node

Rather than using the conventional content management UI for adding an entity resource, we can do it with REST — specifically, a POST request. There are several ways to accomplish this: using the core function drupal_http_request(), or cURL PHP code, or cURL on the commandline, which we will use. Returning to the XML format, we can use a file based upon the one output earlier, containing only those elements needed to create a new node with some content in the body field:

<node> <type>page</type> <title>New Page Title</title> <body> <value> New body text </value> </body> </node>

Assuming that the file is named create.xml, we can create a new node with this command:

curl -X POST -H "Content-Type: application/xml" -d @create.xml http://drupal_7_test/node -u restws_user:password

Assuming the AUTO_INCREMENT value of the nid field in the node table is 1, for example, then the output in the command window would indicate that a second node, node/2, had been created:

<?xml version="1.0" encoding="utf-8"?> <uri resource="node" id="2">http://drupal_7_test/node/2</uri>

If the XML file were missing an essential element, such as the node type, then we would receive an error message such as: 406 Not Acceptable: Missing bundle: type

Modifying an existing node is similar, but instead of using the POST method, use PUT and specify a node ID. The XML file needs even fewer elements:

<node> <title>Modified Page Title</title> <body> <value> Modified body text </value> </body> </node>

The cURL command is straightforward:

curl -X PUT -H "Content-Type: application/xml" -d @modify.xml http://drupal_7_test/node/2 -u restws_user:password

The output will not include any <uri> element containing the node ID, since no new one was created.

Nuking a Node

To remove any entity resource — in this case, the first node in the database — use the DELETE method:

curl -X DELETE http://drupal_7_test/node/1 -u restws_user:password

The baffling yet correct output is merely:

[]

Using this module, one can also: filter the results when querying for multiple resources (by specifying the user ID or taxonomy ID); sort by one of the properties (either in ascending or descending direction); change the default limit of 100 resource references. For debugging purposes, you can enable the logging of the details of all web service requests to a specified file, with a simple addition to the Drupal settings.php file, for example:

$conf[ 'restws_debug_log' ] = DRUPAL_ROOT . '/restws_debug.log';

If in your own explorations, you discover additional techniques and pitfalls, please consider publishing them, as there appears to be both interest and confusion as to how to leverage this module fully.

Copyright © 2014 Michael J. Ross. All rights reserved.

Categories: Drupal

Larry Garfield: Building Bridges: 2015 Edition

31 December 2014 - 1:11pm

As most who have met me know, building collaborative communities is a minor passion of mine. 2 years ago, I called on the Drupal community to Get off the Island and connect with other communities.

That call was part of a larger movement within the PHP community to interact more, connect more, and collaborate more than ever before. The PHP Renaissance has been driven in no small part by that greater collaboration between many different PHP communities.

To close out 2014, I spoke with Jeff "JAM" McGuire of Acquia Podcast fame about Drupal and community building, and what it means to be the "Drupal Community" when so much of Drupal isn't Drupal.

And as a final capstone, I made a challenge to the entire PHP community: Don't just talk to each other, build with each other. Get out of your comfort zone and learn something new, from someone else.

Happy New Year, PHP. Let's Build Something Good together.

read more

Categories: Drupal

Larry Garfield: 2014: A Year of Travel

31 December 2014 - 11:58am

As 2014 draws to a close, I look back at the year and realize... holy crap I traveled a lot! I hadn't actually done a fully tally yet, but here's the full rundown:

Sunshine PHP - Miami, FL - February
Drupal South - Wellington, New Zealand - February
Florida Drupal Camp - Orlando, FL - March
MidCamp - Chicago, IL - March
Museums and the Web - Baltimore, MD - April
Lonestar PHP - Dallas, TX - April
Drupal Association Webinar - Online - May
php[tek] - Chicago, IL - May
DrupalCon Austin - Austin, TX - June
Refactor::Chicago (User group) - Chicago, IL - May
Nomad PHP (User group) - Online - June
Crafting Code Tour - Minneapolis, MN; Milwaukee, WI; Cincinnati, OH - July
Design 4 Drupal - Boston, MA -July
Twin Cities Drupal Camp - Minneapolis, MN - August
Madison PHP - Madison, WI - September
DrupalCon Amsterdam - Amsterdam, The Netherlands - September
Symfony Live - New York, NY - October
Higher Ed Web - Portland, OR - October
BADCamp - San Francisco, CA - November
php[world] - Washington, DC - November

In all, I flew 64,082 miles (103,130 kilometers for the metric fans in the audience), presented 29 times, with 13 distinct presentations at 20 conferences and user groups across 3 continents, and spent 82 days on the road (not counting non-conference travel). You know what that means?

It means I created about 10 metric tonnes of carbon pollution.

read more

Categories: Drupal

Blink Reaction: Principles of Technical Excellence

31 December 2014 - 9:01am

Stephen R. Covey wrote in his Seven Habits of Highly Effective People about the concept of being effective in attaining goals by aligning oneself to what he calls "true north" principles. I’ve long been a fan of his teachings and, over the years, have developed an idea of these principles that we apply at Blink whenever we engage a new technical challenge.

Categories: Drupal

InternetDevels: Events 2014 sum up

31 December 2014 - 8:27am

This year was rich in different IT-events of any kind. InternetDevels company also doesn’t stay aside the movements and trends taking place in Drupal community. We care about Drupal development and about the system itself, that’s why we sponsored and took part in different Drupal events all around the world.

Special attention goes out to DrupalCons, of course. There were two of them this year:

Read more
Categories: Drupal

Drupal Watchdog: The Drupal 6 to 8 Upgrade Challenge - Part 3

31 December 2014 - 8:14am

Nathaniel Catchpole , the Drupal 8 release maintainer and Tag1 Senior Performance Engineer, suggested that Drupal shops everywhere could support the release of Drupal 8 by upgrading their own sites and addressing the issues they find along the way. This series chronicles the journey from Drupal 6 to Drupal 8. Part 2: Preparing to Migrate.

Part 1: Readiness Assessment | Part 2: Preparing to Migrate

Part 3: Migrating D6 to D8

I first tried running this migration back in November at BADCamp, where I was able to work with Migrate architect and Tag1 Senior Performance Engineer, Károly “ChX” Négyesi. As a result of our work, there are a few patches I’m using to solve issues I’m still encountering. Before running the migration, I pulled the latest commits to 8.0.x. Everything I describe below took place with Drupal core at commit 3359738e0fe, which contains all the commits through December 24. I re-created a migration manifest as described in Part 2: Preparing to Migrate. I briefly reviewed known issues with the D6 > D8 migration path and followed along with Migrate’s Inline module which allows users to display uploaded files and images inline and it has no 8.x version, I’ll disable the migration and use core’s wysiwyg to replace images.

Migration d6_file Severity Serious Error failed to open stream: No such file or directory EntityFile.php:70 Solution Disable file migration in the manifest Issue/s https://www.drupal.org/node/2369401 Blocks

The block migration produced PHP Fatal error: Call to a member function uuid() on a non-object. This is a straight-up bug. I worked with chx, who showed me how to use PHPStorm and xdebug to track down the issue, and we made a patch back at BADCamp, but it still needs work before it’s ready for core. In the meantime, it solved the issue for me, so I used it.

Migration d6_blocks Severity Fatal Error Call to a member function uuid() on a non-object in core/modules/migrate_drupal/src/Plugin/migrate/process/d6/BlockPluginId.php on line 87 Solution Patch: https://www.drupal.org/node/2372785#comment-9328703 Issue/s https://www.drupal.org/node/2372785 Menu Links

This migration was problematic and the patches referenced below didn’t solve the problem. For this simple site, the best recourse was to rebuild the menu system by hand, but this presents a serious issue for any site with complex menus.

Migration d6_menu_links Severity Serious Error PDOException' with message 'SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'menu_name' cannot be null' in core/lib/Drupal/Core/Database/Statement.php:61 Solution Removed the migration from the manifest Issue/s https://www.drupal.org/node/2372837 https://www.drupal.org/node/2232477 Book Module

I hadn’t bothered removing migrations that were irrelevant. For example, I didn’t have any book content and the module was uninstalled on both sites. Even so, it was still in the manifest, and I hit the following error:

Migration d6_book Severity Trivial Error exception 'Drupal\Component\Plugin\Exception\PluginNotFoundException' with message 'The "book" plugin does not exist.' Solution Removed the migration from the manifest Issue/s https://www.drupal.org/node/2397339 Node Setting Promote

The promotion options on the nodes have values, but the labels, Promote and Sticky, as well as their descriptions are not displayed.

Migration d6_node_setting_promote Severity Normal Error base_field_override entity with ID node.page.promote already exists. (drupal/core/lib/Drupal/Core/Entity/EntityStorageBase.php:391) Solution Unsure Issue/s Expand FieldConfig/BaseFieldOverride with methods https://www.drupal.org/node/2030637 Vocabulary Migration Errors

I hit errors with respect to the “forum” vocabulary. We’re not using Forums, so I uninstalled it from the D6 site. This removed the error, but nodes were still not associated with their terms.

Migration d6_vocabulary_field_instance
d6_vocabulary_entity_display d6_vocabulary_entity_form_display Severity Serious Error Vocabularies and terms are migrated but are not applied to the nodes Missing bundle entity, entity type <em class="placeholder">node_type</em>, entity id <em class="placeholder">forum</em>. (core/lib/Drupal/Core/Entity/EntityDisplayBase.php:166) Solution Relink The Moment of Truth

Once I’d resolved (or avoided) all the fatal errors and the migration ran cleanly, I visited my new site. On first glance it was a little disconcerting. I’d already run into many of these issues when I ran the migration at BADCamp, and they were still happening. More disconcerting, however, was the discovery that I can’t add new content nor edit any content.

Front Page Not Loading

The first view of the site looked broken, and rebuilding the cache with drush didn’t help either. Still, a lot of things happened with the migration, so I figured I’d try logging in: The login page looked proper, and I went straight back to the home page. Even without even logging in, the theme looked normal. That’s easy enough to cope with. I added this to the Known issues.

Couldn’t Edit Nodes

I couldn’t edit the nodes that had been migrated. I initially lost some time on this because “Recent log messages” referenced reported “Missing filter plugin: filter_null.” which turned out to be irrelevant, but made me suspect the migration. When I verified that the problem occurred with core and not just migrations, I finally checked my Apache error logs, which revealed what I needed to know - I had a local configuration issue:

Migration N/A - Local configuration issue unrelated to migrate Problem Adding or editing nodes led to a white screen. Error PHP Fatal error: Maximum function nesting level of '100' reached, aborting! in /var/www/eight/drupal-beta4/core/vendor/twig/twig/lib/Twig/Node.php on line 141 Solution In php5/apache2/php.ini, I set the xdebug.max_nesting_level = 500 Issue/s https://www.drupal.org/node/2393531 Aliases Not Working

I’d encountered this problem at BADCamp and it was still happening: none of the content was loading by URL alias. I looked at the values in the url_alias table, and saw no langcode was set.

MariaDB [tag1six]> select * from url_alias limit 10; +-----+------------------+------------------------------------------------------------+----------+ | pid | source | alias | langcode | +-----+------------------+------------------------------------------------------------+----------+ | 1 | node/9 | blog/jeremy/Achieving_Optimal_MySQL_Performance_for_Drupal | | | 2 | tagadelic | tags | | | 3 | node/12 | performance_checklist | | | 4 | node/16 | Improving_Page_Load_Times | | | 5 | taxonomy/term/11 | Dries_Buytaert | | | 6 | taxonomy/term/7 | Drupal | | | 7 | taxonomy/term/3 | performance | | | 8 | taxonomy/term/10 | scalability | | | 9 | taxonomy/term/9 | Tag1_Consulting | | | 10 | taxonomy/term/14 | ab | | +-----+------------------+------------------------------------------------------------+----------+

I set the value in the database with update url_alias set langcode ='en' and nodes loaded by their alias.

Migration d6_url_alias Problem Nodes wouldn’t load by URL alias Solution Apply patch before migrating or fix in the database after migrating: update url_alias set langcode = 'en'; Issue/s https://www.drupal.org/node/2226455 Couldn't Edit Users

Trying to edit users resulted in the errors below., and until all the profile fields were removed, users couldn't be edited at all.

Migration - d6_user_profile_entity_display
- d6_user_profile_entity_form_display
- d6_user_profile_field
- d6_user_profile_field_instance Problem Editing the user resulted in the following: Recoverable fatal error: Argument 5 passed to Drupal\Core\Field\WidgetBase::__construct() must be of the type array, null given, called in /var/www/tag1/eight/drupal/core/lib/Drupal/Core/Field/WidgetPluginManager.php on line 130 and defined in Drupal\Core\Field\WidgetBase->__construct() (line 55 of ore/lib/Drupal/Core/Field/WidgetBase.php). Notice: Undefined index: third_party_settings in Drupal\Core\Field\WidgetPluginManager->createInstance() (line 130 of core/lib/Drupal/Core/Field/WidgetPluginManager.php). Solution None yet. There is work in progress on profile fields. I opted not to migrate the user profiles since we’ll be doing extensive work on them anyway. Issue/s https://www.drupal.org/node/2394615 https://www.drupal.org/node/2394567 Node Authors not Migrated

During the BADCamp migration, I saw that node authors weren’t being set. I experimented with putting the user migrations before the node migrations, which worked, but a more proper approach is in under development and a patch available at https://www.drupal.org/node/2376141.

Migration d6_node d6_node_revision Problem Node authors weren’t being set Solution Patch: https://www.drupal.org/node/2376141#comment-9453157 Issue/s https://www.drupal.org/node/2376141

This concludes what was reasonably possible with Migrate this week. I think it bears repeating that Migrate is NOT a requirement for a Drupal 8 release candidate, nor for Drupal 8 itself. With more than 85 critical core issues, it’s likely that Migrate won’t be ready for site builders for quite some time. Developers, however, can pitch in!

Overview  

Group

Migrations

Site comments

+

Blocks

- d6_block
- d6_block_content_body_field
- d6_block_content_type
- d6_custom_block

Blocks need to be placed within the theme, but migrate fine.

+

Fields

- d6_cck_field_revision
- d6_cck_field_values
- d6_field
- d6_field_formatter_settings
- d6_field_instance
- d6_field_instance_widget_settings

No visible problems.

+

Comments

- d6_comment
- d6_comment_entity_display
- d6_comment_entity_form_display
- d6_comment_entity_form_display_subject
- d6_comment_field
- d6_comment_field_instance
- d6_comment_type

These migrated seamlessly.

x

Files

- d6_file
- d6_file_settings

Files are a work-in-progress. See

+

Input Filters

- d6_filter_format

These migrated fine, but I preferred to update to the filter formats provided with Drupal 8.

o

Menus

- d6_menu
- d6_menu_settings
- d6_menu_links

Hierarchical menu links were problematic. Single level menus migrated successfully.

+

Nodes

- d6_node
- d6_node_revision
- d6_node_setting_promote
- d6_node_setting_status
- d6_node_setting_sticky
- d6_node_settings
- d6_node_type

Nodes migrated successfully with the small exception of the labels for Promote to Front Page and Sticky.

+

System Settings

- d6_syslog_settings
- d6_system_cron
- d6_system_file
- d6_system_filter
- d6_system_image
- d6_system_image_gd
- d6_system_logging
- d6_system_maintenance
- d6_system_performance
- d6_system_rss
- d6_system_site

 

o

Taxonomies

- d6_taxonomy_settings
- d6_taxonomy_term
- d6_taxonomy_vocabulary
- d6_term_node
- d6_term_node_revision
- d6_vocabulary_entity_display
- d6_vocabulary_entity_form_display
- d6_vocabulary_field
- d6_vocabulary_field_instance

Vocabularies and terms migrated. Freetag terms weren’t associated with their nodes.

x

Uploads

- d6_upload
- d6_upload_entity_display
- d6_upload_entity_form_display
- d6_upload_field
- d6_upload_field_instance

Uploads didn’t migrate.

o

Users

- d6_user
- d6_user_contact_settings
- d6_user_mail
- d6_user_picture_entity_display
- d6_user_picture_entity_form_display
- d6_user_picture_field
- d6_user_picture_field_instance
- d6_user_picture_file
- d6_user_profile_entity_display
- d6_user_profile_entity_form_display
- d6_user_profile_field
- d6_user_profile_field_instance
- d6_user_role
- d6_user_settings
- d6_profile_values

User profile fields and pictures didn’t migrate properly, but users did.

+

Miscellaneous

- d6_action_settings
- d6_date_formats
- d6_dblog_settings
- d6_locale_settings
- d6_search_page
- d6_search_settings
- d6_simpletest_settings
- d6_statistics_settings
- d6_text_settings
- d6_update_settings
- d6_url_alias
- d6_view_modes

These either migrated successfully or weren’t significant values on the site

 

Not run

#- d6_aggregator_feed
#- d6_aggregator_item
#- d6_aggregator_settings
#- d6_book
#- d6_book_settings
#- d6_forum_settings
#- d6_contact_category
#- d6_contact_settings

There was no need to run these

Images: 
Categories: Drupal

Palantir: Explaining Panels: An Overview for Drupal Developers

31 December 2014 - 7:54am

In my time as a Drupal developer I've had plenty of long and— and sometimes heated— conversations about Panels module. Is it good? Is it too complex? Does it make a site faster or slower? Those are all fine questions to debate; after first agreeing what is Panels?

First, when people talk about "Panels" there are numerous submodules and supporting projects they might be referring to as well. To understand where the lines are drawn between the modules I like to look at which existing Drupal concepts each module augments.

Panels module is a User Interface on top of theme() and hook_theme()

I suspect the common introduction to Panels is something like:

It's very easy to get overwhelmed by the number of options, buttons and links. So let's start simpler: a two column layout.

The code needed to declare that this two column layout exists at all is really small. Browse around the directory declaring it. There is not much there. An info array tells us most importantly that we have left and right regions.

  'regions' => array(
    'left' => t('Left side'),
    'right' => t('Right side')
  ),

Source: http://cgit.drupalcode.org/panels/tree/plugins/layouts/twocol/twocol.inc?id=7.x-3.4

And there's a template file that prints the left and right regions.

<div class="panel-display panel-2col clearfix" <?php if (!empty($css_id)) { print "id="$css_id""; } ?>>
  <div class="panel-panel panel-col-first">
    <div class="inside"><?php print $content['left']; ?></div>
  </div>
  <div class="panel-panel panel-col-last">
    <div class="inside"><?php print $content['right']; ?></div>
  </div>
</div>

Source: http://cgit.drupalcode.org/panels/tree/plugins/layouts/twocol/panels-twocol.tpl.php?id=7.x-3.4

The simplicity of this layout plugin (aside from its love for divs and css classes) makes it easy to forget that it is an abstraction layer on top of the hook_theme() and theme(). When most Drupalers think about the theme system, they think about the complexity.


Not many people in the world completely understand this diagram. Source: http://john.albin.net/drupal/arrays-of-doom

At the heart of this complexity, is the simple idea that you use hook_theme() to tell Drupal that there's a type of thing that you would like to print in the future (like a two column layout). And that thing will require some variables (like ‘left' and ‘right'). Then at any time theme() can be asked to print a two column layout and all that it needs is to be told are the values of ‘left' and ‘right'.

So how do we figure out what is actually supposed to go in ‘left' and ‘right'? Panels answers that question in a way that is fundamentally different from nearly all Drupal Core usages of hook_theme()/theme(). Core's node module uses hook_theme() to say "Hey, in the future I'm going to ask to you print a ‘node' and I'll give you is a variable called ‘node'". And what it really means is "Oh, yeah, and I'm going to need a ton of preprocessing to derive some variables that can actually be printed in a template." When that Panels layout plugin says "I really only need ‘left' and ‘right'", it can do so because Panels has a separate subsystem for building up those variables.

If you wanted Core to print a node into two columns then you would likely rely on preprocessing and template overrides to figure out what goes where inside theme(‘node'). Panels can take a node and follow instructions for splitting it into ‘left' and ‘right' before calling theme(‘twocol').

This somewhat academic distinction opens up a different mental model for developers. Core encourages you to think "I'm rendering a node and I'll alter and override what that means once I start doing it." Panels encourages you to think "I know I want a two column layout and here is the configuration to split up this node accordingly." In an upcoming blog post I will explain why I prefer the second way of thinking.

Page Manager is a UI on top of hook_menu() and hook_menu_alter()

The response you get when visiting a URL on a Drupal site is determined largely by hook_menu(). When you go to the user register page you see a registration form because the user module told hook_menu() that a registration form belonged there. Other stuff may appear in Blocks in sidebars, footer and headers that are outside the knowledge of hook_menu(). hook_menu() primarily controls the main response that shows up in the "content" region of page.tpl.php

If you're on a music website that has a Taxonomy term of "Jazz" you might see a listing of every node on the site tagged in Jazz. That's what taxonomy module tells hook_menu() to put in the response for a taxonomy term page. On the other hand, if I were running a music website I would probably want my page for Jazz to be a little more customized than a simple listing of everything on the site tagged in Jazz. Let's step up the complexity just a little and imagine that the term pages for music genres should separately group musical artists, albums and songs.

Here the distinction between Panels and Page Manager becomes cloudy. Page Manager tells Drupal "I know taxonomy module told you how term pages look, but I am overriding that. Use this Panels configuration instead." Page Manager changes the way Drupal responds to existing paths as well as telling Drupal about new ones.

Panels is not even necessary to use Page Manager. Out of the box, Page Manager gives you the option to override existing URL paths just to change the HTTP response code (for that rare case where you want node/%node to respond with a 403 for anonymous users but don't want to use the node access system). There are even other modules like Contextual Adminstration https://www.drupal.org/project/context_admin which use Page Manager to add more administrative options.

To go over that Jazz use case one more time, Page Manager:

  • Overrides the normal ‘list all nodes' behavior declared by Taxonomy module.
  • Replaces it with a package of Panels configuration which
    • Declares the layout it wants
    • Contains instructions for populating the regions of that layout based on the given "context" (the taxonomy term "Jazz")
Panels Everywhere is a UI alternative to page.tpl.php and Blocks UI

I just mentioned above that Page Manager is the way to control the the "content" region inside page.tpl.php. Panels Everywhere is meant to entirely replace your typical page.tpl.php customizations and the Block configurations that feed that file.

The Core block system encourages a "global" mindset.

This block will show up on every page except those the pages whose URL paths match a certain pattern.

Blocks can also be restricted by user role.

This model implicitly asks you to think "all of my Blocks are going to show up all of the time except for when I tell them not to show up." You can simultaneously think in the inverse of "this Block will never show up except for this one page." That model can put too much in the site builder's head at once; especially when there is another way to change what happens at this global, Block level. With Core's tools you can always override page.tpl.php with something like page--front.tpl.php. That template can change the overall markup of the front page as well as change what regions will actually print their Blocks. Now you have another independent paradigm to keep in mind when asking yourself "should I expect this block to print?"

Panels Everywhere allows for those two concepts to be united. Decide on your layout first and then pull in the contents of the layout regions. Core will load Blocks even if an override of page.tpl.php has nowhere to print them.

With Panels Everywhere, just like Core, you will have to figure out if a given element should be controlled by main page response (hook_menu()/Page Manager) or if the element is a more global decoration (Blocks/Panels Everywhere). An advantage of the Panels Everywhere / Page Manager combo is that the same workflows and concepts apply in both places.

Categories: Drupal

Evolving Web: Creating a Multilingual Install Profile for Drupal

31 December 2014 - 7:53am

Drupal allow you to set up installation profiles to fast-track creating a website. Rather than starting from scratch each time you create a site, you can select an install profile that does some initial configuration for you. This is super useful if you make a lot of websites that start the same way. I think multilingual websites are a good example, since there's a lot of configuration that gets repeated.

read more
Categories: Drupal

Acquia: 2014 greatest hits – Drupal 8 means better business – Michael Schmid

31 December 2014 - 6:53am
Language Undefined

I am looking back on a great year of events and conversations with people in and around Acquia, open source, government, and business. I think I could happily repost at least 75% of the podcasts I published in 2014 as "greatest hits," but then we'd never get on to all the cool stuff I am lining up for 2015! Nonetheless, here's a recording from one of my favorite moments from 2014: Drupal Dev Days in Szeged Hungary, where more than 300 contributors went wild working together on Drupal, I was honored to be the keynote speaker, and where Adam Juran and Campbell Vertesi debuted their now-legendary "Coder v Themer" ultimate smackdown grudge-match. In this podcast, Michael "Schnitzel" Schmid and I talk Drupal 8 from his perspective as a service provider.

Categories: Drupal

Code Karate: Using Drupal views pager feature to display users

31 December 2014 - 5:16am

From time to time we get a question that we feel would be beneficial to more than just the person

Categories: Drupal

Mediacurrent: Understanding the Value of a Drupal-centric Agency

30 December 2014 - 1:52pm

Over the last 7+ years, I’ve talked to hundreds of prospects and customers regarding their strategy for ongoing Drupal-based website support. Most organizations tend to agree that their web presence is the single most important marketing tool they can leverage to engage their target audiences.

Categories: Drupal

Open Source Training: Drupal File Access for Specific User Roles Only

30 December 2014 - 12:26pm

By default, Drupal file fields have very limited permission options.

So, if you want to make some files available only to certain user groups, you'll need an extra module.

For some simple examples, we recommend the Private files download permission module.

In this tutorial, we'll show you how to use that module to allow only logged-in users to download a file.

  • Go to Configuration > File system.
  • Here you can enter a folder which is only for private files. This means that files in this folder will not be publicly available on the internet.

Some notes on this folder for private files:

  • You will have to create this folder manually. Drupal will tell you if there are any problems with this folder.
  • You must also click "more information about securing private files". That page will give you instructions on making sure your folder is private.

Now you can set up added permissions for your files.

  • Install the Private files download permission module.
  • Enter a path for this set of file uploads. In this example I used "loggedin_files" because that folder will be for files accessible only to logged in users.
  • Under "Enabled Users" and "Enabled Roles", choose who can download these files.
  • Go to Structure > Content types > Manage fields
  • Create a new field using the "File" type.
  • Enter the file directory that you choose in the previous step:
  • Save the field.
  • Create a test content item using the File field:
  • Logged in users should now be able to see and access the file.
  • Visitors who are not logged in will be able to see the file, but when they click on it, they'll get an "Access denied" message:
Categories: Drupal

Open Source Training: Date and Time Formats in Drupal

30 December 2014 - 11:02am

One of our members had this question for us:

"I'm using the Date module and I would like it to display morning as AM and evening as PM. At the moment it shows 15:00, but I'd like it to show 3 PM".

In this tutorial we'll answer that question and show you how to set up Date and Time formats in Drupal.

Categories: Drupal

Cheeky Monkey Media: A Quick Look Back at BADCamp 2014

30 December 2014 - 9:00am

This year, I was invited to attend BADCamp 2014. No, I know what you are thinking, but this is not a camp for bad web developers. In fact, its quite the opposite. Badcamp stands for Bay Area Drupal Camp. This is a four day, annual event held in the San Francisco Bay area. Basically, its a gathering of like-minded web developers discussing and learning about Drupal.

Having never been to a major Drupal event, I jumped on the opportunity to go. It was a fun trip full of learning, networking and maybe an after...Read More

Categories: Drupal


Google+
about seo