How to setup a multi-site, multi-lingual, multi-user Drupal blog
The guide is based on Debian GNU/Linux, but the Drupal parts apply to any system.
Goals
The blog we have in mind has some (basic?) requirements:
- Single CMS installation hosts multiple sites (on different domain names).
- Stable and search engine friendly URLs
- Content types: Blog posts, Pages, News
-
Multilingual features:
-
Nodes: a post can be in any of the defined languages, or even language
Neutral (even if I can't see a real use for this type of content yet).
The front page will show only one translation in a group of node translations, with this rule: the version in current interface language is shown if available, then the version in the default language if available, one of the available versions (the original, for example) otherwise. Language neutral nodes are shown always, independently from the current language.
Language neutral nodes, or nodes in language other than current one, might be presented differently to the user.
-
Nodes: a post can be in any of the defined languages, or even language
Neutral (even if I can't see a real use for this type of content yet).
-
Feeds:
- Global feed, with all content.
- Per language feed, with content filtered per language.
- Global comments feed
- Per language comments feed
- Per node and per tag feeds.
- Common blog blocks: recent posts and comments, popular contents, monthly archive, tag clouds, links to contents related to a post.
- Pingback and Trackback functionalities (low priority)
- Multi-user capability comes with Drupal for free.
Reading the specs one could figure out that we are trying to express a Wordpress-on-steroids concept in terms of Drupal. It could be considered over-design, but it is interesting nonetheless.
This guide focuses only on building the infrastructure and not on content editing tools.
Install MySQL
aptitude install mysql-server
Install Apache webserver
Install apache2
aptitude install apache2
Add ServerName (you can use the IP address of the server if you plan to use name-based Virtual Hosts):
$EDITOR /etc/apache2/httpd.conf
Fine tune ports settings:
$EDITOR /etc/apache2/ports.conf
Some more config options can be seen here: http://articles.slicehost.com/2007/9/14/debian-etch-apache-configuration-1 http://articles.slicehost.com/2007/9/14/debian-etch-apache-configuration-2
Enable needed modules
a2enmod whatever # Enable userdir, rewrite or what else you need
Install the php5 apache module, and also the mysqli and gd ones:
aptitude install libapache2-mod-php5 php5-mysql php5-gd
Apache VirtualHost
In order to have a single Drupal installation for multiple sites we can put the directory with Drupal code inside the global webserver DocumentRoot (e.g /home/www-data/drupal
) and link this location inside site-local DocumentRoot:
# put Drupal in /home/www-data/drupal and then: mkdir -p /home/www-data/example.com ln -s /home/www-data/drupal /home/www-data/example.com/drupal
Setup the virtual host for our domain in /etc/apache2/sites-availabe/example.com_site
<VirtualHost *:80> ServerAdmin webmaster@localhost ServerName example.com ServerAlias www.example.com DocumentRoot /home/www-data/example.com/drupal <Directory /home/www-data/example.com/drupal> Options -Indexes +FollowSymLinks MultiViews AllowOverride None Order allow,deny allow from all </Directory> ErrorLog /var/log/apache2/ao2.it.error.log # Possible values include: debug, info, notice, warn, error, crit, # alert, emerg. LogLevel warn CustomLog /var/log/apache2/ao2.it.access.log combined </VirtualHost>
then enable it
a2ensite example.com_site
and restart apache
invoke-rc.d apache2 restart
Install Drupal
This method has been inspired by http://www.howtoforge.com/multisite_drupal_installation_ubuntu
Get the desired revision from git:
cd /home/www-data/ git clone --branch 6.x http://git.drupal.org/project/drupal.git git reset --hard 6.26 git checkout 6.26
cd drupal/sites mkdir -p all/modules && \ mkdir -p all/themes/{engines,custom,drupal-contrib} && \ mkdir -p default/modules && \ mkdir -p default/themes/{custom,drupal-contrib}
When a new version is released you can upgrade with these commands:
chmod u+w sites/default git checkout 6.x git pull git reset --hard 6.27 git checkout 6.27 chmod u-w sites/default
like told here.
If you use the Italian hosting provider Aruba, follow this guide.
Create the DB for example.com and a database user for it.
mysqladmin -u root -p CREATE drupal_example_com mysql -u root -p mysql> USE drupal_example_com; mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON drupal_example_com.* TO 'drupal_db_username'@'localhost' IDENTIFIED BY 'drupal_db_password'; mysql> flush privileges; mysql> quit
Prepare for installation
mkdir -p default/files sudo chown www-data:www-data default/files cp default/default.settings.php default/settings.php chmod a+w default chmod a+w default/settings.php
Access the page http://example.com/install.php
from the browser
and insert the DB data specified above, you will also be asked to choose the
Administrator username and password, which of course can be different from the
DB account.
Remove write permission to some files:
chmod go-w default/ sudo chown www-data:www-data default/settings.php chmod 600 default/settings.php
If needed edit default/settings.php and add what you want, for instance you may want to set the maintenance theme and some multi language variables, see http://drupal.org/node/313272
When you want to add another site, repeat the steps above replacing default
, example_com
and example.com
with your domain name.
Do some basic setup
- Setting the maintainance theme:
- Edit
settings.php
and setmaintenance_theme
- Add a
drupal/sites/default/theme/custom/YOURTHEME/maintenance-page.tpl.php
- Edit
- Set site information
Home » Administer » Site configuration » Site information
- Create a non-admin account for content submission, so you won't use the admin user when posting content from a non-safe place.
- Setup
Home » Administer » User management » Permissions
so that users can administer contents and menu also in maintenance mode:- enable
administer site configuration
undersystem module
for authenticated users.
- enable
- Install and enable your preferred themes and modules
- Setup Cron
- Specify an email address to send update notifications to, check the Settings tab in
Home » Administer » Reports » Available updates
Remember to set also permissions in menu and node modules, such as:
- administer menu
- create page content
- edit any page content
- upload files
- etc...
Configure Drupal
Content Types and Input formats
By using the Better formats module, we can set filter types according to user rights, we decided to use Filtered HTML input format for all content created by anonymous users (mainly comments), and a more permissive input format for authenticated users, see:
Home › Administer › Site configuration › Input formats › Defaults
Another interesting option for authenticated users is htmlpurifier, a way to have clean code without too much restrictions.
Enable the PHP filter module, you will need it in future.
Multilanguage features
Install the i18n module and optionally the languageicons module.
To enable basic multilingual support:
-
Set language prefix (also for default language):
Home » Administer » Site configuration » Languages » List » English (Edit)
and add the en prefix
-
Add your own language (English will still be the default language):
Home » Administer » Site configuration » Languages » Add language
You can import interface string translation at a later stage.
-
Set Language negotiation, I picked up Path prefix with language fallback (Check also here):
Home » Administer » Site configuration » Languages » Configure
-
Hide Content translation links if you handle them in your theme template:
Home » Administer » Site configuration » Languages » Multilingual system
-
Enable Multilingual Support for your content types:
Home » Administer » Content management » Blog entry » Workflow settings
While at it you can change also comments settings as you like.
You might also want to install the Preserve Language module, so to have the language switcher to only change the interface language.
Views
In Drupal they say: “No matter what the question is, the answer is Views”.
Showing nodes, feeds for nodes and comments
To show site content on frontpage according to our goals, use the Select Translation module, Enable the frontpage view, add the filter provided by this module to the Page display only. Then edit Site information
and set the new frontpage path in Site Inforations
page; you may want to adjust your robots.txt as well as suggested here.
To have language dependent feeds edit the Feed display (you may want to rename it to Feed by language) of the frontpage view, add an Override Filter to nodes by Node translation: Language, and use Current Language and No language. You can now filter the items in the feed according to the language prefix.
Change the path to feed
.
To have a global feed with any content in it, add a new feed display to the frontpage view and set its path to all/feed
with no language filters set.
Two notes about feeds:
- You may want to remove the Sort Criterion: Node Sticky, it should not matter for feed users if a node is sticky.
-
You might want to make sure that item links in feeds are prefixed with the content language, and not with the current interface language.
With a patch like this one it could be done:
Index: views/modules/node/views_plugin_row_node_rss.inc =================================================================== --- views.orig/modules/node/views_plugin_row_node_rss.inc 2009-04-18 10:25:29.000000000 +0200 +++ views/modules/node/views_plugin_row_node_rss.inc 2009-04-18 10:33:08.000000000 +0200 @@ -92,7 +92,8 @@ $item = new stdClass(); $item->title = $node->title; - $item->link = url("node/$row->nid", array('absolute' => TRUE)); + $__langs = language_list(); + $item->link = url("node/$row->nid", array('absolute' => TRUE, 'language' => $__langs[$node->language])); // Allow modules to add additional item fields and/or modify $item $extra = node_invoke_nodeapi($node, 'rss item');
However, this is not desirable if you think about it; we eventually decided to separate the interface language from the content language, so we let Drupal handle the interface language selection automatically as a URL the prefix.
In setting up feeds for comments, Views comes to the rescue once again: create a new Comment feeds view, add a Feed display to it (maybe changing its name to Feed by language), set its path to comments/feed
, add a relationship with Comment: Node
and add an Override Filter to comments by “Node translation: Language”, and use Current Language and No language. Add a sort criterion “Comment: Post date” desc.
To have a global feed with any comments in it, add a new feed display (call it Global feed) to the Comment Feeds view and set its path to all/comments/feed
with no filters set.
NOTE: you may want to take a look at the Link display in views settings, if you need them to be well separated consider using different views for different comment types.
Now that feeds are set up we can add a block to list them:
<h3><?php print(t(Feeds)); ?></h3> <ul class="feed-list"> <li><a href="<?php print base_path(); ?>all/feed" title="All posts">All posts</a></li> <li><a href="<?php print base_path(); ?>en/feed" title="Posts in English">Posts in English</a></li> <li><a href="<?php print base_path(); ?>it/feed" title="Articoli in italiano">Articoli in Italiano</a></li> </ul> <h3><?php print(t(Comments)); ?></h3> <ul class="feed-list"> <li><a href="<?php print base_path(); ?>all/comments/feed" title="Comments to all posts">Comments of all posts</a></li> <li><a href="<?php print base_path(); ?>en/comments/feed" title="Comments to posts in English">Comments in English</a></li> <li><a href="<?php print base_path(); ?>it/comments/feed" title="Commenti degli articoli in italiano">Commenti in Italiano</a></li> </ul>
If you want also a per-node feed to track comments of that particular feed you can do it this way: create another view named node_feeds (maybe cloning comments_recent and editing it? Or adding a Relationship with Nodes anyway) add a Feed display (call it Node comments Feed) to the node_feeds view, set its path to node/%/feed
to use the argument (Node) Node: Nid. Set the title as %1
to get the actual node title in the feed display. Now every node has a local feed, if node 1 is in English you get the feed at en/node/1/feed
. Attach the feed to the page display with something like this in your theme:
if (!$node->teaser and $node->comment != COMMENT_NODE_DISABLED) { $url = url('node/'. $node->nid . '/feed'); $title = t('Comments for ') . $node->title; print theme('feed_icon', $url, $title); drupal_add_feed($url, $title); }
Taxonomies
Add a vocabulary named Tags, in Multilingual options choose Localize terms. Terms are common for all languages, but their name and description may be localized., this way we can use a tag for multiple languages, and check the Tags item in Settings.
Coming to taxonomy feeds: we want a per-vocabulary feed (not very important) and a per-term feed, the latter is important because we can use it to be added to “planets” or to other thematic aggregation mechanism without polluting them with out of context material.
Enable the taxonomy_term view.
For tags cloud use the tagadelic module.
Search Engine Friendly URLs
To have Clean URLs
Configure Apache for Drupal Clean URLs
a2enmod rewrite
And add "AllowOverwrite All" to the virtualhost config file in /etc/apache2/sites-enabled/
Restart apache
invoke-rc.d apache2 restart
And set the right RewriteBase in Drupal site .htaccess
if needed.
Enable Clean URLs in Drupal Administer » Site Configuration » Clean URLs
.
Even More Friendly URLs
For Search Engine Friendly URLs in Drupal, install the Token and PathAuto modules, setup transliteration in i18n-ascii.txt as explained in Pathauto INSTALL.txt and
configure alias patters in Administer -> URLs aliases -> Automated alias settings
If you have the Path Redirect module you can choose Create a new alias. Redirect from old alias. as an Update Action in General Settings.
Node Path Settings for blogs, something like:
blog/[yyyy]/[mm]/[dd]/[title-raw]
for stories (or news):
news/[yyyy]/[mm]/[dd]/[title-raw]
for pages:
[menupath-raw]
for taxonomies:
tags/[cat-raw]
Check Bulk Update for any content type and save. If you don't set language-specific patterns this patch would be handy to have alias generated for all languages: http://drupal.org/node/321848
Gravatar
Download the gravatar module and check
Home » Administer » Site building » Themes » Themes
and User settings to be sure you enabled user pictures.
Check also the “use gravatar” permission to grant access to the gravatar module functionality to anonymous and authenticated users.
Related or Similar content
Use the similarterms module.
Trackbacks and pingbacks
There are Drupal modules for trackback and pingback functionality, but they both have issues.
The trackback modules represents trackbacks as content separate from comments, and I like that very much, but it lacks automatic pingback functionality.
The pingback module offers the automatinc pingback as per pingback specification, but it it not very solid, you can easily break titles with special characters (see here) and it represents pingbacks as comments, which IMHO can interrupt the discussion between humans.
We should really complete and port the pingback functionality to the trackback module and be happy with it.
SEO checklist
Use the SEO checklist module to have a guide about how to improve your position in search engines, I recommend particularly the Meta tags and Page Title modules.